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>///<paramname="username"></param>///<returns></returns>publicstaticdynamicBuildJwtToken(Claim[]claims,PermissionRequirementpermissionRequirement){varnow=DateTime.UtcNow;varjwt=newJwtSecurityToken(issuer:permissionRequirement.Issuer,audience:permissionRequirement.Audience,claims:claims,notBefore:now,expires:now.Add(permissionRequirement.Expiration),signingCredentials:permissionRequirement.SigningCredentials);varencodedJwt=newJwtSecurityTokenHandler().WriteToken(jwt);varresponse=new{Status=true,access_token=encodedJwt,expires_in=permissionRequirement.Expiration.TotalMilliseconds,token_type="Bearer"};returnresponse;}

Permission.cs

///<summary>///用户或角色或其他凭据实体///</summary>publicclassPermission{///<summary>///用户或角色或其他凭据名称///</summary>publicvirtualstringName{get;set;}///<summary>///请求Url///</summary>publicvirtualstringUrl{get;set;}}

PermissionRequirement.cs

///<summary>///必要参数类///</summary>publicclassPermissionRequirement:IAuthorizationRequirement{///<summary>///用户权限集合///</summary>publicList<Permission>Permissions{get;privateset;}///<summary>///无权限action///</summary>publicstringDeniedAction{get;set;}///<summary>///认证授权类型///</summary>publicstringClaimType{internalget;set;}///<summary>///请求路径///</summary>publicstringLoginPath{get;set;}="/Api/Login";///<summary>///发行人///</summary>publicstringIssuer{get;set;}///<summary>///订阅人///</summary>publicstringAudience{get;set;}///<summary>///过期时间///</summary>publicTimeSpanExpiration{get;set;}=TimeSpan.FromMinutes(5000);///<summary>///签名验证///</summary>publicSigningCredentialsSigningCredentials{get;set;}///<summary>///构造///</summary>///<paramname="deniedAction">无权限action</param>///<paramname="userPermissions">用户权限集合</param>///<summary>///构造///</summary>///<paramname="deniedAction">拒约请求的url</param>///<paramname="permissions">权限集合</param>///<paramname="claimType">声明类型</param>///<paramname="issuer">发行人</param>///<paramname="audience">订阅人</param>///<paramname="signingCredentials">签名验证实体</param>publicPermissionRequirement(stringdeniedAction,List<Permission>permissions,stringclaimType,stringissuer,stringaudience,SigningCredentialssigningCredentials){ClaimType=claimType;DeniedAction=deniedAction;Permissions=permissions;Issuer=issuer;Audience=audience;SigningCredentials=signingCredentials;}}

 自定义策略类PermissionHandler.cs

///<summary>///权限授权Handler///</summary>publicclassPermissionHandler:AuthorizationHandler<PermissionRequirement>{///<summary>///验证方案提供对象///</summary>publicIAuthenticationSchemeProviderSchemes{get;set;}///<summary>///自定义策略参数///</summary>publicPermissionRequirementRequirement{get;set;}///<summary>///构造///</summary>///<paramname="schemes"></param>publicPermissionHandler(IAuthenticationSchemeProviderschemes){Schemes=schemes;}protectedoverrideasyncTaskHandleRequirementAsync(AuthorizationHandlerContextcontext,PermissionRequirementrequirement){////赋值用户权限Requirement=requirement;//从AuthorizationHandlerContext转成HttpContext,以便取出表求信息varhttpContext=(context.ResourceasMicrosoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext;//请求UrlvarquestUrl=httpContext.Request.Path.Value.ToLower();//判断请求是否停止varhandlers=httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();foreach(varschemeinawaitSchemes.GetRequestHandlerSchemesAsync()){varhandler=awaithandlers.GetHandlerAsync(httpContext,scheme.Name)asIAuthenticationRequestHandler;if(handler!=null&&awaithandler.HandleRequestAsync()){context.Fail();return;}}//判断请求是否拥有凭据,即有没有登录vardefaultAuthenticate=awaitSchemes.GetDefaultAuthenticateSchemeAsync();if(defaultAuthenticate!=null){varresult=awaithttpContext.AuthenticateAsync(defaultAuthenticate.Name);//result?.Principal不为空即登录成功if(result?.Principal!=null){httpContext.User=result.Principal;//权限中是否存在请求的urlif(Requirement.Permissions.GroupBy(g=>g.Url).Where(w=>w.Key.ToLower()==questUrl).Count()>0){varname=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

publicvoidConfigureServices(IServiceCollectionservices){//读取配置文件varaudienceConfig=Configuration.GetSection("Audience");varsymmetricKeyAsBase64=audienceConfig["Secret"];varkeyByteArray=Encoding.ASCII.GetBytes(symmetricKeyAsBase64);varsigningKey=newSymmetricSecurityKey(keyByteArray);vartokenValidationParameters=newTokenValidationParameters{ValidateIssuerSigningKey=true,IssuerSigningKey=signingKey,ValidateIssuer=true,ValidIssuer=audienceConfig["Issuer"],ValidateAudience=true,ValidAudience=audienceConfig["Audience"],ValidateLifetime=true,ClockSkew=TimeSpan.Zero};varsigningCredentials=newSigningCredentials(signingKey,SecurityAlgorithms.HmacSha256);services.AddAuthorization(options=>{//这个集合模拟用户权限表,可从数据库中查询出来varpermission=newList<Permission>{newPermission{Url="/",Name="admin"},newPermission{Url="/api/values",Name="admin"},newPermission{Url="/",Name="system"},newPermission{Url="/api/values1",Name="system"}};//如果第三个参数,是ClaimTypes.Role,上面集合的每个元素的Name为角色名称,如果ClaimTypes.Name,即上面集合的每个元素的Name为用户名varpermissionRequirement=newPermissionRequirement("/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=>{//不使用httpso.RequireHttpsMetadata=false;o.TokenValidationParameters=tokenValidationParameters;});//注入授权Handlerservices.AddSingleton<IAuthorizationHandler,PermissionHandler>();services.AddMvc();}

 在需要授的Controller上添加授权特性

[Authorize("Permission")]

PermissionController类有两个方法,一个是登录,验证用户名和密码是否正确,如果正确就发放Token,如果失败,验证失败,别一个成功登后的无权限导航action。

[Authorize("Permission")]publicclassPermissionController:Controller{///<summary>///自定义策略参数///</summary>PermissionRequirement_requirement;publicPermissionController(IAuthorizationHandlerauthorizationHander){_requirement=(authorizationHanderasPermissionHandler).Requirement;}[AllowAnonymous][HttpPost("/api/login")]publicIActionResultLogin(stringusername,stringpassword,stringrole){varisValidated=username=="gsw"&&password=="111111";if(!isValidated){returnnewJsonResult(new{Status=false,Message="认证失败"});}else{//如果是基于角色的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色varclaims=newClaim[]{newClaim(ClaimTypes.Name,username),newClaim(ClaimTypes.Role,role)};//用户标识varidentity=newClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);identity.AddClaims(claims);//登录HttpContext.SignInAsync(JwtBearerDefaults.AuthenticationScheme,newClaimsPrincipal(identity));vartoken=JwtToken.BuildJwtToken(claims,_requirement);returnnewJsonResult(token);}}[AllowAnonymous][HttpGet("/api/denied")]publicIActionResultDenied(){returnnewJsonResult(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,直接是没有授权的

classProgram{///<summary>///访问Url///</summary>staticstring_url="http://localhost:39286";staticvoidMain(string[]args){dynamictoken=null;while(true){Console.WriteLine("1、登录【admin】2、登录【system】3、登录【错误用户名密码】4、查询数据");varmark=Console.ReadLine();varstopwatch=newStopwatch();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();TimeSpantimespan=stopwatch.Elapsed;Console.WriteLine($"间隔时间:{timespan.TotalSeconds}");}}staticdynamicNullLogin(){varloginClient=newRestClient(_url);varloginRequest=newRestRequest("/api/login",Method.POST);loginRequest.AddParameter("username","gswaa");loginRequest.AddParameter("password","111111");//或用用户名密码查询对应角色loginRequest.AddParameter("role","system");IRestResponseloginResponse=loginClient.Execute(loginRequest);varloginContent=loginResponse.Content;Console.WriteLine(loginContent);returnNewtonsoft.Json.JsonConvert.DeserializeObject(loginContent);}staticdynamicSystemLogin(){varloginClient=newRestClient(_url);varloginRequest=newRestRequest("/api/login",Method.POST);loginRequest.AddParameter("username","gsw");loginRequest.AddParameter("password","111111");//或用用户名密码查询对应角色loginRequest.AddParameter("role","system");IRestResponseloginResponse=loginClient.Execute(loginRequest);varloginContent=loginResponse.Content;Console.WriteLine(loginContent);returnNewtonsoft.Json.JsonConvert.DeserializeObject(loginContent);}staticdynamicAdminLogin(){varloginClient=newRestClient(_url);varloginRequest=newRestRequest("/api/login",Method.POST);loginRequest.AddParameter("username","gsw");loginRequest.AddParameter("password","111111");//或用用户名密码查询对应角色loginRequest.AddParameter("role","admin");IRestResponseloginResponse=loginClient.Execute(loginRequest);varloginContent=loginResponse.Content;Console.WriteLine(loginContent);returnNewtonsoft.Json.JsonConvert.DeserializeObject(loginContent);}staticvoidAdminInvock(dynamictoken){varclient=newRestClient(_url);//这里要在获取的令牌字符串前加Bearerstringtk="Bearer"+Convert.ToString(token?.access_token);client.AddDefaultHeader("Authorization",tk);varrequest=newRestRequest("/api/values",Method.GET);IRestResponseresponse=client.Execute(request);varcontent=response.Content;Console.WriteLine($"状态:{response.StatusCode}返回结果:{content}");}}

 运行结果:

源码:https://github.com/axzxs2001/AuthorizePolicy.JWT