Ocelot统一权限验证
Posted 老桂博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Ocelot统一权限验证相关的知识,希望对你有一定的参考价值。
Ocelot作为网关,可以用来作统一验证,接上一篇博客,我们继续
前一篇,我们创建了OcelotGateway网关项目,DemoAAPI项目,DemoBAPI项目,为了验证用户并分发Token,现在还需要添加AuthenticationAPI项目,也是asp.net core web api项目,整体思路是,当用户首次请求(Request)时web服务,网关会判断本请求有无Token,并是否正确,如果没有或不正确,就会反回401 Unauthorized;如果请求调用登录,正确输入用户名或密码,AuthenticationAPI会验证并分发Token;当客户端带上Token再次访问web服务时,网关就会放过本请求,当请求到达web服务时,web服务要对本Token进行授权验证,如果有访问请求的地址,会成功返回应答,负责会提示没有权验,所以只要具有正确的Token,应答返回都是200 OK,因为Token正确,只是没有权限访问请求的内容。
下面创建最重要的一个项目Ocelot.JWTAuthorizePolicy,选.NET Standard的类库作为项目模板创建本项目,本项目的作用是为网关项目(OcelotGateway),web服务项目(DemoAAPI和DemoBAPI),和AuthenticationAPI提供注入JWT或自定义策略的API,关于自定义策略,可参考(http://www.cnblogs.com/axzxs2001/p/7530929.html)
本项目中的组成部分:
Permission.cs
1 namespace Ocelot.JWTAuthorizePolicy 2 { 3 /// <summary> 4 /// 用户或角色或其他凭据实体 5 /// </summary> 6 public class Permission 7 { 8 /// <summary> 9 /// 用户或角色或其他凭据名称 10 /// </summary> 11 public virtual string Name 12 { get; set; } 13 /// <summary> 14 /// 请求Url 15 /// </summary> 16 public virtual string Url 17 { get; set; } 18 } 19 }
PermissionRequirement.cs
1 using Microsoft.AspNetCore.Authorization; 2 using Microsoft.IdentityModel.Tokens; 3 using System; 4 using System.Collections.Generic; 5 6 namespace Ocelot.JWTAuthorizePolicy 7 { 8 /// <summary> 9 /// 必要参数类 10 /// </summary> 11 public class PermissionRequirement : IAuthorizationRequirement 12 { 13 /// <summary> 14 /// 无权限action 15 /// </summary> 16 public string DeniedAction { get; set; } 17 18 /// <summary> 19 /// 认证授权类型 20 /// </summary> 21 public string ClaimType { internal get; set; } 22 /// <summary> 23 /// 请求路径 24 /// </summary> 25 public string LoginPath { get; set; } = "/Api/Login"; 26 /// <summary> 27 /// 发行人 28 /// </summary> 29 public string Issuer { get; set; } 30 /// <summary> 31 /// 订阅人 32 /// </summary> 33 public string Audience { get; set; } 34 /// <summary> 35 /// 过期时间 36 /// </summary> 37 public TimeSpan Expiration { get; set; } 38 /// <summary> 39 /// 签名验证 40 /// </summary> 41 public SigningCredentials SigningCredentials { get; set; } 42 43 /// <summary> 44 /// 构造 45 /// </summary> 46 /// <param name="deniedAction">无权限action</param> 47 /// <param name="userPermissions">用户权限集合</param> 48 49 /// <summary> 50 /// 构造 51 /// </summary> 52 /// <param name="deniedAction">拒约请求的url</param> 53 /// <param name="claimType">声明类型</param> 54 /// <param name="issuer">发行人</param> 55 /// <param name="audience">订阅人</param> 56 /// <param name="signingCredentials">签名验证实体</param> 57 public PermissionRequirement(string deniedAction, string claimType, string issuer, string audience, SigningCredentials signingCredentials, TimeSpan expiration) 58 { 59 ClaimType = claimType; 60 DeniedAction = deniedAction; 61 Issuer = issuer; 62 Audience = audience; 63 Expiration = expiration; 64 SigningCredentials = signingCredentials; 65 } 66 } 67 }
PermissionHandler.cs
1 using Microsoft.AspNetCore.Authentication; 2 using Microsoft.AspNetCore.Authentication.JwtBearer; 3 using Microsoft.AspNetCore.Authorization; 4 using Microsoft.Extensions.DependencyInjection; 5 using System; 6 using System.Collections.Generic; 7 using System.Linq; 8 using System.Security.Claims; 9 using System.Threading.Tasks; 10 11 namespace Ocelot.JWTAuthorizePolicy 12 { 13 /// <summary> 14 /// 权限授权Handler 15 /// </summary> 16 public class PermissionHandler : AuthorizationHandler<PermissionRequirement> 17 { 18 /// <summary> 19 /// 验证方案提供对象 20 /// </summary> 21 public IAuthenticationSchemeProvider Schemes { get; set; } 22 /// <summary> 23 /// 用户权限集合 24 /// </summary> 25 List<Permission> _permissions; 26 /// <summary> 27 /// 构造 28 /// </summary> 29 /// <param name="schemes"></param> 30 public PermissionHandler(IAuthenticationSchemeProvider schemes, List<Permission> permissions=null) 31 { 32 Schemes = schemes; 33 _permissions = permissions; 34 } 35 36 protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) 37 { 38 //从AuthorizationHandlerContext转成HttpContext,以便取出表求信息 39 var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext; 40 //请求Url 41 var questUrl = httpContext.Request.Path.Value.ToLower(); 42 //判断请求是否停止 43 var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>(); 44 foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync()) 45 { 46 var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler; 47 if (handler != null && await handler.HandleRequestAsync()) 48 { 49 context.Fail(); 50 return; 51 } 52 } 53 //判断请求是否拥有凭据,即有没有登录 54 var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); 55 if (defaultAuthenticate != null) 56 { 57 var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); 58 //result?.Principal不为空即登录成功 59 if (result?.Principal != null) 60 { 61 httpContext.User = result.Principal; 62 //权限中是否存在请求的url 63 if (_permissions!=null&&_permissions.GroupBy(g => g.Url).Where(w => w.Key.ToLower() == questUrl).Count() > 0) 64 { 65 var name = httpContext.User.Claims.SingleOrDefault(s => s.Type == requirement.ClaimType).Value; 66 //验证权限 67 if (_permissions.Where(w => w.Name == name && w.Url.ToLower() == questUrl).Count() == 0) 68 { 69 //无权限跳转到拒绝页面 70 httpContext.Response.Redirect(requirement.DeniedAction); 71 context.Succeed(requirement); 72 return; 73 } 74 } 75 //判断过期时间 76 if (DateTime.Parse(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration).Value) >= DateTime.Now) 77 { 78 context.Succeed(requirement); 79 } 80 else 81 { 82 context.Fail(); 83 } 84 return; 85 } 86 } 87 //判断没有登录时,是否访问登录的url,并且是Post请求,并且是form表单提交类型,否则为失败 88 if (!questUrl.Equals(requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") 89 || !httpContext.Request.HasFormContentType)) 90 { 91 context.Fail(); 92 return; 93 } 94 context.Succeed(requirement); 95 } 96 } 97 }
JwtToken.cs
1 using System; 2 using System.IdentityModel.Tokens.Jwt; 3 using System.Security.Claims; 4 5 namespace Ocelot.JWTAuthorizePolicy 6 { 7 /// <summary> 8 /// JWTToken生成类 9 /// </summary> 10 public class JwtToken 11 { 12 /// <summary> 13 /// 获取基于JWT的Token 14 /// </summary> 15 /// <param name="username"></param> 16 /// <returns></returns> 17 public static dynamic BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement) 18 { 19 var now = DateTime.UtcNow; 20 var jwt = new JwtSecurityToken( 21 issuer: permissionRequirement.Issuer, 22 audience: permissionRequirement.Audience, 23 claims: claims, 24 notBefore: now, 25 expires: now.Add(permissionRequirement.Expiration), 26 signingCredentials: permissionRequirement.SigningCredentials 27 ); 28 var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); 29 var responseJson = new 30 { 31 Status = true, 32 access_token = encodedJwt, 33 expires_in = permissionRequirement.Expiration.TotalMilliseconds, 34 token_type = "Bearer" 35 }; 36 return responseJson; 37 } 38 } 39 }
OcelotJwtBearerExtension.cs,本类型中的方法分别用于网关,web服务,和验证服务,请参看注释
1 using Microsoft.AspNetCore.Authentication; 2 using Microsoft.AspNetCore.Authorization; 3 using Microsoft.Extensions.DependencyInjection; 4 using Microsoft.IdentityModel.Tokens; 5 using System; 6 using System.Collections.Generic; 7 using System.Security.Claims; 8 using System.Text; 9 10 namespace Ocelot.JWTAuthorizePolicy 11 { 12 /// <summary> 13 /// Ocelot下JwtBearer扩展 14 /// </summary> 15 public static class OcelotJwtBearerExtension 16 { 17 /// <summary> 18 /// 注入Ocelot下JwtBearer,在ocelot网关的Startup的ConfigureServices中调用 19 /// </summary> 20 /// <param name="services">IServiceCollection</param> 21 /// <param name="issuer">发行人</param> 22 /// <param name="audience">订阅人</param> 23 /// <param name="secret">密钥</param> 24 /// <param name="defaultScheme">默认架构</param> 25 /// <param name="isHttps">是否https</param> 26 /// <returns></returns> 27 public static AuthenticationBuilder AddOcelotJwtBearer(this IServiceCollection services, string issuer, string audience, string secret, string defaultScheme, bool isHttps = false) 28 { 29 var keyByteArray = Encoding.ASCII.GetBytes(secret); 30 var signingKey = new SymmetricSecurityKey(keyByteArray); 31 var tokenValidationParameters = new TokenValidationParameters 32 { 33 ValidateIssuerSigningKey = true, 34 IssuerSigningKey = signingKey, 35 ValidateIssuer = true, 36 ValidIssuer = issuer,//发行人 37 ValidateAudience = true, 38 ValidAudience = audience,//订阅人 39 ValidateLifetime = true, 40 ClockSkew = TimeSpan.Zero, 41 RequireExpirationTime = true, 42 }; 43 return services.AddAuthentication(options => 44 { 45 options.DefaultScheme = defaultScheme; 46 }) 47 .AddJwtBearer(defaultScheme, opt => 48 { 49 //不使用https 50 opt.RequireHttpsMetadata = isHttps; 51 opt.TokenValidationParameters = tokenValidationParameters; 52 }); 53 } 54 55 /// <summary> 56 /// 注入Ocelot jwt策略,在业务API应用中的Startup的ConfigureServices调用 57 /// </summary> 58 /// <param name="services">IServiceCollection</param> 59 /// <param name="issuer">发行人</param> 60 /// <param name="audience">订阅人</param> 61 /// <param name="secret">密钥</param> 62 /// <param name="defaultScheme">默认架构</param> 63 /// <param name="policyName">自定义策略名称</param> 64 /// <param name="deniedUrl">拒绝路由</param> 65 /// <param name="isHttps">是否https</param> 66 /// <returns></returns> 67 public static AuthenticationBuilder AddOcelotPolicyJwtBearer(this IServiceCollection services, string issuer, string audience, string secret, string defaultScheme, string policyName, string deniedUrl, bool isHttps = false) 68 { 69 70 var keyByteArray = Encoding.ASCII.GetBytes(secret); 71 var signingKey = new SymmetricSecurityKey(keyByteArray); 72 var tokenValidationParameters = new TokenValidationParameters 73 { 74 ValidateIssuerSigningKey = true, 75 IssuerSigningKey = signingKey, 76 ValidateIssuer = true, 77 ValidIssuer = issuer,//发行人 78 ValidateAudience = true, 79 ValidAudience = audience,//订阅人 80 ValidateLifetime = true, 81 ClockSkew = TimeSpan.Zero, 82 RequireExpirationTime = true, 83 84 }; 85 var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); 86 //如果第三个参数,是ClaimTypes.Role,上面集合的每个元素的Name为角色名称,如果ClaimTypes.Name,即上面集合的每个元素的Name为用户名 87 var permissionRequirement = new PermissionRequirement( 88 deniedUrl, 89 ClaimTypes.Role, 90 issuer, 91 audience, 92 signingCredentials, 93 expiration: TimeSpan.FromHours(10) 94 ); 95 //注入授权Handler 96 services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); 97 services.AddSingleton(permissionRequirement); 98 return services.AddAuthorization(options => 99 { 100 options.AddPolicy(policyName, 101 policy => policy.Requirements.Add(permissionRequirement)); 102 103 }) 104 .AddAuthentication(options => 105 { 106 options.DefaultScheme = defaultScheme; 107 }) 108 .AddJwtBearer(defaultScheme, o => 109 { 110 //不使用https 111 o.RequireHttpsMetadata = isHttps; 112 o.TokenValidationParameters = tokenValidationParameters; 113 }); 114 } 115 /// <summary> 116 /// 注入Token生成器参数,在token生成项目的Startup的ConfigureServices中使用 117 /// </summary> 118 /// <param name="services">IServic以上是关于Ocelot统一权限验证的主要内容,如果未能解决你的问题,请参考以下文章