ASP.NET Core 使用 JWT 自定义角色/策略授权需要实现的接口
① 存储角色/用户所能访问的 API
例如
使用 ListApiPermission>
存储角色的授权 API 列表。
可有可无。
可以把授权访问的 API 存放到 Token 中,Token 也可以只存放角色信息和用户身份信息。
/// summary> /// API /// /summary> public class ApiPermission { /// summary> /// API名称 /// /summary> public virtual string Name { get; set; } /// summary> /// API地址 /// /summary> public virtual string Url { get; set; } }
② 实现 IAuthorizationRequirement 接口
IAuthorizationRequirement
接口代表了用户的身份信息,作为认证校验、授权校验使用。
事实上,IAuthorizationRequirement
没有任何要实现的内容。
namespace Microsoft.AspNetCore.Authorization { // // 摘要: // Represents an authorization requirement. public interface IAuthorizationRequirement { } }
实现 IAuthorizationRequirement
,可以任意定义需要的属性,这些会作为自定义验证的便利手段。
要看如何使用,可以定义为全局标识,设置全局通用的数据。
我后面发现我这种写法不太好:
//IAuthorizationRequirement 是 Microsoft.AspNetCore.Authorization 接口 /// summary> /// 用户认证信息必要参数类 /// /summary> public class PermissionRequirement : IAuthorizationRequirement { /// summary> /// 用户所属角色 /// /summary> public Role Roles { get; set; } = new Role(); public void SetRolesName(string roleName) { Roles.Name = roleName; } /// summary> /// 无权限时跳转到此API /// /summary> public string DeniedAction { get; set; } /// summary> /// 认证授权类型 /// /summary> public string ClaimType { internal get; set; } /// summary> /// 未授权时跳转 /// /summary> public string LoginPath { get; set; } = "/Account/Login"; /// summary> /// 发行人 /// /summary> public string Issuer { get; set; } /// summary> /// 订阅人 /// /summary> public string Audience { get; set; } /// summary> /// 过期时间 /// /summary> public TimeSpan Expiration { get; set; } /// summary> /// 颁发时间 /// /summary> public long IssuedTime { get; set; } /// summary> /// 签名验证 /// /summary> public SigningCredentials SigningCredentials { get; set; } /// summary> /// 构造 /// /summary> /// param name="deniedAction"> 无权限时跳转到此API/param> /// param name="userPermissions"> 用户权限集合/param> /// param name="deniedAction"> 拒约请求的url/param> /// param name="permissions"> 权限集合/param> /// param name="claimType"> 声明类型/param> /// param name="issuer"> 发行人/param> /// param name="audience"> 订阅人/param> /// param name="issusedTime"> 颁发时间/param> /// param name="signingCredentials"> 签名验证实体/param> public PermissionRequirement(string deniedAction, Role Role, string claimType, string issuer, string audience, SigningCredentials signingCredentials,long issusedTime, TimeSpan expiration) { ClaimType = claimType; DeniedAction = deniedAction; Roles = Role; Issuer = issuer; Audience = audience; Expiration = expiration; IssuedTime = issusedTime; SigningCredentials = signingCredentials; } }
③ 实现 TokenValidationParameters
Token 的信息配置
public static TokenValidationParameters GetTokenValidationParameters() { var tokenValida = new TokenValidationParameters { // 定义 Token 内容 ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AuthConfig.SecurityKey)), ValidateIssuer = true, ValidIssuer = AuthConfig.Issuer, ValidateAudience = true, ValidAudience = AuthConfig.Audience, ValidateLifetime = true, ClockSkew = TimeSpan.Zero, RequireExpirationTime = true } ; return tokenValida; }
④ 生成 Token
用于将用户的身份信息(Claims)和角色授权信息(PermissionRequirement)存放到 Token 中。
/// summary> /// 获取基于JWT的Token /// /summary> /// param name="username"> /param> /// returns> /returns> public static dynamic BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement) { var now = DateTime.UtcNow; var jwt = new JwtSecurityToken( issuer: permissionRequirement.Issuer, audience: permissionRequirement.Audience, claims: claims, notBefore: now, expires: now.Add(permissionRequirement.Expiration), signingCredentials: permissionRequirement.SigningCredentials ); var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); var response = new { Status = true, access_token = encodedJwt, expires_in = permissionRequirement.Expiration.TotalMilliseconds, token_type = "Bearer" } ; return response; }
⑤ 实现服务注入和身份认证配置
从别的变量导入配置信息,可有可无
// 设置用于加密 Token 的密钥 // 配置角色权限 var roleRequirement = RolePermission.GetRoleRequirement(AccountHash.GetTokenSecurityKey()); // 定义如何生成用户的 Token var tokenValidationParameters = RolePermission.GetTokenValidationParameters();
配置 ASP.NET Core 的身份认证服务
需要实现三个配置
- AddAuthorization 导入角色身份认证策略
- AddAuthentication 身份认证类型
- AddJwtBearer Jwt 认证配置
// 导入角色身份认证策略 services.AddAuthorization(options => { options.AddPolicy("Permission", policy => policy.Requirements.Add(roleRequirement)); // ↓ 身份认证类型 } ).AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; // ↓ Jwt 认证配置 } ) .AddJwtBearer(options => { options.TokenValidationParameters = tokenValidationParameters; options.SaveToken = true; options.Events = new JwtBearerEvents() { // 在安全令牌通过验证和ClaimsIdentity通过验证之后调用 // 如果用户访问注销页面 OnTokenValidated = context => { if (context.Request.Path.Value.ToString() == "/account/logout") { var token = ((context as TokenValidatedContext).SecurityToken as JwtSecurityToken).RawData; } return Task.CompletedTask; } } ; } );
注入自定义的授权服务 PermissionHandler
注入自定义认证模型类 roleRequirement
// 添加 httpcontext 拦截 services.AddSingletonIAuthorizationHandler, PermissionHandler> (); services.AddSingleton(roleRequirement);
添加中间件
在微软官网看到例子是这样的。。。但是我测试发现,客户端携带了 Token 信息,请求通过验证上下文,还是失败,这样使用会返回403。
app.UseAuthentication(); app.UseAuthorization();
发现这样才OK:
app.UseAuthorization(); app.UseAuthentication();
参考文章下面的评论~
⑥ 实现登陆
可以在颁发 Token 时把能够使用的 API 存储进去,但是这种方法不适合 API 较多的情况。
可以存放 用户信息(Claims)和角色信息,后台通过角色信息获取授权访问的 API 列表。
/// summary> /// 登陆 /// /summary> /// param name="username"> 用户名/param> /// param name="password"> 密码/param> /// returns> Token信息/returns> [HttpPost("login")] public JsonResult Login(string username, string password) { var user = UserModel.Users.FirstOrDefault(x => x.UserName == username & & x.UserPossword == password); if (user == null) return new JsonResult( new ResponseModel { Code = 0, Message = "登陆失败!" } ); // 配置用户标识 var userClaims = new Claim[] { new Claim(ClaimTypes.Name,user.UserName), new Claim(ClaimTypes.Role,user.Role), new Claim(ClaimTypes.Expiration,DateTime.Now.AddMinutes(_requirement.Expiration.TotalMinutes).ToString()), } ; _requirement.SetRolesName(user.Role); // 生成用户标识 var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme); identity.AddClaims(userClaims); var token = JwtToken.BuildJwtToken(userClaims, _requirement); return new JsonResult( new ResponseModel { Code = 200, Message = "登陆成功!请注意保存你的 Token 凭证!", Data = token } ); }
⑦ 添加 API 授权策略
[Authorize(Policy = "Permission")]
⑧ 实现自定义授权校验
要实现自定义 API 角色/策略授权,需要继承 AuthorizationHandlerTRequirement>
。
里面的内容是完全自定义的, AuthorizationHandlerContext
是认证授权的上下文,在此实现自定义的访问授权认证。
也可以加上自动刷新 Token 的功能。
/// summary> /// 验证用户信息,进行权限授权Handler /// /summary> public class PermissionHandler : AuthorizationHandlerPermissionRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { ListPermissionRequirement> requirements = new ListPermissionRequirement> (); foreach (var item in context.Requirements) { requirements.Add((PermissionRequirement)item); } foreach (var item in requirements) { // 校验 颁发和接收对象 if (!(item.Issuer == AuthConfig.Issuer ? item.Audience == AuthConfig.Audience ? true : false : false)) { context.Fail(); } // 校验过期时间 var nowTime = DateTimeOffset.Now.ToUnixTimeSeconds(); var issued = item.IssuedTime +Convert.ToInt64(item.Expiration.TotalSeconds); if (issued nowTime) context.Fail(); // 是否有访问此 API 的权限 var resource = ((Microsoft.AspNetCore.Routing.RouteEndpoint)context.Resource).RoutePattern; var permissions = item.Roles.Permissions.ToList(); var apis = permissions.Any(x => x.Name.ToLower() == item.Roles.Name.ToLower() & & x.Url.ToLower() == resource.RawText.ToLower()); if (!apis) context.Fail(); context.Succeed(requirement); // 无权限时跳转到某个页面 //var httpcontext = new HttpContextAccessor(); //httpcontext.HttpContext.Response.Redirect(item.DeniedAction); } context.Succeed(requirement); return Task.CompletedTask; } }
⑨ 一些有用的代码
将字符串生成哈希值,例如密码。
为了安全,删除字符串里面的特殊字符,例如 "
、'
、$
。
public static class AccountHash { // 获取字符串的哈希值 public static string GetByHashString(string str) { string hash = GetMd5Hash(str.Replace("\"", String.Empty) .Replace("\'", String.Empty) .Replace("$", String.Empty)); return hash; } /// summary> /// 获取用于加密 Token 的密钥 /// /summary> /// returns> /returns> public static SigningCredentials GetTokenSecurityKey() { var securityKey = new SigningCredentials( new SymmetricSecurityKey( Encoding.UTF8.GetBytes(AuthConfig.SecurityKey)), SecurityAlgorithms.HmacSha256); return securityKey; } private static string GetMd5Hash(string source) { MD5 md5Hash = MD5.Create(); byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(source)); StringBuilder sBuilder = new StringBuilder(); for (int i = 0; i data.Length; i++) { sBuilder.Append(data[i].ToString("x2")); } return sBuilder.ToString(); } }
签发 Token
PermissionRequirement
不是必须的,用来存放角色或策略认证信息,Claims 应该是必须的。
/// summary> /// 颁发用户Token /// /summary> public class JwtToken { /// summary> /// 获取基于JWT的Token /// /summary> /// param name="username"> /param> /// returns> /returns> public static dynamic BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement) { var now = DateTime.UtcNow; var jwt = new JwtSecurityToken( issuer: permissionRequirement.Issuer, audience: permissionRequirement.Audience, claims: claims, notBefore: now, expires: now.Add(permissionRequirement.Expiration), signingCredentials: permissionRequirement.SigningCredentials ); var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); var response = new { Status = true, access_token = encodedJwt, expires_in = permissionRequirement.Expiration.TotalMilliseconds, token_type = "Bearer" } ; return response; }
表示时间戳
// Unix 时间戳 DateTimeOffset.Now.ToUnixTimeSeconds(); // 检验 Token 是否过期 // 将 TimeSpan 转为 Unix 时间戳 Convert.ToInt64(TimeSpan); DateTimeOffset.Now.ToUnixTimeSeconds() + Convert.ToInt64(TimeSpan);
一个逗逗的大学生
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: ASP.NET Core 使用 JWT 自定义角色/策略授权需要实现的接口
本文地址: https://pptw.com/jishu/298664.html