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
namespace Ocelot.JWTAuthorizePolicy
{
///
/// 用户或角色或其他凭据实体
///
public class Permission
{
///
/// 用户或角色或其他凭据名称
///
public virtual string Name
{ get; set; }
///
/// 请求Url
///
public virtual string Url
{ get; set; }
}
}
PermissionRequirement.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
namespace Ocelot.JWTAuthorizePolicy
{
///
/// 必要参数类
///
public class PermissionRequirement : IAuthorizationRequirement
{
///
/// 无权限action
///
public string DeniedAction { get; set; }
///
/// 认证授权类型
///
public string ClaimType { internal get; set; }
///
/// 请求路径
///
public string LoginPath { get; set; } = "/Api/Login";
///
/// 发行人
///
public string Issuer { get; set; }
///
/// 订阅人
///
public string Audience { get; set; }
///
/// 过期时间
///
public TimeSpan Expiration { get; set; }
///
/// 签名验证
///
public SigningCredentials SigningCredentials { get; set; }
///
/// 构造
///
/// 无权限action
/// 用户权限集合
///
/// 构造
///
/// 拒约请求的url
/// 声明类型
/// 发行人
/// 订阅人
/// 签名验证实体
public PermissionRequirement(string deniedAction, string claimType, string issuer, string audience, SigningCredentials signingCredentials, TimeSpan expiration)
{
ClaimType = claimType;
DeniedAction = deniedAction;
Issuer = issuer;
Audience = audience;
Expiration = expiration;
SigningCredentials = signingCredentials;
}
}
}
PermissionHandler.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Ocelot.JWTAuthorizePolicy
{
///
/// 权限授权Handler
///
public class PermissionHandler : AuthorizationHandler
{
///
/// 验证方案提供对象
///
public IAuthenticationSchemeProvider Schemes { get; set; }
///
/// 用户权限集合
///
List
///
/// 构造
///
///
public PermissionHandler(IAuthenticationSchemeProvider schemes, List
{
Schemes = schemes;
_permissions = permissions;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement 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
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 (_permissions!=null&&_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 (_permissions.Where(w => w.Name == name && w.Url.ToLower() == questUrl).Count() == 0)
{
//无权限跳转到拒绝页面
httpContext.Response.Redirect(requirement.DeniedAction);
context.Succeed(requirement);
return;
}
}
//判断过期时间
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);
}
}
}
JwtToken.cs
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
namespace Ocelot.JWTAuthorizePolicy
{
///
/// JWTToken生成类
///
public class JwtToken
{
///
/// 获取基于JWT的Token
///
///
///
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 responseJson = new
{
Status = true,
access_token = encodedJwt,
expires_in = permissionRequirement.Expiration.TotalMilliseconds,
token_type = "Bearer"
};
return responseJson;
}
}
}
OcelotJwtBearerExtension.cs,本类型中的方法分别用于网关,web服务,和验证服务,请参看注释
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text;
namespace Ocelot.JWTAuthorizePolicy
{
///
/// Ocelot下JwtBearer扩展
///
public static class OcelotJwtBearerExtension
{
///
/// 注入Ocelot下JwtBearer,在ocelot网关的Startup的ConfigureServices中调用
///
/// IServiceCollection
/// 发行人
/// 订阅人
/// 密钥
/// 默认架构
/// 是否https
///
public static AuthenticationBuilder AddOcelotJwtBearer(this IServiceCollection services, string issuer, string audience, string secret, string defaultScheme, bool isHttps = false)
{
var keyByteArray = Encoding.ASCII.GetBytes(secret);
var signingKey = new SymmetricSecurityKey(keyByteArray);
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = issuer,//发行人
ValidateAudience = true,
ValidAudience = audience,//订阅人
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
RequireExpirationTime = true,
};
return services.AddAuthentication(options =>
{
options.DefaultScheme = defaultScheme;
})
.AddJwtBearer(defaultScheme, opt =>
{
//不使用https
opt.RequireHttpsMetadata = isHttps;
opt.TokenValidationParameters = tokenValidationParameters;
});
}
///
/// 注入Ocelot jwt策略,在业务API应用中的Startup的ConfigureServices调用
///
/// IServiceCollection
/// 发行人
/// 订阅人
/// 密钥
/// 默认架构
/// 自定义策略名称
/// 拒绝路由
/// 是否https
///
public static AuthenticationBuilder AddOcelotPolicyJwtBearer(this IServiceCollection services, string issuer, string audience, string secret, string defaultScheme, string policyName, string deniedUrl, bool isHttps = false)
{
var keyByteArray = Encoding.ASCII.GetBytes(secret);
var signingKey = new SymmetricSecurityKey(keyByteArray);
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = issuer,//发行人
ValidateAudience = true,
ValidAudience = audience,//订阅人
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
RequireExpirationTime = true,
};
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
//如果第三个参数,是ClaimTypes.Role,上面集合的每个元素的Name为角色名称,如果ClaimTypes.Name,即上面集合的每个元素的Name为用户名
var permissionRequirement = new PermissionRequirement(
deniedUrl,
ClaimTypes.Role,
issuer,
audience,
signingCredentials,
expiration: TimeSpan.FromHours(10)
);
//注入授权Handler
services.AddSingleton
services.AddSingleton(permissionRequirement);
return services.AddAuthorization(options =>
{
options.AddPolicy(policyName,
policy => policy.Requirements.Add(permissionRequirement));
})
.AddAuthentication(options =>
{
options.DefaultScheme = defaultScheme;
})
.AddJwtBearer(defaultScheme, o =>
{
//不使用https
o.RequireHttpsMetadata = isHttps;
o.TokenValidationParameters = tokenValidationParameters;
});
}
///
/// 注入Token生成器参数,在token生成项目的Startup的ConfigureServices中使用
///
/// IServiceCollection
/// 发行人
/// 订阅人
/// 密钥
/// 拒绝路由
///
public static IServiceCollection AddJTokenBuild(this IServiceCollection services, string issuer, string audience, string secret, string deniedUrl)
{
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secret)), SecurityAlgorithms.HmacSha256);
//如果第三个参数,是ClaimTypes.Role,上面集合的每个元素的Name为角色名称,如果ClaimTypes.Name,即上面集合的每个元素的Name为用户名
var permissionRequirement = new PermissionRequirement(
deniedUrl,
ClaimTypes.Role,
issuer,
audience,
signingCredentials,
expiration: TimeSpan.FromHours(10)
);
return services.AddSingleton(permissionRequirement);
}
}
}
接下来看AuthenticationAPI项目:
appsettings.json
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Information"
}
},
"Console": {
"LogLevel": {
"Default": "Information"
}
}
},
"Audience": {
"Secret": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
"Issuer": "gsw",
"Audience": "everone"
}
}
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.JWTAuthorizePolicy;
namespace AuthenticationAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var audienceConfig = Configuration.GetSection("Audience");
//注入OcelotJwtBearer
services.AddJTokenBuild(audienceConfig["Issuer"], audienceConfig["Issuer"], audienceConfig["Secret"], "/api/denied");
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
}
PermissionController.cs
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Ocelot.JWTAuthorizePolicy;
namespace AuthenticationAPI
{
public class PermissionController : Controller
{
///
/// 自定义策略参数
///
PermissionRequirement _requirement;
public PermissionController(PermissionRequirement requirement)
{
_requirement = requirement;
}
[AllowAnonymous]
[HttpPost("/authapi/login")]
public IActionResult Login(string username, string password)
{
var isValidated = (username == "gsw" && password == "111111")|| (username == "ggg" && password == "222222");
var role=username=="gsw"?"admin" :"system";
if (!isValidated)
{
return new JsonResult(new
{
Status = false,
Message = "认证失败"
});
}
else
{
//如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色
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())};
//用户标识
var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);
identity.AddClaims(claims);
var token = JwtToken.BuildJwtToken(claims, _requirement);
return new JsonResult(token);
}
}
}
}
DemoAAPI项目,DemoBAPI项目类似
appsettings.json与网关,AuthenticationAPI相同
Startup.cs
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Ocelot.JWTAuthorizePolicy;
namespace DemoAAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//读取配置文件
var audienceConfig = Configuration.GetSection("Audience");
services.AddOcelotPolicyJwtBearer(audienceConfig["Issuer"], audienceConfig["Issuer"], audienceConfig["Secret"], "GSWBearer", "Permission", "/demoaapi/denied");
//这个集合模拟用户权限表,可从数据库中查询出来
var permission = new List
new Permission { Url="/demoaapi/values", Name="system"},
new Permission { Url="/", Name="system"}
};
services.AddSingleton(permission);
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
}
ValuesController.cs
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DemoAAPI.Controllers
{
[Authorize("Permission")]
[Route("demoaapi/[controller]")]
public class ValuesController : Controller
{
[HttpGet]
public IEnumerable
{
return new string[] { "DemoA服务", "请求" };
}
[AllowAnonymous]
[HttpGet("/demoaapi/denied")]
public IActionResult Denied()
{
return new JsonResult(new
{
Status = false,
Message = "demoaapi你无权限访问"
});
}
}
}
OcelotGateway项目
configuration.json,注意每个连接的AuthenticationOptions. AuthenticationProviderKey,要设置成
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/demoaapi/values",
"DownstreamScheme": "http",
"DownstreamPort": 5001,
"DownstreamHost": "localhost",
"UpstreamPathTemplate": "/demoaapi/values",
"UpstreamHttpMethod": [ "Get" ],
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10,
"TimeoutValue": 5000
},
"HttpHandlerOptions": {
"AllowAutoRedirect": false,
"UseCookieContainer": false
},
"AuthenticationOptions": {
"AuthenticationProviderKey": "GSWBearer",
"AllowedScopes": []
}
},
{
"DownstreamPathTemplate": "/demoaapi/denied",
"DownstreamScheme": "http",
"DownstreamPort": 5001,
"DownstreamHost": "localhost",
"UpstreamPathTemplate": "/demoaapi/denied",
"UpstreamHttpMethod": [ "Get" ],
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10,
"TimeoutValue": 5000
},
"AuthenticationOptions": {
}
},
{
"DownstreamPathTemplate": "/demobapi/values",
"DownstreamScheme": "http",
"DownstreamPort": 5002,
"DownstreamHost": "localhost",
"UpstreamPathTemplate": "/demobapi/values",
"UpstreamHttpMethod": [ "Get" ],
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10,
"TimeoutValue": 5000
},
"HttpHandlerOptions": {
"AllowAutoRedirect": false,
"UseCookieContainer": false
},
"AuthenticationOptions": {
"AuthenticationProviderKey": "GSWBearer",
"AllowedScopes": []
}
},
{
"DownstreamPathTemplate": "/demobapi/denied",
"DownstreamScheme": "http",
"DownstreamPort": 5002,
"DownstreamHost": "localhost",
"UpstreamPathTemplate": "/demobapi/denied",
"UpstreamHttpMethod": [ "Get" ],
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10,
"TimeoutValue": 5000
},
"AuthenticationOptions": {
}
},
{
"DownstreamPathTemplate": "/authapi/login",
"DownstreamScheme": "http",
"DownstreamPort": 5003,
"DownstreamHost": "localhost",
"UpstreamPathTemplate": "/authapi/login",
"UpstreamHttpMethod": [ "Get", "Post" ],
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 10,
"TimeoutValue": 5000
},
"AuthenticationOptions": {
}
}
]
}
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.JWTAuthorizePolicy;
namespace OcelotGateway
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var audienceConfig = Configuration.GetSection("Audience");
//注入OcelotJwtBearer
services.AddOcelotJwtBearer(audienceConfig["Issuer"], audienceConfig["Issuer"], audienceConfig["Secret"], "GSWBearer");
//注入配置文件,AddOcelot要求参数是IConfigurationRoot类型,所以要作个转换
services.AddOcelot(Configuration as ConfigurationRoot);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseOcelot().Wait();
}
}
}
接下来是测试项目,创建一个控制项目TestClient
Nuget中添加RestSharp包
Program.cs
using RestSharp;
using System;
using System.Diagnostics;
namespace TestClient
{
class Program
{
///
/// 访问Url
///
static string _url = "http://127.0.0.1:5000";
static void Main(string[] args)
{
Console.Title = "TestClient";
dynamic token = null;
while (true)
{
Console.WriteLine("1、登录【admin】 2、登录【system】 3、登录【错误用户名密码】 4、查询HisUser数据 5、查询LisUser数据 ");
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":
DemoAAPI(token);
break;
case "5":
DemoBAPI(token);
break;
}
stopwatch.Stop();
TimeSpan timespan = stopwatch.Elapsed;
Console.WriteLine($"间隔时间:{timespan.TotalSeconds}");
tokenString = "Bearer " + Convert.ToString(token?.access_token);
}
}
static string tokenString = "";
static dynamic NullLogin()
{
var loginClient = new RestClient(_url);
var loginRequest = new RestRequest("/authapi/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("/authapi/login", Method.POST);
loginRequest.AddParameter("username", "ggg");
loginRequest.AddParameter("password", "222222");
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("/authapi/login", Method.POST);
loginRequest.AddParameter("username", "gsw");
loginRequest.AddParameter("password", "111111");
IRestResponse loginResponse = loginClient.Execute(loginRequest);
var loginContent = loginResponse.Content;
Console.WriteLine(loginContent);
return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent);
}
static void DemoAAPI(dynamic token)
{
var client = new RestClient(_url);
//这里要在获取的令牌字符串前加Bearer
string tk = "Bearer " + Convert.ToString(token?.access_token);
client.AddDefaultHeader("Authorization", tk);
var request = new RestRequest("/demoaapi/values", Method.GET);
IRestResponse response = client.Execute(request);
var content = response.Content;
Console.WriteLine($"状态码:{(int)response.StatusCode} 状态信息:{response.StatusCode} 返回结果:{content}");
}
static void DemoBAPI(dynamic token)
{
var client = new RestClient(_url);
//这里要在获取的令牌字符串前加Bearer
string tk = "Bearer " + Convert.ToString(token?.access_token);
client.AddDefaultHeader("Authorization", tk);
var request = new RestRequest("/demobapi/values", Method.GET);
IRestResponse response = client.Execute(request);
var content = response.Content; Console.WriteLine($"状态码:{(int)response.StatusCode} 状态信息:{response.StatusCode} 返回结果:{content}");
}
}
}
相关文章:
Ocelot——初识基于.Net Core的API网关
Ocelot API网关的实现剖析
微服务网关Ocelot
API网关Ocelot 使用Polly 处理部分失败问题
谈谈微服务中的 API 网关(API Gateway)
Ocelot网关
原文:http://www.cnblogs.com/axzxs2001/p/8005084.html
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com