一.概述

  在Ocelot中,为了保护下游api资源,用户访问时需要进行认证鉴权,这需要在Ocelot 网关中添加认证服务。添加认证后,ReRoutes路由会进行身份验证,并使用Ocelot的基于声明的功能。在Startup.cs中注册认证服务,为每个注册提供一个方案 (authenticationProviderKey身份验证提供者密钥)。

//下面是在网关项目中,添加认证服务
public void ConfigureServices(IServiceCollection services)
{
var authenticationProviderKey = "TestKey"; services.AddAuthentication()
.AddJwtBearer(authenticationProviderKey, x =>
{
//..
});
}

  其中TestKey是此提供程序已注册的方案,将映射到ReRoute的配置中

      "AuthenticationOptions": {
"AuthenticationProviderKey": "TestKey",
"AllowedScopes": []
}

  当Ocelot运行时,会查看此configuration.json中的AuthenticationProviderKey节点,并检查是否使用给定密钥,该密钥是否已注册身份验证提供程序。如果没有,那么Ocelot将无法启动。如果有,则ReRoute将在执行时使用该提供程序。

  本次示例有四个项目:

    APIGateway网关项目  http://localhost:9000

    AuthServer项目生成jwt令牌服务  http://localhost:9009

    CustomerAPIServices 是web api项目  http://localhost:9001

    ClientApp项目 模拟客户端HttpClient

  当客户想要访问web api服务时,首先访问API网关的身份验证模块。我们需要首先访问AuthServer以获取访问令牌,以便我们可以使用access_token访问受保护的api服务。开源Github地址,  架构如下图所示:

  

二. AuthServer项目

  此服务主要用于,为用户请求受保护的api,需要的jwt令牌。生成jwt关键代码如下:

        /// <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)
{
//验证用户,通过后发送一个token
if (name == "catcher" && pwd == "")
{ 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令牌(json web token)
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()),
//签名凭证: 安全密钥、签名算法
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
);
//序列化jwt对象,写入一个字符串encodedJwt
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); var responseJson = new
{
access_token = encodedJwt,
expires_in = (int)TimeSpan.FromMinutes().TotalSeconds
};
//以json形式返回
return Json(responseJson);
}
else
{
return Json("");
}
}
}

  

  在之前讲IS4的第55篇中,讲ResourceOwnerPasswords项目,获取token也是要发送用户名和密码,那是由is4来完成,包括自动:验证用户,生成jwtToken。这里由System.IdentityModel.Tokens类库来生成jwtToken。最后返回jwt令牌token给用户。

  当catcher用户请求:http://localhost:9009/api/auth?name=catcher&pwd=123服务时,产生jwt令牌token,下面是换了行的Token, 如下所示:

{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiJjYXRjaGVyIiwianRpIjoiZWJmNWIyZGItNDg5YS00OTBjLTk0NjUtODZmOTE5YWEzMDRjIiwiaWF0IjoiMjAxOS80LzI1IDE6NTc6MjAiLCJuYmYiOjE1NTYxNTc0NDAsImV4cC
  I6MTU1NjE1NzU2MCwiaXNzIjoiaHR0cDovL3d3dy5jLXNoYXJwY29ybmVyLmNvbS9tZW1iZXJzL2NhdGNoZXItd29uZyIsImF1ZCI6IkNhdGNoZXIgV29uZyJ9
.O2jI7NSnothl9Agbr0VhmdoBsXhDEoxkYNOuGaSEkkg","expires_in":}

  简单了解下JWT(JSON Web Token),它是在Web上以JSON格式传输的Token。该Token被设计为紧凑声明表示格式,意味着字节少,它可以在GET URL中,Header中,Post Parameter中进行传输。

  JWT一般由三段构成(Header.Payload.Signature),用"."号分隔开,是base64编码的,可以把该字符串放到https://jwt.io/中进行查看,如下所示:

  在Header中:alg:声明加密的算法,这里为HS256。typ:声明类型,这里为JWT。

  在Payload中:  

    sub: 主题, jwt发布者名称。

    jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。也就是请求生成的token不一样。

    iat: 签发时间

    nbf: 在什么时间之前,该jwt都是不可用的,是时间戳格式。

    exp:jwt的过期时间,这个过期时间必须要大于签发时间。

    adu: 订阅者,接收jwt的一方。

    iss:  jwt的发行方。

  Signature(数字签名,防止信息被篡改):

    包含了:base64后的Header,Payload ,Secret,secret就是用来进行jwt的签发和jwt的验证。相当于服务端的私钥。该secret在示例中,用在AuthServer和CustomerAPIServices项目中。

三. CustomerAPIServices项目

  在该web api 项目中启用身份验证来保护api服务,使用JwtBearer,将默认的身份验证方案设置为TestKey。添加身份验证代码如下:

   public void ConfigureServices(IServiceCollection services)
{
//获取当前用户(订阅者)信息
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();
}

  

  新建一个CustomersController类,在api方法中使用Authorize属性。

   [Route("api/[controller]")]
public class CustomersController : Controller
{
//Authorize]:加了该标记,当用户请求时,需要发送有效的jwt
[Authorize]
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "Catcher Wong", "James Li" };
} //未加授权标记,不受保护,任何用户都可以获取
[HttpGet("{id}")]
public string Get(int id)
{
return $"Catcher Wong - {id}";
}
}

  下面运行,在浏览器中直接访问http://localhost:9001/api/customers 报http 500错误,而访问http://localhost:9001/api/customers/1 则成功http 200,显示“Catcher Wong - 1”

四. APIGateway网关

  添加认证服务,基本与CustomerAPIServices项目中的认证服务一样。代码如下:

  public void ConfigureServices(IServiceCollection services)
{
//获取当前用户(订阅者)信息
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;
});
//这里也可以使用IS4承载令牌
/*
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);
*/ //添加Ocelot网关服务时,包括Secret秘钥、Iss发布者、Aud订阅者
services.AddOcelot(Configuration);
}

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

  下面应该修改configuration.json文件。添加一个名为AuthenticationOptions的新节点,并使AuthenticationProviderKey与我们在Startup类中定义的相同。

        "ReRoutes": [
{
"DownstreamPathTemplate": "/api/customers",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port":
}
],
"UpstreamPathTemplate": "/customers",
"UpstreamHttpMethod": [ "Get" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "TestKey",
"AllowedScopes": []
}
}

  APIGateway网关项目和CustomerAPIServices项目的appsettings.json文件,都配置了订阅者信息如下:

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

五. ClientApp项目

  最后使用的客户端应用程序,来模拟API网关的一些请求。首先,我们需要添加一个方法来获取access_token。

        /// <summary>
/// 获取jwtToken
/// </summary>
/// <returns></returns>
private static string GetJwt()
{
HttpClient client = new HttpClient(); //9000是网关,会自动转发到下游服务器,
client.BaseAddress = new Uri( "http://localhost:9000");
client.DefaultRequestHeaders.Clear(); //转发到AuthServer的9009
var res2 = client.GetAsync("/api/auth?name=catcher&pwd=123").Result; dynamic jwt = JsonConvert.DeserializeObject(res2.Content.ReadAsStringAsync().Result); return jwt.access_token;
}

  接着,编写了三段代码 , 通过API Gateway网关, 来访问CustomerAPIServices项目中的api服务:

    static void Main(string[] args)
{
HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Clear();
client.BaseAddress = new Uri("http://localhost:9000"); // 1. 需要授权的api访问,没有token时,返回http状态401
var resWithoutToken = client.GetAsync("/customers").Result; Console.WriteLine($"Sending Request to /customers , without token.");
Console.WriteLine($"Result : {resWithoutToken.StatusCode}"); //2. 需要授权的api访问,获取令牌请求api,返回http状态200正常
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.不需要授权的api访问,返回http状态200正常
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();
}

 

  参考文献

    在ASP.NET核心中使用Ocelot构建API网关 - 身份验证

     官方文档

asp.net core系列 60 Ocelot 构建服务认证示例的更多相关文章

  1. asp.net core系列 61 Ocelot 构建服务发现简单示例

    一.概述 Ocelot允许指定服务发现提供程序,如Consul或Eureka. 这二个中间件是用来实现:服务治理或秒服务发现,服务发现查找Ocelot正在转发请求的下游服务的主机和端口.目前Ocelo ...

  2. asp.net core系列 59 Ocelot 构建基础项目示例

    一.入门概述 从这篇开始探讨Ocelot,Ocelot是一个.NET API网关,仅适用于.NET Core,用于.NET面向微服务/服务的架构中.当客户端(web站点.ios. app 等)访问we ...

  3. asp.net core系列 39 Razor 介绍与详细示例

    原文:asp.net core系列 39 Razor 介绍与详细示例 一. Razor介绍 在使用ASP.NET Core Web开发时, ASP.NET Core MVC 提供了一个新特性Razor ...

  4. asp.net core 系列 3 依赖注入服务

    一. 依赖注入概述 在软件设计的通用原则中,SOLID是非常流行的缩略语,它由5个设计原则的首字母构成:单一原则(S).开放封闭原则(O).里氏替换原则(L).接口分离原则(I).依赖反转原则(D). ...

  5. 【目录】asp.net core系列篇

    随笔分类 - asp.net core系列篇 asp.net core系列 68 Filter管道过滤器 摘要: 一.概述 本篇详细了解一下asp.net core filters,filter叫&q ...

  6. Asp.net Core 系列之--3.领域、仓储、服务简单实现

    ChuanGoing 2019-11-11  距离上篇近两个月时间,一方面时因为其他事情耽搁,另一方面也是之前准备不足,关于领域驱动有几个地方没有想通透,也就没有继续码字.目前网络包括园子里大多领域驱 ...

  7. asp.net core系列 40 Web 应用MVC 介绍与详细示例

    一. MVC介绍 MVC架构模式有助于实现关注点分离.视图和控制器均依赖于模型. 但是,模型既不依赖于视图,也不依赖于控制器. 这是分离的一个关键优势. 这种分离允许模型独立于可视化展示进行构建和测试 ...

  8. asp.net core系列 36 WebAPI 搭建详细示例

    一.概述 HTTP不仅仅用于提供网页.HTTP也是构建公开服务和数据的API强大平台.HTTP简单灵活且无处不在.几乎任何你能想到的平台都有一个HTTP库,因此HTTP服务可以覆盖广泛的客户端,包括浏 ...

  9. asp.net core 系列 17 通用主机 IHostBuilder

    一.概述 ASP.NET Core 通用主机 (HostBuilder),该主机对于托管不处理 HTTP 请求的应用非常有用.通用主机的目标是将 HTTP 管道从 Web 主机 API 中分离出来,从 ...

随机推荐

  1. Java并发-线程安全性

    首先了解一下多线程的概念 多线程:两段或以上的代码同时进行,多个顺序执行流. 并发和并行的区别 并发:做一下这个做一下那个. 并行:同时进行. 线程和进程的区别 进程:资源分配的基本单位,运行中的程序 ...

  2. UE4学习心得:Scene Component蓝图的一个简单应用

    Scene Component是蓝图类中一个不怎么常用的分类(特别是对于新手而言),主要是其实现的功能可以在Actor类中用相同的方法实现,使其作用显得有点多余. 笔者在使用过这个类之后发现其作用更相 ...

  3. AUTOSAR的前期开源实现Arctic Core

    AUTOSAR (AUTomotive Open System ARchitecture) is a worldwide development partnership of vehicle manu ...

  4. Django入门五之admin管理

    1. 准备工作 #settings.py #urls.py 2. 创建管理员账号 进入CMD 3. 运行服务器 登录后,发现没有数据的 4. 创建数据 在website/blog/ 新建一个admin ...

  5. wcf双工通讯

    首先说一个服务引用不成功的解决办法: 需要把服务端配置文件中的Binding换成: <endpoint address="" binding="BasicHttpB ...

  6. android studio 在使用图片的时候会检测图片的合法性

    1.当android studio 在使用png格式的时候,会报一系列的错误,甚至会显示找不到R文件,这是因为Android studio 不支持png格式的图片,检测图片的合法性的时候会报错 解决方 ...

  7. [ 搭建Redis本地服务器实践系列二 ] :图解CentOS7配置Redis

    上一章 [ 搭建Redis本地服务器实践系列一 ] :图解CentOS7安装Redis 详细的介绍了Redis的安装步骤,那么只是安装完成,此时的Redis服务器还无法正常运作,我们需要对其进行一些配 ...

  8. 微信小程序开发资源汇总 💯

    目录 官方文档 工具 插件 讨论 教程 视频教程 文章 代码 轮子 置顶 WePY:组件化的小程序开发框架 

  9. 利用Django进行Web开发

    Web就是用来表示Internet主机上供外界访问的资源的.网页也统称为web资源.Internet上供外界访问的Web资源主要分为如下两类: 静态web资源:指web页面中供人们浏览的数据始终是不变 ...

  10. XamarinForm Effects 调用事件

    原文地址 在Xamarin.Forms控件中实现底层多点触控跟踪. 一个effect可以定义和调用一个事件,在底层本地视图中发出信号的变化.这篇文章演示如何实现底层多点触控跟踪,以及如何生成信号触摸活 ...