一篇,我們創建了OcelotGateway網關項目,DemoAAPI項目,DemoBAPI項目,為了驗證用戶並分發Token,現在還需要添加AuthenticationAPI項目,也是asp.net core web api項目,整體思路是,當用戶首次請求(Request)時web服務,網關會判斷本... ...
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">IServiceCollection</param> 119 /// <param name="issuer">發行人</param> 120 /// <param name="audience">訂閱人</param> 121 /// <param name="secret">密鑰</param> 122 /// <param name="deniedUrl">拒絕路由</param> 123 /// <returns></returns> 124 public static IServiceCollection AddJTokenBuild(this IServiceCollection services, string issuer, string audience, string secret, string deniedUrl) 125 { 126 var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secret)), SecurityAlgorithms.HmacSha256); 127 //如果第三個參數,是ClaimTypes.Role,上面集合的每個元素的Name為角色名稱,如果ClaimTypes.Name,即上面集合的每個元素的Name為用戶名 128 var permissionRequirement = new PermissionRequirement( 129 deniedUrl, 130 ClaimTypes.Role, 131 issuer, 132 audience, 133 signingCredentials, 134 expiration: TimeSpan.FromHours(10) 135 ); 136 return services.AddSingleton(permissionRequirement); 137 138 } 139 140 } 141 }View Code
接下來看AuthenticationAPI項目:
appsettings.json
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Information"
}
},
"Console": {
"LogLevel": {
"Default": "Information"
}
}
},
"Audience": {
"Secret": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
"Issuer": "gsw",
"Audience": "everone"
}
}
Startup.cs
1 using Microsoft.AspNetCore.Builder; 2 using Microsoft.AspNetCore.Hosting; 3 using Microsoft.Extensions.Configuration; 4 using Microsoft.Extensions.DependencyInjection; 5 using Ocelot.JWTAuthorizePolicy; 6 7 namespace AuthenticationAPI 8 { 9 public class Startup 10 { 11 public Startup(IConfiguration configuration) 12 { 13 Configuration = configuration; 14 } 15 public IConfiguration Configuration { get; } 16 public void ConfigureServices(IServiceCollection services) 17 { 18 var audienceConfig = Configuration.GetSection("Audience"); 19 //註入OcelotJwtBearer 20 services.AddJTokenBuild(audienceConfig["Issuer"], audienceConfig["Issuer"], audienceConfig["Secret"], "/api/denied"); 21 services.AddMvc(); 22 } 23 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 24 { 25 if (env.IsDevelopment()) 26 { 27 app.UseDeveloperExceptionPage(); 28 } 29 app.UseMvc(); 30 } 31 } 32 }View Code
PermissionController.cs
1 using System; 2 using Microsoft.AspNetCore.Mvc; 3 using Microsoft.AspNetCore.Authorization; 4 using System.Security.Claims; 5 using Microsoft.AspNetCore.Authentication.JwtBearer; 6 using