ASP.NET项目级用户权限管理咨询:策略及存储方案探讨
Great question—this is such a common scenario for content-sharing apps, and ASP.NET has all the built-in tools you need to handle this properly, no need to reinvent the wheel entirely. Let’s break this down:
1. Yes, ASP.NET’s Policy Authorization is Perfect for This
Role-based authorization works great for global permissions (like "Admin" or "Basic User"), but it falls short when you need resource-specific (in your case, Project-specific) access control. That’s exactly what policy-based authorization was designed for. Here’s how to implement it:
Step 1: Define a Permission Requirement
First, create a custom requirement that represents the permission you want to check (e.g., viewing or editing a project):
public enum ProjectPermissionType { View, Edit } public class ProjectPermissionRequirement : IAuthorizationRequirement { public ProjectPermissionType RequiredPermission { get; } public ProjectPermissionRequirement(ProjectPermissionType requiredPermission) { RequiredPermission = requiredPermission; } }
Step 2: Build an Authorization Handler
Next, write a handler that checks if the current user has the required permission for the target project. This is where you’ll query your database to look up the user’s permissions for that specific project:
public class ProjectPermissionHandler : AuthorizationHandler<ProjectPermissionRequirement, Project> { private readonly YourDbContext _dbContext; public ProjectPermissionHandler(YourDbContext dbContext) { _dbContext = dbContext; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ProjectPermissionRequirement requirement, Project project) { // Get the current user's ID from their claims var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) { context.Fail(); return; } // Project creators get full permissions by default if (project.CreatorId == userId) { context.Succeed(requirement); return; } // Look up the user's explicit permission for this project var userPermission = await _dbContext.ProjectPermissions .FirstOrDefaultAsync(p => p.ProjectId == project.Id && p.UserId == userId); if (userPermission != null && userPermission.PermissionType == requirement.RequiredPermission) { context.Succeed(requirement); } else { context.Fail(); } } }
Step 3: Register the Policy and Handler
In your Program.cs, register the handler and define named policies for each permission type:
builder.Services.AddAuthorization(options => { options.AddPolicy("CanViewProject", policy => policy.Requirements.Add(new ProjectPermissionRequirement(ProjectPermissionType.View))); options.AddPolicy("CanEditProject", policy => policy.Requirements.Add(new ProjectPermissionRequirement(ProjectPermissionType.Edit))); }); builder.Services.AddScoped<IAuthorizationHandler, ProjectPermissionHandler>();
Step 4: Use the Policy in Your Controllers
You can apply the policy to controller actions, and even explicitly check authorization for granular control:
[Authorize(Policy = "CanEditProject")] public async Task<IActionResult> Edit(int projectId) { var project = await _dbContext.Projects.FindAsync(projectId); if (project == null) return NotFound(); // Optional: Explicit authorization check if you need custom failure logic var authResult = await _authorizationService.AuthorizeAsync(User, project, "CanEditProject"); if (!authResult.Succeeded) { return Forbid(); } // Proceed with your edit logic return View(project); }
2. Storing User-Project Permissions is the Standard (and Safe) Approach
Your idea of storing user IDs alongside their project-specific permissions is exactly the right approach. Here’s a recommended database schema for this:
- Projects Table:
Id(primary key),CreatorId(foreign key to Users),Name,Description, and other project fields. - ProjectPermissions Table:
Id(primary key),ProjectId(foreign key to Projects),UserId(foreign key to Users),PermissionType(enum or string representing View/Edit).
Key Security Notes for This Approach:
- Never trust client input: Always validate that the user has permission to access the specific
projectIdthey’re requesting—frontend checks are easy to bypass, so all logic must happen server-side. - Optimize database queries: When fetching projects for a user, filter directly in the database (e.g.,
_dbContext.Projects.Where(p => p.CreatorId == userId || p.Permissions.Any(perm => perm.UserId == userId && perm.PermissionType == View))) instead of fetching all projects and filtering in memory. - Cache strategically: If your app has high traffic, cache user permissions for a project to reduce database hits—just make sure to invalidate the cache immediately when permissions are updated.
3. Are There More Secure Alternatives?
This user-project permission model is the industry standard for resource-specific access control, and when implemented correctly with ASP.NET’s built-in tools, it’s highly secure. The main "alternatives" are just variations on this theme:
- Cached claims: You could add project-specific claims to the user’s identity on login, but this only works if the number of projects per user is small—otherwise, the authentication token (JWT) will get too large and unwieldy.
- ABAC (Attribute-Based Access Control): This is a more flexible model where permissions are based on attributes (e.g., "user is part of the project’s team"), but it’s overkill for most simple project-sharing apps. Your original approach is simpler and more maintainable for your use case.
Final Takeaway
Stick with ASP.NET’s policy authorization combined with a ProjectPermissions table—this gives you the best of both worlds: leveraging ASP.NET’s secure, battle-tested authorization framework while supporting the project-specific permission granularity you need. It’s scalable, maintainable, and aligns with best practices for this kind of app.
内容的提问来源于stack exchange,提问作者user57029

