ASP.NET Core使用JWT自定義角色並實現策略授權需要的接口
① 存儲角色/用戶所能訪問的 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()); // 定義如何生成用戶的 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.AddSingleton<IAuthorizationHandler, 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 角色/策略授權,需要繼承 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 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);
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- .net core api接口JWT方式認證Token
- .NET Core支持Cookie和JWT混合認證、授權的方法
- .Net Core實現JWT授權認證
- asp.net core MVC之實現基於token的認證
- asp.net core3.1cookie和jwt混合認證授權實現多種身份驗證方案