asp.net core 2.0 web api基于JWT自定义策略授权
JWT(json web token)是一種基于json的身份驗證機制,流程如下:
?
?
通過登錄,來獲取Token,再在之后每次請求的Header中追加Authorization為Token的憑據,服務端驗證通過即可能獲取想要訪問的資源。關于JWT的技術,可參考網絡上文章,這里不作詳細說明,
這篇博文,主要說明在asp.net core 2.0中,基于jwt的web api的權限設置,即在asp.net core中怎么用JWT,再次就是不同用戶或角色因為權限問題,即使援用Token,也不能訪問不該訪問的資源。
基本思路是我們自定義一個策略,來驗證用戶,和驗證用戶授權,PermissionRequirement是驗證傳輸授權的參數。在Startup的ConfigureServices注入驗證(Authentication),授權(Authorization),和JWT(JwtBearer)
自定義策略:
已封閉成AuthorizeRolicy.JWT nuget包,并發布到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> /// 用戶或角色或其他憑據實體 /// </summary> public? class? Permission { ???? /// <summary> ???? /// 用戶或角色或其他憑據名稱 ???? /// </summary> ???? public? virtual? string? Name ???? {? get ;? set ; } ???? /// <summary> ???? /// 請求Url ???? /// </summary> ???? public? virtual? string? Url ???? {? get ;? set ; } } |
PermissionRequirement.cs
| /// <summary> /// 必要參數類 /// </summary> public? class? PermissionRequirement : IAuthorizationRequirement { ???? /// <summary> ???? /// 用戶權限集合 ???? /// </summary> ???? public? List<Permission> Permissions {? get ;? private? set ; } ???? /// <summary> ???? /// 無權限action ???? /// </summary> ???? public? string? DeniedAction {? get ;? set ; } ???? /// <summary> ???? /// 認證授權類型 ???? /// </summary> ???? public? string? ClaimType {? internal? get ;? set ; } ???? /// <summary> ???? /// 請求路徑 ???? /// </summary> ???? public? string? LoginPath {? get ;? set ; } =? "/Api/Login" ; ???? /// <summary> ???? /// 發行人 ???? /// </summary> ???? public? string? Issuer {? get ;? set ; } ???? /// <summary> ???? /// 訂閱人 ???? /// </summary> ???? public? string? Audience {? get ;? set ; } ???? /// <summary> ???? /// 過期時間 ???? /// </summary> ???? public? TimeSpan Expiration {? get ;? set ; } = TimeSpan.FromMinutes(5000); ???? /// <summary> ???? /// 簽名驗證 ???? /// </summary> ???? public? SigningCredentials SigningCredentials {? get ;? set ; } ???? /// <summary> ???? /// 構造 ???? /// </summary> ???? /// <param name="deniedAction">無權限action</param> ???? /// <param name="userPermissions">用戶權限集合</param> ???? /// <summary> ???? /// 構造 ???? /// </summary> ???? /// <param name="deniedAction">拒約請求的url</param> ???? /// <param name="permissions">權限集合</param> ???? /// <param name="claimType">聲明類型</param> ???? /// <param name="issuer">發行人</param> ???? /// <param name="audience">訂閱人</param> ???? /// <param name="signingCredentials">簽名驗證實體</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> /// 權限授權Handler /// </summary> public? class? PermissionHandler : AuthorizationHandler<PermissionRequirement> {??? ???? /// <summary> ???? /// 驗證方案提供對象 ???? /// </summary> ???? public? IAuthenticationSchemeProvider Schemes {? get ;? set ; } ???? /// <summary> ???? /// 自定義策略參數 ???? /// </summary> ???? public? PermissionRequirement Requirement ???? {? get ;? set ; } ???? /// <summary> ???? /// 構造 ???? /// </summary> ???? /// <param name="schemes"></param> ???? public? PermissionHandler(IAuthenticationSchemeProvider schemes) ???? { ???????? Schemes = schemes; ???? }?? ???? protected? override? async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) ???? { ???????? 賦值用戶權限?????? ???????? Requirement = requirement; ???????? //從AuthorizationHandlerContext轉成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 ; ???????????? } ???????? } ???????? //判斷請求是否擁有憑據,即有沒有登錄 ???????? 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; ???????????????? //權限中是否存在請求的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; ???????????????????? //驗證權限 ???????????????????? if? (Requirement.Permissions.Where(w => w.Name == name && w.Url.ToLower() == questUrl).Count() <= 0) ???????????????????? { ???????????????????????? //無權限跳轉到拒絕頁面 ???????????????????????? httpContext.Response.Redirect(requirement.DeniedAction); ???????????????????? } ???????????????? } ???????????????? context.Succeed(requirement); ???????????????? return ; ???????????? } ???????? } ???????? //判斷沒有登錄時,是否訪問登錄的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項目,并在項目添加AuthorizePolicy.JWT如圖
先設置配置文件,用戶可以定義密匙和發生人,訂閱人
??"Audience": {
??? "Secret": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
??? "Issuer": "gsw",
??? "Audience": "everone"
? }
在ConfigureServices中注入驗證(Authentication),授權(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 => ???? { ???????? //這個集合模擬用戶權限表,可從數據庫中查詢出來 ???????? 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" } ?????????????????? }; ???????? //如果第三個參數,是ClaimTypes.Role,上面集合的每個元素的Name為角色名稱,如果ClaimTypes.Name,即上面集合的每個元素的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; ???? }); ???? //注入授權Handler ???? services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); ???? services.AddMvc(); } |
在需要授的Controller上添加授權特性
??? [Authorize("Permission")]
?
PermissionController類有兩個方法,一個是登錄,驗證用戶名和密碼是否正確,如果正確就發放Token,如果失敗,驗證失敗,別一個成功登后的無權限導航action。
| [Authorize( "Permission" )] public? class? PermissionController : Controller { ???? /// <summary> ???? /// 自定義策略參數 ???? /// </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 =? "認證失敗" ???????????? }); ???????? } ???????? else ???????? { ???????????? //如果是基于角色的授權策略,這里要添加用戶;如果是基于角色的授權策略,這里要添加角色 ???????????? var? claims = new? Claim[]{? new? Claim(ClaimTypes.Name, username), new? Claim(ClaimTypes.Role, role) }; ???????????? //用戶標識 ???????????? 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 =? "你無權限訪問" ???????? }); ???? } } |
下面定義一個控制臺(.NetFramewrok)程序,用RestSharp來訪問我們定義的web api,其中1為admin角色登錄,2為system角色登錄,3為錯誤用戶密碼登錄,4是一個查詢功能,在startup.cs中,admin角色是具有查詢/api/values的權限的,所以用admin登錄是能正常訪問的,用system登錄,能成功登錄,但沒有權限訪問/api/values,用戶名密碼錯誤,訪問/aip/values,直接是沒有授權的
| 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、登錄【錯誤用戶名密碼】 4、查詢數據 " ); ?????????????? 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($ "間隔時間:{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" ); ?????????? //或用用戶名密碼查詢對應角色 ?????????? 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" ); ?????????? //或用用戶名密碼查詢對應角色 ?????????? 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" ); ?????????? //或用用戶名密碼查詢對應角色 ?????????? 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($ "狀態:{response.StatusCode}? 返回結果:{content}" ); ?????? } ?? } |
運行結果:
?
源碼:https://github.com/axzxs2001/AuthorizePolicy.JWT
原文地址:http://www.cnblogs.com/axzxs2001/p/7530929.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的asp.net core 2.0 web api基于JWT自定义策略授权的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASP.NET Core Web服务器
- 下一篇: 体验 ASP.NET Core 中的多语