ASP.NET Core使用JWT自定義角色并實(shí)現(xiàn)策略授權(quán)需要的接口
① 存儲(chǔ)角色/用戶所能訪問(wèn)的 API
例如
使用 List<ApiPermission>
存儲(chǔ)角色的授權(quán) API 列表。
可有可無(wú)。
可以把授權(quán)訪問(wè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
,可以任意定義需要的屬性,這些會(huì)作為自定義驗(yàn)證的便利手段。
要看如何使用,可以定義為全局標(biāo)識(shí),設(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> /// 無(wú)權(quán)限時(shí)跳轉(zhuǎn)到此API /// </summary> public string DeniedAction { get; set; } /// <summary> /// 認(rèn)證授權(quán)類型 /// </summary> public string ClaimType { internal get; set; } /// <summary> /// 未授權(quán)時(shí)跳轉(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> /// 過(guò)期時(shí)間 /// </summary> public TimeSpan Expiration { get; set; } /// <summary> /// 頒發(fā)時(shí)間 /// </summary> public long IssuedTime { get; set; } /// <summary> /// 簽名驗(yàn)證 /// </summary> public SigningCredentials SigningCredentials { get; set; } /// <summary> /// 構(gòu)造 /// </summary> /// <param name="deniedAction">無(wú)權(quán)限時(shí)跳轉(zhuǎn)到此API</param> /// <param name="userPermissions">用戶權(quán)限集合</param> /// <param name="deniedAction">拒約請(qǐng)求的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ā)時(shí)間</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)入配置信息,可有可無(wú)
// 設(shè)置用于加密 Token 的密鑰 // 配置角色權(quán)限 var roleRequirement = RolePermission.GetRoleRequirement(AccountHash.GetTokenSecurityKey()); // 定義如何生成用戶的 Token var tokenValidationParameters = RolePermission.GetTokenValidationParameters();
配置 ASP.NET Core 的身份認(rèn)證服務(wù)
需要實(shí)現(xiàn)三個(gè)配置
- 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() { // 在安全令牌通過(guò)驗(yàn)證和ClaimsIdentity通過(guò)驗(yàn)證之后調(diào)用 // 如果用戶訪問(wèn)注銷頁(yè)面 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)看到例子是這樣的。。。但是我測(cè)試發(fā)現(xiàn),客戶端攜帶了 Token 信息,請(qǐng)求通過(guò)驗(yàn)證上下文,還是失敗,這樣使用會(huì)返回403。
app.UseAuthentication(); app.UseAuthorization();
發(fā)現(xiàn)這樣才OK:
app.UseAuthorization(); app.UseAuthentication();
⑥ 實(shí)現(xiàn)登陸
可以在頒發(fā) Token 時(shí)把能夠使用的 API 存儲(chǔ)進(jìn)去,但是這種方法不適合 API 較多的情況。
可以存放 用戶信息(Claims)和角色信息,后臺(tái)通過(guò)角色信息獲取授權(quán)訪問(wè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)識(shí) 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)識(shí) var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme); identity.AddClaims(userClaims); var token = JwtToken.BuildJwtToken(userClaims, _requirement); return new JsonResult( new ResponseModel { Code = 200, Message = "登陸成功!請(qǐng)注意保存你的 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)自定義的訪問(wèn)授權(quán)認(rèn)證。
也可以加上自動(dòng)刷新 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ā)和接收對(duì)象 if (!(item.Issuer == AuthConfig.Issuer ? item.Audience == AuthConfig.Audience ? true : false : false)) { context.Fail(); } // 校驗(yàn)過(guò)期時(shí)間 var nowTime = DateTimeOffset.Now.ToUnixTimeSeconds(); var issued = item.IssuedTime +Convert.ToInt64(item.Expiration.TotalSeconds); if (issued < nowTime) context.Fail(); // 是否有訪問(wèn)此 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); // 無(wú)權(quán)限時(shí)跳轉(zhuǎn)到某個(gè)頁(yè)面 //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
不是必須的,用來(lái)存放角色或策略認(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; }
表示時(shí)間戳
// Unix 時(shí)間戳 DateTimeOffset.Now.ToUnixTimeSeconds(); // 檢驗(yàn) Token 是否過(guò)期 // 將 TimeSpan 轉(zhuǎn)為 Unix 時(shí)間戳 Convert.ToInt64(TimeSpan); DateTimeOffset.Now.ToUnixTimeSeconds() + Convert.ToInt64(TimeSpan);
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解.NET中使用Redis數(shù)據(jù)庫(kù)
Redis是一個(gè)用的比較廣泛的Key/Value的內(nèi)存數(shù)據(jù)庫(kù),這篇文章主要介紹了詳解.NET中使用Redis數(shù)據(jù)庫(kù),有興趣的可以了解一下。2016-12-12asp.net 錯(cuò)誤:0x8007000B 異常的解決方法
這篇文章主要介紹了asp.net 錯(cuò)誤:0x8007000B 異常的解決方法,需要的朋友可以參考下2015-01-01asp.net 修改/刪除站內(nèi)目錄操作后Session丟失問(wèn)題
在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ì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09AspNet Core上實(shí)現(xiàn)web定時(shí)任務(wù)實(shí)例
在本篇文章里小編給大家分享了關(guān)于AspNet Core上實(shí)現(xiàn)web定時(shí)任務(wù)的實(shí)例內(nèi)容,有興趣的朋友們學(xué)習(xí)參考下。2019-02-02ASP.NET Core跨站登錄重定向的實(shí)現(xiàn)新姿勢(shì)
這篇文章主要給大家介紹了關(guān)于ASP.NET Core實(shí)現(xiàn)跨站登錄重定向的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07ASP.Net中利用CSS實(shí)現(xiàn)多界面的兩種方法
這篇文章主要介紹了ASP.Net中利用CSS實(shí)現(xiàn)多界面的兩種方法,包括動(dòng)態(tài)加載css樣式與動(dòng)態(tài)設(shè)置頁(yè)面同類控件的方法來(lái)實(shí)現(xiàn)該功能,是非常具有實(shí)用價(jià)值的技巧,需要的朋友可以參考下2014-12-12ASP.NET 鏈接 Access 數(shù)據(jù)庫(kù)路徑問(wèn)題最終解決方案
ASP.NET 鏈接 Access 數(shù)據(jù)庫(kù)路徑問(wèn)題最終解決方案...2007-04-04