您好,登錄后才能下訂單哦!
JWT(json web token)是一種基于json的身份驗(yàn)證機(jī)制,流程如下:
通過登錄,來獲取Token,再在之后每次請求的Header中追加Authorization為Token的憑據(jù),服務(wù)端驗(yàn)證通過即可能獲取想要訪問的資源。關(guān)于JWT的技術(shù),可參考網(wǎng)絡(luò)上文章,這里不作詳細(xì)說明,
這篇博文,主要說明在asp.net core 2.0中,基于jwt的web api的權(quán)限設(shè)置,即在asp.net core中怎么用JWT,再次就是不同用戶或角色因?yàn)闄?quán)限問題,即使援用Token,也不能訪問不該訪問的資源。
基本思路是我們自定義一個(gè)策略,來驗(yàn)證用戶,和驗(yàn)證用戶授權(quán),PermissionRequirement是驗(yàn)證傳輸授權(quán)的參數(shù)。在Startup的ConfigureServices注入驗(yàn)證(Authentication),授權(quán)(Authorization),和JWT(JwtBearer)
自定義策略:
已封閉成AuthorizeRolicy.JWT nuget包,并發(fā)布到nuget上:
https://www.nuget.org/packages/AuthorizePolicy.JWT/
源碼如下:
JwtToken.cs
/// <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; }
Permission.cs
/// <summary> /// 用戶或角色或其他憑據(jù)實(shí)體 /// </summary> public class Permission { /// <summary> /// 用戶或角色或其他憑據(jù)名稱 /// </summary> public virtual string Name { get; set; } /// <summary> /// 請求Url /// </summary> public virtual string Url { get; set; } }
PermissionRequirement.cs
/// <summary> /// 必要參數(shù)類 /// </summary> public class PermissionRequirement : IAuthorizationRequirement { /// <summary> /// 用戶權(quán)限集合 /// </summary> public List<Permission> Permissions { get; private set; } /// <summary> /// 無權(quán)限action /// </summary> public string DeniedAction { get; set; } /// <summary> /// 認(rèn)證授權(quán)類型 /// </summary> public string ClaimType { internal get; set; } /// <summary> /// 請求路徑 /// </summary> public string LoginPath { get; set; } = "/Api/Login"; /// <summary> /// 發(fā)行人 /// </summary> public string Issuer { get; set; } /// <summary> /// 訂閱人 /// </summary> public string Audience { get; set; } /// <summary> /// 過期時(shí)間 /// </summary> public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes(5000); /// <summary> /// 簽名驗(yàn)證 /// </summary> public SigningCredentials SigningCredentials { get; set; } /// <summary> /// 構(gòu)造 /// </summary> /// <param name="deniedAction">無權(quán)限action</param> /// <param name="userPermissions">用戶權(quán)限集合</param> /// <summary> /// 構(gòu)造 /// </summary> /// <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="signingCredentials">簽名驗(yàn)證實(shí)體</param> public PermissionRequirement(string deniedAction, List<Permission> permissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials) { ClaimType = claimType; DeniedAction = deniedAction; Permissions = permissions; Issuer = issuer; Audience = audience; SigningCredentials = signingCredentials; } }
自定義策略類PermissionHandler.cs
/// <summary> /// 權(quán)限授權(quán)Handler /// </summary> public class PermissionHandler : AuthorizationHandler<PermissionRequirement> { /// <summary> /// 驗(yàn)證方案提供對象 /// </summary> public IAuthenticationSchemeProvider Schemes { get; set; } /// <summary> /// 自定義策略參數(shù) /// </summary> public PermissionRequirement Requirement { get; set; } /// <summary> /// 構(gòu)造 /// </summary> /// <param name="schemes"></param> public PermissionHandler(IAuthenticationSchemeProvider schemes) { Schemes = schemes; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { ////賦值用戶權(quán)限 Requirement = requirement; //從AuthorizationHandlerContext轉(zhuǎn)成HttpContext,以便取出表求信息 var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext; //請求Url var questUrl = httpContext.Request.Path.Value.ToLower(); //判斷請求是否停止 var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>(); foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync()) { var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler; if (handler != null && await handler.HandleRequestAsync()) { context.Fail(); return; } } //判斷請求是否擁有憑據(jù),即有沒有登錄 var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null) { var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); //result?.Principal不為空即登錄成功 if (result?.Principal != null) { httpContext.User = result.Principal; //權(quán)限中是否存在請求的url if (Requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key.ToLower() == questUrl).Count() > 0) { var name = httpContext.User.Claims.SingleOrDefault(s => s.Type == requirement.ClaimType).Value; //驗(yàn)證權(quán)限 if (Requirement.Permissions.Where(w => w.Name == name && w.Url.ToLower() == questUrl).Count() <= 0) { //無權(quán)限跳轉(zhuǎn)到拒絕頁面 httpContext.Response.Redirect(requirement.DeniedAction); } } context.Succeed(requirement); return; } } //判斷沒有登錄時(shí),是否訪問登錄的url,并且是Post請求,并助是form表單提交類型,否則為失敗 if (!questUrl.Equals(Requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType)) { context.Fail(); return; } context.Succeed(requirement); } }
新建asp.net core 2.0的web api項(xiàng)目,并在項(xiàng)目添加AuthorizePolicy.JWT如圖
先設(shè)置配置文件,用戶可以定義密匙和發(fā)生人,訂閱人
"Audience": {
"Secret": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
"Issuer": "gsw",
"Audience": "everone"
}
在ConfigureServices中注入驗(yàn)證(Authentication),授權(quán)(Authorization),和JWT(JwtBearer)
Startup.cs
public void ConfigureServices(IServiceCollection services) { //讀取配置文件 var audienceConfig = Configuration.GetSection("Audience"); var symmetricKeyAsBase64 = audienceConfig["Secret"]; var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); var signingKey = new SymmetricSecurityKey(keyByteArray); var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, ValidateIssuer = true, ValidIssuer = audienceConfig["Issuer"], ValidateAudience = true, ValidAudience = audienceConfig["Audience"], ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); services.AddAuthorization(options => { //這個(gè)集合模擬用戶權(quán)限表,可從數(shù)據(jù)庫中查詢出來 var permission = new List<Permission> { new Permission { Url="/", Name="admin"}, new Permission { Url="/api/values", Name="admin"}, new Permission { Url="/", Name="system"}, new Permission { Url="/api/values1", Name="system"} }; //如果第三個(gè)參數(shù),是ClaimTypes.Role,上面集合的每個(gè)元素的Name為角色名稱,如果ClaimTypes.Name,即上面集合的每個(gè)元素的Name為用戶名 var permissionRequirement = new PermissionRequirement("/api/denied", permission, ClaimTypes.Role, audienceConfig["Issuer"], audienceConfig["Audience"], signingCredentials); options.AddPolicy("Permission", policy => policy.Requirements.Add(permissionRequirement)); }).AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(o => { //不使用https o.RequireHttpsMetadata = false; o.TokenValidationParameters = tokenValidationParameters; }); //注入授權(quán)Handler services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); services.AddMvc(); }
在需要授的Controller上添加授權(quán)特性
[Authorize("Permission")]
PermissionController類有兩個(gè)方法,一個(gè)是登錄,驗(yàn)證用戶名和密碼是否正確,如果正確就發(fā)放Token,如果失敗,驗(yàn)證失敗,別一個(gè)成功登后的無權(quán)限導(dǎo)航action。
[Authorize("Permission")] public class PermissionController : Controller { /// <summary> /// 自定義策略參數(shù) /// </summary> PermissionRequirement _requirement; public PermissionController(IAuthorizationHandler authorizationHander) { _requirement = (authorizationHander as PermissionHandler).Requirement; } [AllowAnonymous] [HttpPost("/api/login")] public IActionResult Login(string username,string password,string role) { var isValidated = username == "gsw" && password == "111111"; if (!isValidated) { return new JsonResult(new { Status = false, Message = "認(rèn)證失敗" }); } else { //如果是基于角色的授權(quán)策略,這里要添加用戶;如果是基于角色的授權(quán)策略,這里要添加角色 var claims =new Claim[]{ new Claim(ClaimTypes.Name, username),new Claim(ClaimTypes.Role, role) }; //用戶標(biāo)識 var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme); identity.AddClaims(claims); //登錄 HttpContext.SignInAsync(JwtBearerDefaults.AuthenticationScheme, new ClaimsPrincipal(identity)); var token = JwtToken.BuildJwtToken(claims, _requirement); return new JsonResult(token); } } [AllowAnonymous] [HttpGet("/api/denied")] public IActionResult Denied() { return new JsonResult(new { Status = false, Message = "你無權(quán)限訪問" }); } }
下面定義一個(gè)控制臺(tái)(.NetFramewrok)程序,用RestSharp來訪問我們定義的web api,其中1為admin角色登錄,2為system角色登錄,3為錯(cuò)誤用戶密碼登錄,4是一個(gè)查詢功能,在startup.cs中,admin角色是具有查詢/api/values的權(quán)限的,所以用admin登錄是能正常訪問的,用system登錄,能成功登錄,但沒有權(quán)限訪問/api/values,用戶名密碼錯(cuò)誤,訪問/aip/values,直接是沒有授權(quán)的
class Program { /// <summary> /// 訪問Url /// </summary> static string _url = "http://localhost:39286"; static void Main(string[] args) { dynamic token = null; while (true) { Console.WriteLine("1、登錄【admin】 2、登錄【system】 3、登錄【錯(cuò)誤用戶名密碼】 4、查詢數(shù)據(jù) "); var mark = Console.ReadLine(); var stopwatch = new Stopwatch(); stopwatch.Start(); switch (mark) { case "1": token = AdminLogin(); break; case "2": token = SystemLogin(); break; case "3": token = NullLogin(); break; case "4": AdminInvock(token); break; } stopwatch.Stop(); TimeSpan timespan = stopwatch.Elapsed; Console.WriteLine($"間隔時(shí)間:{timespan.TotalSeconds}"); } } static dynamic NullLogin() { var loginClient = new RestClient(_url); var loginRequest = new RestRequest("/api/login", Method.POST); loginRequest.AddParameter("username", "gswaa"); loginRequest.AddParameter("password", "111111"); //或用用戶名密碼查詢對應(yīng)角色 loginRequest.AddParameter("role", "system"); IRestResponse loginResponse = loginClient.Execute(loginRequest); var loginContent = loginResponse.Content; Console.WriteLine(loginContent); return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent); } static dynamic SystemLogin() { var loginClient = new RestClient(_url); var loginRequest = new RestRequest("/api/login", Method.POST); loginRequest.AddParameter("username", "gsw"); loginRequest.AddParameter("password", "111111"); //或用用戶名密碼查詢對應(yīng)角色 loginRequest.AddParameter("role", "system"); IRestResponse loginResponse = loginClient.Execute(loginRequest); var loginContent = loginResponse.Content; Console.WriteLine(loginContent); return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent); } static dynamic AdminLogin() { var loginClient = new RestClient(_url); var loginRequest = new RestRequest("/api/login", Method.POST); loginRequest.AddParameter("username", "gsw"); loginRequest.AddParameter("password", "111111"); //或用用戶名密碼查詢對應(yīng)角色 loginRequest.AddParameter("role", "admin"); IRestResponse loginResponse = loginClient.Execute(loginRequest); var loginContent = loginResponse.Content; Console.WriteLine(loginContent); return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent); } static void AdminInvock(dynamic token) { var client = new RestClient(_url); //這里要在獲取的令牌字符串前加Bearer string tk = "Bearer " + Convert.ToString(token?.access_token); client.AddDefaultHeader("Authorization", tk); var request = new RestRequest("/api/values", Method.GET); IRestResponse response = client.Execute(request); var content = response.Content; Console.WriteLine($"狀態(tài):{response.StatusCode} 返回結(jié)果:{content}"); } }
運(yùn)行結(jié)果:
源碼:https://github.com/axzxs2001/AuthorizePolicy.JWT
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。