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/

源码如下:

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;//请求Urlvar 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;//权限中是否存在请求的urlif (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;});//注入授权Handlerservices.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);//这里要在获取的令牌字符串前加Bearerstring 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

分享给朋友:
您可能感兴趣的文章:
随机阅读: