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/
<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
1 using Microsoft.AspNetCore.Authorization; 2 using
Microsoft.IdentityModel.Tokens; 3 using System; 4 using
System.Collections.Generic; 5 6 namespace AuthorizePolicy.JWT 7 { 8 ///
<summary> 9 /// 必要参数类 10 /// </summary> 11 public class PermissionRequirement
: IAuthorizationRequirement12 { 13 /// <summary> 14 /// 用户权限集合 15 ///
</summary> 16 public List<Permission> Permissions { get; private set; } 17 ///
<summary> 18 /// 无权限action 19 /// </summary> 20 public string DeniedAction { get
;set; } 21 22 /// <summary> 23 /// 认证授权类型 24 /// </summary> 25 public string
ClaimType {internal get; set; } 26 /// <summary> 27 /// 请求路径 28 /// </summary>
29 public string LoginPath { get; set; } = "/Api/Login"; 30 /// <summary> 31 ///
发行人32 /// </summary> 33 public string Issuer { get; set; } 34 /// <summary> 35
/// 订阅人 36 /// </summary> 37 public string Audience { get; set; } 38 ///
<summary> 39 /// 过期时间 40 /// </summary> 41 public TimeSpan Expiration { get; set
; }42 /// <summary> 43 /// 签名验证 44 /// </summary> 45 public SigningCredentials
SigningCredentials {get; set; } 46 47 /// <summary> 48 /// 构造 49 /// </summary>
50 /// <param name="deniedAction">无权限action</param> 51 /// <param
name="userPermissions">用户权限集合</param> 52 53 /// <summary> 54 /// 构造 55 ///
</summary> 56 /// <param name="deniedAction">拒约请求的url</param> 57 /// <param
name="permissions">权限集合</param> 58 /// <param name="claimType">声明类型</param> 59
/// <param name="issuer">发行人</param> 60 /// <param name="audience">订阅人</param>
61 /// <param name="signingCredentials">签名验证实体</param> 62 public
PermissionRequirement(string deniedAction, List<Permission> permissions, string
claimType,string issuer, string audience, SigningCredentials
signingCredentials, TimeSpan expiration)63 { 64 ClaimType = claimType; 65
DeniedAction = deniedAction; 66 Permissions = permissions; 67 Issuer = issuer;
68 Audience = audience; 69 Expiration = expiration; 70 SigningCredentials =
signingCredentials;71 } 72 } 73 }
 自定义策略类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); } }
//判断过期时间

if (DateTime.Parse(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration).Value) >= DateTime.Now)
{
     context.Succeed(requirement);
}
else
{      context.Fail(); }
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
1 public void ConfigureServices(IServiceCollection services) 2 { 3 var
urls ="http://localhost:39287/"; 4 services.AddCors(options => 5
options.AddPolicy("MyDomain", 6 builder =>
builder.WithOrigins(urls).AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin().AllowCredentials()));
7 8 //读取配置文件 9 var audienceConfig = Configuration.GetSection("Audience"); 10
var symmetricKeyAsBase64 = audienceConfig["Secret"]; 11 var keyByteArray =
Encoding.ASCII.GetBytes(symmetricKeyAsBase64);12 var signingKey = new
SymmetricSecurityKey(keyByteArray);13 var tokenValidationParameters = new
TokenValidationParameters14 { 15 ValidateIssuerSigningKey = true, 16
IssuerSigningKey = signingKey, 17 ValidateIssuer = true, 18 ValidIssuer =
audienceConfig["Issuer"],//发行人 19 ValidateAudience = true, 20 ValidAudience =
audienceConfig["Audience"],//订阅人 21 ValidateLifetime = true, 22 ClockSkew =
TimeSpan.Zero,23 RequireExpirationTime = true, 24 25 }; 26 var
signingCredentials =new SigningCredentials(signingKey,
SecurityAlgorithms.HmacSha256);27 //这个集合模拟用户权限表,可从数据库中查询出来 28 var permission =
new List<Permission> { 29 new Permission { Url="/", Name="admin"}, 30 new
Permission { Url="/api/values", Name="admin"}, 31 new Permission { Url="/",
Name="system"}, 32 new Permission { Url="/api/values1", Name="system"} 33 }; 34
//
如果第三个参数,是ClaimTypes.Role,上面集合的每个元素的Name为角色名称,如果ClaimTypes.Name,即上面集合的每个元素的Name为用户名
35 var permissionRequirement = new PermissionRequirement( 36 "/api/denied",
permission,37 ClaimTypes.Role, 38 audienceConfig["Issuer"], 39 audienceConfig["
Audience"], 40 signingCredentials, 41 expiration: TimeSpan.FromSeconds(10
)//设置Token过期时间42 ); 43 services.AddAuthorization(options => 44 { 45 46
options.AddPolicy("Permission", 47 policy =>
policy.Requirements.Add(permissionRequirement));48 49
}).AddAuthentication(options =>50 { 51 options.DefaultAuthenticateScheme =
JwtBearerDefaults.AuthenticationScheme;52 options.DefaultChallengeScheme =
JwtBearerDefaults.AuthenticationScheme;53 }) 54 .AddJwtBearer(o => 55 { 56 //
不使用https 57 o.RequireHttpsMetadata = false; 58 o.TokenValidationParameters =
tokenValidationParameters;59 o.Events = new JwtBearerEvents 60 { 61
OnTokenValidated = context =>62 { 63 if (context.Request.Path.Value.ToString()
=="/api/logout") 64 { 65 var token = ((context as
TokenValidatedContext).SecurityTokenas JwtSecurityToken).RawData; 66 } 67
return Task.CompletedTask; 68 } 69 }; 70 }); 71 //注入授权Handler 72
services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); 73
services.AddSingleton(permissionRequirement);74 services.AddMvc(); 75 }
 

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

    [Authorize("Permission")]

 


PermissionController类有两个方法,一个是登录,验证用户名和密码是否正确,如果正确就发放Token,如果失败,验证失败,别一个成功登后的无权限导航action。
1 [Authorize("Permission")] 2 [EnableCors("MyDomain")] 3 public class
PermissionController : Controller 4 { 5 /// <summary> 6 /// 自定义策略参数 7 ///
</summary> 8 PermissionRequirement _requirement; 9 public
PermissionController(PermissionRequirement requirement)10 { 11 _requirement =
requirement;12 } 13 [AllowAnonymous] 14 [HttpPost("/api/login")] 15 public
IActionResult Login(string username, string password, string role) 16 { 17 var
isValidated = username =="gsw" && password == "111111"; 18 if (!isValidated) 19
{20 return new JsonResult(new 21 { 22 Status = false, 23 Message = "认证失败" 24
});25 } 26 else 27 { 28 //如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色 29 var
claims =new Claim[] { new Claim(ClaimTypes.Name, username), new
Claim(ClaimTypes.Role, role),new Claim(ClaimTypes.Expiration
,DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString())};30
//用户标识 31 var identity = new
ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);32
identity.AddClaims(claims);33 34 var token = JwtToken.BuildJwtToken(claims,
_requirement);35 return new JsonResult(token); 36 } 37 } 38 39 [HttpPost("
/api/logout")] 40 public IActionResult Logout() 41 { 42 return Ok(); 43 } 44
45 [AllowAnonymous] 46 [HttpGet("/api/denied")] 47 public IActionResult
Denied()48 { 49 return new JsonResult(new 50 { 51 Status = false, 52 Message =
"你无权限访问" 53 }); 54 } 55 }
 

下面定义一个控制台(.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

 

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信