Ocelot统一权限验证

Ocelot作为网关,可以用来作统一验证,接上一篇博客Ocelot网关,我们继续
Ocelot统一权限验证_第1张图片
前一篇,我们创建了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 _permissions;

///

/// 构造

///

///

public PermissionHandler(IAuthenticationSchemeProvider schemes, List permissions=null)

{

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 Get()

{

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

640?wx_fmt=jpeg

你可能感兴趣的:(Ocelot统一权限验证)