目录
① 存储角色/用户所能访问的 API
② 实现 IAuthorizationRequirement 接口
③ 实现 TokenValidationParameters
④ 生成 Token
⑤ 实现服务注入和身份认证配置
⑥ 实现登陆
⑦ 添加 API 授权策略
⑧ 实现自定义授权校验
⑨ 一些有用的代码
① 存储角色/用户所能访问的 API
例如 使用List<ApiPermission>
存储角色的授权 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());// 定义如何生成用户的 Tokenvar tokenValidationParameters = RolePermission.GetTokenValidationParameters();
配置 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 pletedTask;}};});
注入自定义的授权服务 PermissionHandler
注入自定义认证模型类 roleRequirement
// 添加 httpcontext 拦截services.AddSingleton<IAuthorizationHandler, PermissionHandler>();services.AddSingleton(roleRequirement);
添加中间件
貌似这两个不区分先后顺序
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 角色/策略授权,需要继承AuthorizationHandler<TRequirement>
。
里面的内容是完全自定义的,AuthorizationHandlerContext
是认证授权的上下文,在此实现自定义的访问授权认证。
也可以加上自动刷新 Token 的功能。
/// <summary>/// 验证用户信息,进行权限授权Handler/// </summary>public class PermissionHandler : AuthorizationHandler<PermissionRequirement>{protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,PermissionRequirement requirement){List<PermissionRequirement> requirements = new List<PermissionRequirement>();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 pletedTask;}}
⑨ 一些有用的代码
将字符串生成哈希值,例如密码。
为了安全,删除字符串里面的特殊字符,例如"
、'
、$
。
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 = puteHash(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;}
表示时间戳