ASP.NET Core使用JWT自定義角色并實(shí)現(xiàn)策略授權(quán)需要的接口
① 存儲角色/用戶所能訪問的 API
例如
使用 List<ApiPermission>
存儲角色的授權(quán) API 列表。
可有可無。
可以把授權(quán)訪問的 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; } }
② 實(shí)現(xiàn) IAuthorizationRequirement 接口
IAuthorizationRequirement
接口代表了用戶的身份信息,作為認(rèn)證校驗(yàn)、授權(quán)校驗(yàn)使用。
事實(shí)上,IAuthorizationRequirement
沒有任何要實(shí)現(xiàn)的內(nèi)容。
namespace Microsoft.AspNetCore.Authorization { // // 摘要: // Represents an authorization requirement. public interface IAuthorizationRequirement { } }
實(shí)現(xiàn) IAuthorizationRequirement
,可以任意定義需要的屬性,這些會作為自定義驗(yàn)證的便利手段。
要看如何使用,可以定義為全局標(biāo)識,設(shè)置全局通用的數(shù)據(jù)。
我后面發(fā)現(xiàn)我這種寫法不太好:
//IAuthorizationRequirement 是 Microsoft.AspNetCore.Authorization 接口 /// <summary> /// 用戶認(rèn)證信息必要參數(shù)類 /// </summary> public class PermissionRequirement : IAuthorizationRequirement { /// <summary> /// 用戶所屬角色 /// </summary> public Role Roles { get; set; } = new Role(); public void SetRolesName(string roleName) { Roles.Name = roleName; } /// <summary> /// 無權(quán)限時跳轉(zhuǎn)到此API /// </summary> public string DeniedAction { get; set; } /// <summary> /// 認(rèn)證授權(quán)類型 /// </summary> public string ClaimType { internal get; set; } /// <summary> /// 未授權(quán)時跳轉(zhuǎn) /// </summary> public string LoginPath { get; set; } = "/Account/Login"; /// <summary> /// 發(fā)行人 /// </summary> public string Issuer { get; set; } /// <summary> /// 訂閱人 /// </summary> public string Audience { get; set; } /// <summary> /// 過期時間 /// </summary> public TimeSpan Expiration { get; set; } /// <summary> /// 頒發(fā)時間 /// </summary> public long IssuedTime { get; set; } /// <summary> /// 簽名驗(yàn)證 /// </summary> public SigningCredentials SigningCredentials { get; set; } /// <summary> /// 構(gòu)造 /// </summary> /// <param name="deniedAction">無權(quán)限時跳轉(zhuǎn)到此API</param> /// <param name="userPermissions">用戶權(quán)限集合</param> /// <param name="deniedAction">拒約請求的url</param> /// <param name="permissions">權(quán)限集合</param> /// <param name="claimType">聲明類型</param> /// <param name="issuer">發(fā)行人</param> /// <param name="audience">訂閱人</param> /// <param name="issusedTime">頒發(fā)時間</param> /// <param name="signingCredentials">簽名驗(yàn)證實(shí)體</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; } }
③ 實(shí)現(xiàn) TokenValidationParameters
Token 的信息配置
public static TokenValidationParameters GetTokenValidationParameters() { var tokenValida = new TokenValidationParameters { // 定義 Token 內(nèi)容 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)和角色授權(quán)信息(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; }
⑤ 實(shí)現(xiàn)服務(wù)注入和身份認(rèn)證配置
從別的變量導(dǎo)入配置信息,可有可無
// 設(shè)置用于加密 Token 的密鑰 // 配置角色權(quán)限 var roleRequirement = RolePermission.GetRoleRequirement(AccountHash.GetTokenSecurityKey()); // 定義如何生成用戶的 Token var tokenValidationParameters = RolePermission.GetTokenValidationParameters();
配置 ASP.NET Core 的身份認(rèn)證服務(wù)
需要實(shí)現(xiàn)三個配置
- AddAuthorization 導(dǎo)入角色身份認(rèn)證策略
- AddAuthentication 身份認(rèn)證類型
- AddJwtBearer Jwt 認(rèn)證配置
// 導(dǎo)入角色身份認(rèn)證策略 services.AddAuthorization(options => { options.AddPolicy("Permission", policy => policy.Requirements.Add(roleRequirement)); // ↓ 身份認(rèn)證類型 }).AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; // ↓ Jwt 認(rèn)證配置 }) .AddJwtBearer(options => { options.TokenValidationParameters = tokenValidationParameters; options.SaveToken = true; options.Events = new JwtBearerEvents() { // 在安全令牌通過驗(yàn)證和ClaimsIdentity通過驗(yàn)證之后調(diào)用 // 如果用戶訪問注銷頁面 OnTokenValidated = context => { if (context.Request.Path.Value.ToString() == "/account/logout") { var token = ((context as TokenValidatedContext).SecurityToken as JwtSecurityToken).RawData; } return Task.CompletedTask; } }; });
注入自定義的授權(quán)服務(wù) PermissionHandler
注入自定義認(rèn)證模型類 roleRequirement
// 添加 httpcontext 攔截 services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); services.AddSingleton(roleRequirement);
添加中間件
在微軟官網(wǎng)看到例子是這樣的。。。但是我測試發(fā)現(xiàn),客戶端攜帶了 Token 信息,請求通過驗(yàn)證上下文,還是失敗,這樣使用會返回403。
app.UseAuthentication(); app.UseAuthorization();
發(fā)現(xiàn)這樣才OK:
app.UseAuthorization(); app.UseAuthentication();
⑥ 實(shí)現(xiàn)登陸
可以在頒發(fā) Token 時把能夠使用的 API 存儲進(jìn)去,但是這種方法不適合 API 較多的情況。
可以存放 用戶信息(Claims)和角色信息,后臺通過角色信息獲取授權(quán)訪問的 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 = "登陸失敗!" }); // 配置用戶標(biāo)識 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); // 生成用戶標(biāo)識 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 授權(quán)策略
[Authorize(Policy = "Permission")]
⑧ 實(shí)現(xiàn)自定義授權(quán)校驗(yàn)
要實(shí)現(xiàn)自定義 API 角色/策略授權(quán),需要繼承 AuthorizationHandler<TRequirement>
。
里面的內(nèi)容是完全自定義的, AuthorizationHandlerContext
是認(rèn)證授權(quán)的上下文,在此實(shí)現(xiàn)自定義的訪問授權(quán)認(rèn)證。
也可以加上自動刷新 Token 的功能。
/// <summary> /// 驗(yàn)證用戶信息,進(jìn)行權(quán)限授權(quán)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) { // 校驗(yàn) 頒發(fā)和接收對象 if (!(item.Issuer == AuthConfig.Issuer ? item.Audience == AuthConfig.Audience ? true : false : false)) { context.Fail(); } // 校驗(yàn)過期時間 var nowTime = DateTimeOffset.Now.ToUnixTimeSeconds(); var issued = item.IssuedTime +Convert.ToInt64(item.Expiration.TotalSeconds); if (issued < nowTime) context.Fail(); // 是否有訪問此 API 的權(quán)限 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); // 無權(quán)限時跳轉(zhuǎn)到某個頁面 //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(); } }
簽發(fā) Token
PermissionRequirement
不是必須的,用來存放角色或策略認(rèn)證信息,Claims 應(yīng)該是必須的。
/// <summary> /// 頒發(fā)用戶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(); // 檢驗(yàn) Token 是否過期 // 將 TimeSpan 轉(zhuǎn)為 Unix 時間戳 Convert.ToInt64(TimeSpan); DateTimeOffset.Now.ToUnixTimeSeconds() + Convert.ToInt64(TimeSpan);
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
asp.net 修改/刪除站內(nèi)目錄操作后Session丟失問題
在Web項(xiàng)目中使用 Directory.Move(olddir,newdir)修改目錄名稱或使用Directory.Delete(true)刪除目錄后, 發(fā)現(xiàn)Session都失效。2010-01-01.NET Core 微信小程序退款步驟——(統(tǒng)一退款)
這篇文章主要介紹了.NET Core 微信小程序退款步驟——(統(tǒng)一退款),本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09AspNet Core上實(shí)現(xiàn)web定時任務(wù)實(shí)例
在本篇文章里小編給大家分享了關(guān)于AspNet Core上實(shí)現(xiàn)web定時任務(wù)的實(shí)例內(nèi)容,有興趣的朋友們學(xué)習(xí)參考下。2019-02-02ASP.NET Core跨站登錄重定向的實(shí)現(xiàn)新姿勢
這篇文章主要給大家介紹了關(guān)于ASP.NET Core實(shí)現(xiàn)跨站登錄重定向的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07ASP.Net中利用CSS實(shí)現(xiàn)多界面的兩種方法
這篇文章主要介紹了ASP.Net中利用CSS實(shí)現(xiàn)多界面的兩種方法,包括動態(tài)加載css樣式與動態(tài)設(shè)置頁面同類控件的方法來實(shí)現(xiàn)該功能,是非常具有實(shí)用價值的技巧,需要的朋友可以參考下2014-12-12ASP.NET 鏈接 Access 數(shù)據(jù)庫路徑問題最終解決方案
ASP.NET 鏈接 Access 數(shù)據(jù)庫路徑問題最終解決方案...2007-04-04