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 }
View Code

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 }
View Code

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 }
View Code

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 }
View Code

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统一权限验证的主要内容,如果未能解决你的问题,请参考以下文章

ocelot jwt 进行统一验证

教程4 - 验证和权限

net core体系-API-1Ocelot-项目实战

使用 OKTA 进行 Ocelot 网关身份验证

ocelot 自定义认证和授权

Ocelot API网关的实现剖析