1.认证

当客户端通过Ocelot访问下游服务的时候,为了保护下游资源服务器会进行认证鉴权,这时候需要在Ocelot添加认证服务。添加认证服务后,随后使用Ocelot基于声明的任何功能,例如授权或使用Token中的值修改请求。用户必须像往常一样在其Startup.cs中注册身份验证服务,但是他们为每次注册提供一个方案(身份验证提供者密钥),例如:

public void ConfigureServices(IServiceCollection services)
{
var authenticationProviderKey = "TestKey";
services.AddAuthentication()
.AddJwtBearer(authenticationProviderKey, x =>
{
});
}

在此Ocelot认证项目示例中,TestKey是已注册此提供程序的方案。然后我们将其映射到配置中的Routes路由,例如:

{
"Routes": [
{
"DownstreamPathTemplate": "/api/customers",
"DownstreamScheme": "http",
"DownstreamHost": "localhost",
"DownstreamPort": 9001,
"UpstreamPathTemplate": "/customers",
"UpstreamHttpMethod": [ "Get" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "TestKey",
"AllowedScopes": []
}
}
]
}

Ocelot运行时,它将查看Routes.AuthenticationOptions.AuthenticationProviderKey并检查是否存在使用给定密钥注册的身份验证提供程序。如果不存在,则Ocelot将不会启动,如果存在,则Routes将在执行时使用该提供程序。
如果对路由进行身份验证,Ocelot将在执行身份验证中间件时调用与之关联的任何方案。如果请求通过身份验证失败,Ocelot将返回http状态代码401。

2.JWT Tokens Bearer认证

Json Web Token (JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

2.1JWT令牌结构

在紧凑的形式中,JSON Web Tokens由dot(.)分隔的三个部分组成,它们是:
Header头、Payload有效载荷、Signature签名
因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz(Header.Payload.Signature)

2.1.1Header头

标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法,例如HMAC SHA256或RSA。例如:

{
"alg": "HS256",
"typ": "JWT"
}

然后,这个JSON被编码为Base64Url,形成JWT的第一部分。

2.1.2Payload有效载荷

Payload部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段,供选用。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。例如:

{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

注意,JWT默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。这个JSON对象也要使用Base64URL算法转成字符串。

2.1.3.Signature签名

Signature部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用Header里面指定的签名算法(默认是HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

签名用于验证消息在此过程中未被更改,并且,在使用私钥签名的令牌的情况下,它还可以验证JWT的发件人是否是它所声称的人。
把他们三个全部放在一起,输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递,而与基于XML的标准(如SAML)相比更加紧凑。
下面显示了一个JWT,它具有先前的头和有效负载编码,并使用机密签名。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoid3prNzAzIiwibmJmIjoiMTU5MjE0MzkzNyIsImV4cCI6MTU5MjE0Mzk5OCwiaXNzIjoiYXV0aC5qd3QuY2MiLCJhdWQiOiJkZW5nd3V8MjAyMC82LzE0IDIyOjEyOjE5In0
.4RiwhRy0rQkZjclOFWyTpmW7v0AMaL3aeve1L-eWIz0

其实一般发送用户名和密码获取token那是由Identity4来完成的,包括验证用户,生成JwtToken。但是项目这里是由System.IdentityModel.Tokens类库来生成JwtToken。最后返回jwt令牌token给用户。JwtToken解码可以通过https://jwt.io/中进行查看。

3.项目演示

3.1APIGateway项目

在该项目中启用身份认证来保护下游api服务,使用JwtBearer认证,将默认的身份验证方案设置为TestKey。在appsettings.json文件中配置认证中密钥(Secret)跟受众(Aud)信息:

{
"Audience": {
"Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==",
"Iss": "http://www.c-sharpcorner.com/members/catcher-wong",
"Aud": "Catcher Wong"
}
}

Startup添加身份认证代码如下:

public void ConfigureServices(IServiceCollection services)
{
//获取appsettings.json文件中配置认证中密钥(Secret)跟受众(Aud)信息
var audienceConfig = Configuration.GetSection("Audience");
//获取安全秘钥
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));
//token要验证的参数集合
var tokenValidationParameters = new TokenValidationParameters
{
//必须验证安全秘钥
ValidateIssuerSigningKey = true,
//赋值安全秘钥
IssuerSigningKey = signingKey,
//必须验证签发人
ValidateIssuer = true,
//赋值签发人
ValidIssuer = audienceConfig["Iss"],
//必须验证受众
ValidateAudience = true,
//赋值受众
ValidAudience = audienceConfig["Aud"],
//是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
ValidateLifetime = true,
//允许的服务器时间偏移量
ClockSkew = TimeSpan.Zero,
//是否要求Token的Claims中必须包含Expires
RequireExpirationTime = true,
};
//添加服务验证,方案为TestKey
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = "TestKey";
})
.AddJwtBearer("TestKey", x =>
{
x.RequireHttpsMetadata = false;
//在JwtBearerOptions配置中,IssuerSigningKey(签名秘钥)、ValidIssuer(Token颁发机构)、ValidAudience(颁发给谁)三个参数是必须的。
x.TokenValidationParameters = tokenValidationParameters;
});
//添加Ocelot网关服务时,包括Secret秘钥、Iss签发人、Aud受众
services.AddOcelot(Configuration);
}
public async void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//使用认证服务
app.UseAuthentication();
//使用Ocelot中间件
await app.UseOcelot();
}
3.1.1Identity Server承载JWT Token

在第二小节介绍JWT Token认证时候,我们都知道一般发送用户名和密码获取Token那是由Identity4来完成的,包括验证用户,生成JWT Token。也就是说Identity Server承载了JWT Token认证功能。为了使用IdentityServer承载Token,请像往常一样在ConfigureServices中使用方案(密钥)注册IdentityServer服务。如果您不知道如何执行此操作,请查阅IdentityServer文档。

public void ConfigureServices(IServiceCollection services)
{
var authenticationProviderKey = "TestKey";
Action<IdentityServerAuthenticationOptions> options = o =>
{
o.Authority = "https://whereyouridentityserverlives.com";
o.ApiName = "api";
o.SupportedTokens = SupportedTokens.Both;
o.ApiSecret = "secret";
};
services.AddAuthentication()
.AddIdentityServerAuthentication(authenticationProviderKey, options);
services.AddOcelot();
}

在Identity4中是由Authority参数指定OIDC服务地址,OIDC可以自动发现Issuer, IssuerSigningKey等配置,而o.Audience与x.TokenValidationParameters = new TokenValidationParameters { ValidAudience = "api" }是等效的。

3.2AuthServer项目

此服务主要用于客户端请求受保护的资源服务器时,认证后产生客户端需要的JWT Token,生成JWT Token关键代码如下:

[Route("api/[controller]")]
public class AuthController : Controller
{
private IOptions<Audience> _settings;
public AuthController(IOptions<Audience> settings)
{
this._settings = settings;
}
/// <summary>
///用户使用 用户名密码 来请求服务器
///服务器进行验证用户的信息
///服务器通过验证发送给用户一个token
///客户端存储token,并在每次请求时附送上这个token值, headers: {'Authorization': 'Bearer ' + token}
///服务端验证token值,并返回数据
/// </summary>
/// <param name="name"></param>
/// <param name="pwd"></param>
/// <returns></returns>
[HttpGet]
public IActionResult Get(string name, string pwd)
{
//验证登录用户名和密码
if (name == "catcher" && pwd == "123")
{
var now = DateTime.UtcNow;
//添加用户的信息,转成一组声明,还可以写入更多用户信息声明
var claims = new Claim[]
{
//声明主题
new Claim(JwtRegisteredClaimNames.Sub, name),
//JWT ID 唯一标识符
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
//发布时间戳 issued timestamp
new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
};
//下面使用 Microsoft.IdentityModel.Tokens帮助库下的类来创建JwtToken //安全秘钥
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_settings.Value.Secret)); //声明jwt验证参数
var tokenValidationParameters = new TokenValidationParameters
{
//必须验证安全秘钥
ValidateIssuerSigningKey = true,
//赋值安全秘钥
IssuerSigningKey = signingKey,
//必须验证签发人
ValidateIssuer = true,
//赋值签发人
ValidIssuer = _settings.Value.Iss,
//必须验证受众
ValidateAudience = true,
//赋值受众
ValidAudience = _settings.Value.Aud,
//是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
ValidateLifetime = true,
//允许的服务器时间偏移量
ClockSkew = TimeSpan.Zero,
//是否要求Token的Claims中必须包含Expires
RequireExpirationTime = true,
};
var jwt = new JwtSecurityToken(
//jwt签发人
issuer: _settings.Value.Iss,
//jwt受众
audience: _settings.Value.Aud,
//jwt一组声明
claims: claims,
notBefore: now,
//jwt令牌过期时间
expires: now.Add(TimeSpan.FromMinutes(2)),
//签名凭证: 安全密钥、签名算法
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
);
//生成jwt令牌(json web token)
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var responseJson = new
{
access_token = encodedJwt,
expires_in = (int)TimeSpan.FromMinutes(2).TotalSeconds
};
return Json(responseJson);
}
else
{
return Json("");
}
}
}
public class Audience
{
public string Secret { get; set; }
public string Iss { get; set; }
public string Aud { get; set; }
}

appsettings.json文件中配置认证中密钥(Secret)跟受众(Aud)信息:

{
"Audience": {
"Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==",
"Iss": "http://www.c-sharpcorner.com/members/catcher-wong",
"Aud": "Catcher Wong"
}
}

3.3CustomerAPIServices项目

该项目跟APIGateway项目是一样的,为了保护下游api服务,使用JwtBearer认证,将默认的身份验证方案设置为TestKey。在appsettings.json文件中配置认证中密钥(Secret)跟受众(Aud)信息:

{
"Audience": {
"Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==",
"Iss": "http://www.c-sharpcorner.com/members/catcher-wong",
"Aud": "Catcher Wong"
}
}

Startup添加身份认证代码如下:

public void ConfigureServices(IServiceCollection services)
{
//获取appsettings.json文件中配置认证中密钥(Secret)跟受众(Aud)信息
var audienceConfig = Configuration.GetSection("Audience");
//获取安全秘钥
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));
//token要验证的参数集合
var tokenValidationParameters = new TokenValidationParameters
{
//必须验证安全秘钥
ValidateIssuerSigningKey = true,
//赋值安全秘钥
IssuerSigningKey = signingKey,
//必须验证签发人
ValidateIssuer = true,
//赋值签发人
ValidIssuer = audienceConfig["Iss"],
//必须验证受众
ValidateAudience = true,
//赋值受众
ValidAudience = audienceConfig["Aud"],
//是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
ValidateLifetime = true,
//允许的服务器时间偏移量
ClockSkew = TimeSpan.Zero,
//是否要求Token的Claims中必须包含Expires
RequireExpirationTime = true,
};
//添加服务验证,方案为TestKey
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = "TestKey";
})
.AddJwtBearer("TestKey", x =>
{
x.RequireHttpsMetadata = false;
//在JwtBearerOptions配置中,IssuerSigningKey(签名秘钥)、ValidIssuer(Token颁发机构)、ValidAudience(颁发给谁)三个参数是必须的。
x.TokenValidationParameters = tokenValidationParameters;
}); services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
//使用认证服务
app.UseAuthentication();
app.UseMvc();
}

在CustomersController下添加一个需要认证方法,一个不需要认证方法:

[Route("api/[controller]")]
public class CustomersController : Controller
{
//添加认证属性
[Authorize]
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "Catcher Wong", "James Li" };
}
[HttpGet("{id}")]
public string Get(int id)
{
return $"Catcher Wong - {id}";
}
}

3.4ClientApp项目

该项目是用来模拟客户端访问资源服务器整个认证流程测试项目,在Program主程序可以看到如下代码:

class Program
{
static void Main(string[] args)
{
HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Clear();
client.BaseAddress = new Uri("http://localhost:9000"); // 1. without access_token will not access the service
// and return 401 .
var resWithoutToken = client.GetAsync("/customers").Result; Console.WriteLine($"Sending Request to /customers , without token.");
Console.WriteLine($"Result : {resWithoutToken.StatusCode}"); //2. with access_token will access the service
// and return result.
client.DefaultRequestHeaders.Clear();
Console.WriteLine("\nBegin Auth....");
var jwt = GetJwt();
Console.WriteLine("End Auth....");
Console.WriteLine($"\nToken={jwt}"); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}");
var resWithToken = client.GetAsync("/customers").Result; Console.WriteLine($"\nSend Request to /customers , with token.");
Console.WriteLine($"Result : {resWithToken.StatusCode}");
Console.WriteLine(resWithToken.Content.ReadAsStringAsync().Result); //3. visit no auth service
Console.WriteLine("\nNo Auth Service Here ");
client.DefaultRequestHeaders.Clear();
var res = client.GetAsync("/customers/1").Result; Console.WriteLine($"Send Request to /customers/1");
Console.WriteLine($"Result : {res.StatusCode}");
Console.WriteLine(res.Content.ReadAsStringAsync().Result); Console.Read();
}
private static string GetJwt()
{
HttpClient client = new HttpClient(); client.BaseAddress = new Uri( "http://localhost:9000");
client.DefaultRequestHeaders.Clear(); var res2 = client.GetAsync("/api/auth?name=catcher&pwd=123").Result; dynamic jwt = JsonConvert.DeserializeObject(res2.Content.ReadAsStringAsync().Result); return jwt.access_token;
}
}

运行项目看看测试结果:

结合代码,我们能看到当客户端通过Ocelot网关访问下游服务http://localhost:9000/api/Customers/Get方法时候,因为该方法是需要通过认证才返回处理结果的,所以会进行JWT Token认证,如果发现没有Token,Ocelot则返回http状态代码401拒绝访问。如果我们通过GetJwt方法在AuthServer服务上登录认证获取到授权Token,然后再访问该资源服务器接口,立即就会返回处理结果,通过跟而未加认证属性的http://localhost:9000/api/Customers/Get/{id}方法对比,我们就知道,Ocelot认证已经成功了!

4.总结

该章节只是结合demo项目简单介绍在Ocelot中如何使用JWT Token认证。其实正式环境中,Ocelot是应该集成IdentityServer认证授权的,同样的通过重写Ocelot中间件我们还可以把configuration.json的配置信息存储到数据库或者缓存到Redis中。

参考文献:
Ocelot官网

(3)ASP.NET Core3.1 Ocelot认证的更多相关文章

  1. (1)ASP.NET Core3.1 Ocelot介绍

    1.简介 Ocelot原本设计仅为与.NET Core一起使用的,它是一个.NET API网关,作为面向使用.NET运行微型服务/面向服务的体系结构需要统一的系统入口点,即当客户端(Web站点,手机A ...

  2. (8)ASP.NET Core3.1 Ocelot Consul服务注册与发现

    1.服务注册与发现(Service Discovery) ●服务注册:我们通过在每个服务实例写入注册代码,实例在启动的时候会先去注册中心(例如Consul.ZooKeeper.etcd.Eureka) ...

  3. (2)ASP.NET Core3.1 Ocelot路由

    1.路由 前一个章节我们已经介绍过Ocelot,相信大家也了解到,Ocelot的主要功能是接收客户端等传入的HTTP请求,并将其转发到下游服务.Ocelot当前仅以另一个http请求的形式支持此功能( ...

  4. (4)ASP.NET Core3.1 Ocelot负载均衡

    1.负载均衡 Ocelot可以在每个路由的可用下游服务中实现负载均衡,这使我们更有效地选择下游服务来处理请求.负载均衡类型:●LeastConnection:根据服务正在处理请求量的情况来决定哪个服务 ...

  5. (5)ASP.NET Core3.1 Ocelot服务质量

    1.服务质量(Quality of Service) 对于微服务来说,熔断就是我们常说的"保险丝",意思是当服务出现某些状况时候,通过切断服务防止应用程序不断地执行可能会失败的操作 ...

  6. (7)ASP.NET Core3.1 Ocelot Swagger

    1.前言 前端与后端的联系更多是通过API接口对接,API文档变成了前后端开发人员联系的纽带,开始变得越来越重要,而Swagger就是一款让你更好的书写规范API文档的框架.在Ocelot Swagg ...

  7. ASP.NET Core3.1使用IdentityServer4中间件系列随笔(二):创建API项目,配置IdentityServer保护API资源

    配套源码:https://gitee.com/jardeng/IdentitySolution 接上一篇<ASP.NET Core3.1使用IdentityServer4中间件系列随笔(一):搭 ...

  8. Asp.Net Identity 2.0 认证

    转Asp.Net Identity 2.0 认证 一个星期前,也就是3月20日,微软发布了Asp.Net Identity 2.0 RTM.功能更加强大,也更加稳定.Identity这个东西现在版本还 ...

  9. asp.net core 2.1认证

    asp.net core 2.1认证 这篇文章基于asp.net core的CookieAuthenticationHandler来讲述. 认证和授权很相似,他们的英文也很相似,一个是Authenti ...

随机推荐

  1. Python 为什么不支持 switch 语句?

    本文出自"Python为什么"系列,请查看全部文章 在这篇文章里,我们会聊一聊为什么 Python 决定不支持 switch 语句. 为什么想要聊这个话题呢? 主要是因为 swit ...

  2. 《VC++ 深入详解》 第3版 这是盗版书么~。。。

    <VC++ 深入详解> 第3版 www.broadview.com.cn 书读到一小半,发现书重复了一部分,缺失一部分.... 难受~ 比较难继续下去了 有一样的小伙伴么~ <VC+ ...

  3. 【学习笔记/题解】虚树/[SDOI2011]消耗战

    题目戳我 \(\text{Solution:}\) 题目很显然可以设\(dp[i]\)表示\(i\)的子树内的关键点都不和\(i\)联通的最小待机,有如下\(dp\)方程: \(v\in son_u, ...

  4. trie树——【吴传之火烧连营】

    突然发现好像没有讲过一种叫做tire树的神奇东西. 问题描述: 题目描述 [题目背景] 蜀汉章武元年(221年),刘备为报吴夺荆州.关羽被杀之仇,率大军攻吴.吴将陆逊为避其锋,坚守不战,双方成对峙之势 ...

  5. CentOS7 没有安装 ifconfig 命令

    ifconfig 命令是设置或显示网络接口的程序,可以显示出我们机器的网卡信息. 除此之外, ip a 命令,也可以设置或显示网卡的信息 在 CentOS 7 下,默认 ifconfig 命令是没有安 ...

  6. if-else和if if的对比

  7. centos8上使用crond

    一,查看crond的状态: [root@yjweb crontab]# systemctl status crond 说明:和其他service的执行相同: 启动:systemctl start cr ...

  8. java安全编码指南之:Thread API调用规则

    目录 简介 start一个Thread 不要使用ThreadGroup 不要使用stop()方法 wait 和 await 需要放在循环中调用 简介 java中多线程的开发中少不了使用Thread,我 ...

  9. day70:Vue:Git&路飞学城页面效果

    目录 1.Git 2.路飞学城项目页面效果 0.安装elements UI 1.顶部导航栏效果 2.轮播图效果 1.Git 什么是git?分布式版本管理工具 1.git操作 # 1 创建git本地仓库 ...

  10. 《Kafka笔记》3、Kafka高级API

    目录 1 Kafka高级API特性 1.1 Offset的自动控制 1.1.1 消费者offset初始策略 1.1.2 消费者offset自动提交策略 1.2 Acks & Retries(应 ...