介绍
当前多数项目采用前后端分离架构,权限控制是保障系统安全的核心环节。JWT(JSON Web Token)作为一种无状态认证方案,无需在服务器端保存传统 Session,仅需客户端携带 Token 即可完成身份认证,天然适配跨域、多端、分布式部署等场景。
本次完善框架的 JWT 认证授权机制,将 Token 配置、Token 生成、当前登录用户上下文、接口权限注解、策略授权统一封装到 Common 层。业务接口只需要通过 [Authorize]、[AllowAnonymous] 或 [Permission("xxx")] 即可完成接口级权限控制,同时也为角色权限、接口权限、按钮权限、数据权限等后续扩展保留统一入口。
实现
1.JWT配置
首先在配置文件中增加 JWT 节点,将签发方、接收方、密钥、过期时间等核心参数统一管理,避免硬编码散落在业务代码中。
"Jwt": {
"Issuer": "HQServer",
"Audience": "HQServer.Client",
"SecurityKey": "HQServer-development-secret-key-change-me",
"ExpireMinutes": 120,
"RefreshExpireDays": 7,
"ClockSkewSeconds": 30
}
namespace HQ.Common.Authentication;
public class JwtOptions
{
public string Issuer { get; set; } = "HQServer";
public string Audience { get; set; } = "HQServer.Client";
public string SecurityKey { get; set; } = string.Empty;
public int ExpireMinutes { get; set; } = 120;
public int RefreshExpireDays { get; set; } = 7;
public int ClockSkewSeconds { get; set; } = 30;
}
2.Token生成服务
Token 生成逻辑统一封装为 IJwtTokenService,登录成功后业务层只需要传入用户编号、用户名、角色、权限等基础信息,不直接关心 Claim 组装和签名细节。
public interface IJwtTokenService
{
string GenerateAccessToken(LoginUserDto user);
}
public class LoginUserDto
{
public string UserId { get; set; } = string.Empty;
public string? UserName { get; set; }
public string? NickName { get; set; }
public List<string> Roles { get; set; } = new();
public List<string> Permissions { get; set; } = new();
}
public class JwtTokenService : IJwtTokenService
{
private readonly JwtOptions _options;
public JwtTokenService(IOptions<JwtOptions> options)
{
_options = options.Value;
}
public string GenerateAccessToken(LoginUserDto user)
{
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.UserId),
new(ClaimTypes.Name, user.UserName ?? string.Empty),
new(HQClaimTypes.NickName, user.NickName ?? string.Empty)
};
claims.AddRange(user.Roles
.Where(role => !string.IsNullOrWhiteSpace(role))
.Distinct()
.Select(role => new Claim(ClaimTypes.Role, role)));
claims.AddRange(user.Permissions
.Where(permission => !string.IsNullOrWhiteSpace(permission))
.Distinct()
.Select(permission => new Claim(HQClaimTypes.Permission, permission)));
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.SecurityKey));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _options.Issuer,
audience: _options.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_options.ExpireMinutes),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
3.当前登录用户上下文
接口中经常需要读取当前登录用户信息,如果每个接口都手动解析 HttpContext.User 会产生大量重复代码。因此封装 ICurrentUser,统一暴露用户编号、用户名、角色和权限。
public interface ICurrentUser
{
bool IsAuthenticated { get; }
string? UserId { get; }
string? UserName { get; }
IReadOnlyList<string> Roles { get; }
IReadOnlyList<string> Permissions { get; }
}
4.注解式权限控制
框架保留 ASP.NET Core 原生 [Authorize] 与 [AllowAnonymous] 能力,同时增加 [Permission] 权限注解,让业务接口可以用更直观的方式声明权限点。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class PermissionAttribute : AuthorizeAttribute
{
public const string PolicyPrefix = "Permission:";
public PermissionAttribute(string permission)
{
if (string.IsNullOrWhiteSpace(permission))
throw new ArgumentException("权限标识不能为空", nameof(permission));
Permission = permission;
Policy = PolicyPrefix + permission;
}
public string Permission { get; }
}
5.策略授权模式
策略授权的核心是 Requirement + Handler。Requirement 描述授权要求,Handler 负责判断当前用户是否满足要求。这样权限判断逻辑可以集中维护,不需要写在 Controller 或 Service 中。
public class PermissionRequirement : IAuthorizationRequirement
{
public PermissionRequirement(string permission)
{
Permission = permission;
}
public string Permission { get; }
}
public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
if (context.User.Identity?.IsAuthenticated != true)
return Task.CompletedTask;
var permissions = context.User
.FindAll(HQClaimTypes.Permission)
.Select(x => x.Value)
.Where(x => !string.IsNullOrWhiteSpace(x))
.ToHashSet(StringComparer.OrdinalIgnoreCase);
if (context.User.IsInRole("Admin")
|| permissions.Contains("*:*:*")
|| permissions.Contains(requirement.Permission))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
6.动态权限策略
权限点通常很多,如果每个权限都手动注册 Policy,会导致配置膨胀。因此通过 IAuthorizationPolicyProvider 动态识别 Permission: 前缀,按需生成授权策略。
public class PermissionPolicyProvider : DefaultAuthorizationPolicyProvider
{
public PermissionPolicyProvider(IOptions<AuthorizationOptions> options)
: base(options)
{
}
public override async Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(PermissionAttribute.PolicyPrefix, StringComparison.OrdinalIgnoreCase))
{
var permission = policyName[PermissionAttribute.PolicyPrefix.Length..];
return new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddRequirements(new PermissionRequirement(permission))
.Build();
}
return await base.GetPolicyAsync(policyName);
}
}
7.统一注册入口
最后将 JWT 认证、当前用户上下文、权限策略提供器、授权处理器统一注册到 DI 容器中,业务项目只需要在启动管线中调用一次即可。
public static IServiceCollection AddHQJwtAuthentication(
this IServiceCollection services,
IConfiguration configuration)
{
services.Configure<JwtOptions>(configuration.GetSection("Jwt"));
var options = configuration.GetSection("Jwt").Get<JwtOptions>()
?? throw new InvalidOperationException("缺少 Jwt 配置节点");
if (string.IsNullOrWhiteSpace(options.SecurityKey) || options.SecurityKey.Length < 32)
throw new InvalidOperationException("Jwt:SecurityKey 长度不能小于 32 位");
services.AddHttpContextAccessor();
services.AddScoped<ICurrentUser, CurrentUser>();
services.AddScoped<IJwtTokenService, JwtTokenService>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(config =>
{
config.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = options.Issuer,
ValidateAudience = true,
ValidAudience = options.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(options.SecurityKey)),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(options.ClockSkewSeconds)
};
});
services.AddHQAuthorization();
return services;
}
builder.Services.AddHQJwtAuthentication(builder.Configuration);
app.UseAuthentication();
app.UseAuthorization();
特点
特性说明
无状态认证:服务端不需要保存传统 Session,适合前后端分离、多端登录、分布式部署。
统一封装:Token 生成、用户上下文、认证注册、授权策略全部沉淀在 Common 层,业务项目开箱即用。
注解控制:接口通过 [Authorize]、[AllowAnonymous]、[Permission("xxx")] 即可完成认证与权限控制。
策略扩展:基于 ASP.NET Core Policy 模型,可灵活扩展角色权限、接口权限、按钮权限、数据权限。
动态权限:通过自定义 IAuthorizationPolicyProvider 动态创建权限策略,无需为每个权限点手动注册。
安全防御:校验 Issuer、Audience、签名密钥、过期时间,并限制 ClockSkew,避免 Token 长时间误放行。
使用示例
[AllowAnonymous]
[HttpPost("login")]
public async Task<IActionResult> Login(LoginInput input)
{
// 1. 校验用户名密码
// 2. 查询角色和权限
var user = new LoginUserDto
{
UserId = "10001",
UserName = input.UserName,
NickName = "管理员",
Roles = new List<string> { "Admin" },
Permissions = new List<string> { "user:list", "user:create", "user:update" }
};
var token = _jwtTokenService.GenerateAccessToken(user);
return Ok(new { accessToken = token });
}
[Authorize]
[HttpGet("profile")]
public IActionResult GetProfile([FromServices] ICurrentUser currentUser)
{
return Ok(new
{
currentUser.UserId,
currentUser.UserName,
currentUser.Roles,
currentUser.Permissions
});
}
[Permission("user:create")]
[HttpPost("user")]
public async Task<IActionResult> CreateUser(CreateUserInput input)
{
// 当前用户必须拥有 user:create 权限,或拥有 Admin / *:*:* 超级权限
return Ok();
}
完整代码
建议在 Common 层按下面结构存放核心文件:
HQ.Common
├── Authentication
│ ├── JwtOptions.cs
│ ├── LoginUserDto.cs
│ ├── IJwtTokenService.cs
│ ├── JwtTokenService.cs
│ ├── ICurrentUser.cs
│ ├── CurrentUser.cs
│ ├── HQClaimTypes.cs
│ └── JwtServiceCollectionExtensions.cs
└── Authorization
├── PermissionAttribute.cs
├── PermissionRequirement.cs
├── PermissionAuthorizationHandler.cs
├── PermissionPolicyProvider.cs
└── AuthorizationServiceCollectionExtensions.cs
以上封装完成后,业务项目无需关心 JWT 的底层校验细节,也无需在每个接口中重复编写权限判断代码。Controller 只负责声明访问规则,具体认证与授权逻辑由框架统一处理。
总结
本次完善后,框架具备了完整的 JWT 认证授权能力:登录后签发 Token,请求时自动校验 Token,接口通过注解控制访问权限,复杂权限通过策略授权统一扩展。后续如果需要接入菜单权限、按钮权限、租户隔离、数据范围控制,也可以继续沿用当前策略授权入口进行扩展,保证权限体系清晰、稳定、可维护。










暂无评论内容