一.概述

  在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. TCP分组交换详解

    TCP(Transmission Control Protocol) 传输控制协议 TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接: 位码即tcp标志位,有6种标 ...

  2. idea 整合ssm 启动页404问题

  3. Tomcat和JavaWeb目录和流程

    Tomcat主要目录结构 bin 二进制可执行文件,包含启动和关闭tomcat文件  conf 配置文件,其中包含了server.xml.context.xml.web.xml等  webapps 存 ...

  4. 火狐兼容window.event.returnValue=false;

    火狐中window.event是未定义的,可用e.preventDefault();替代window.event.returnValue=false; 直接上图

  5. Python_字符串格式化

    #冒泡排序 array = [1,2,3,6,5,4] for i in range(len(array)): for j in range(i): if array[j] > array[j ...

  6. 进阶-Redis 知识梳理

    redis介绍 1.什么是NoSQL NoSQL(统称),泛指非关系型的数据库,NoSQL即Not-Only SQL,它可以作为关系型数据库的良好补充. 2.NoSQL数据库的四大分类如下: 键值(K ...

  7. eclipse maven 构建简单springmvc项目

    环境:eclipse Version: Oxygen.3a Release (4.7.3a) 创建maven Project项目,目录结构 修改工程的相关编译属性 修改pop.xml,引入spring ...

  8. react native 1跳2 2跳3 3跳4 4pop回2

    网上有介绍导航的很多了 就不一一说了   直接说一个小功能 popToRoute pop回指定页面  第一次写 组织能力不是特别好 直接贴代码 例如 我们有四个页面  从第四个pop到第二个页面  先 ...

  9. Spring中的循环依赖

    循环依赖 在使用Spring时,如果主要采用基于构造器的依赖注入方式,则可能会遇到循环依赖的情况,简而言之就是Bean A的构造器依赖于Bean B,Bean B的构造器又依赖于Bean A.在这种情 ...

  10. 基于支付系统真实场景的分布式事务解决方案效果演示: http://www.iqiyi.com/w_19rsveqlhh.html

    基于支付系统真实场景的分布式事务解决方案效果演示:http://www.iqiyi.com/w_19rsveqlhh.html