第七节:基于Ocelot网关层的微服务校验(手写jwt校验中间件和利用IdentityModel.Tokens.Jwt校验)

一. Ocelot+jwt 方案1

本节架构图:

 

 

 

 

1. 方案背景

  截至目前,我们已经完成了可以通过Ocelot转发请求给业务服务器了,但现在还有一项工作没有做,那就是身份校验,当然我们可以直接写在业务服务器上,但是业务服务器会非常多,不利于维护,所以最佳的写法是写在Ocelot网关上,让Ocelot进行校验,校验通过了,才进行转发给业务服务器,并且业务服务器可能是在内网,外部的客户端无法直接访问。

2. 涉及到的项目

  GateWay1、RequestTokenServer1、GoodsService、OrderService

  用到的程序集:【Ocelot 16.0.1】【Ocelot.Provider.Consul 16.0.1】【JWT 7.2.1】

3. 设计思路

 (1). RequestTokenServer1是认证服务器,提供GetAccessToken接口,客户端通过账号和密码访问,验证通过后返回授权码token。

 代码如下:

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        public IConfiguration Configuration;
        public AuthController(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        /// <summary>
        /// 获取请求Token
        /// </summary>
        /// <param name="account"></param>
        /// <param name="pwd"></param>
        /// <returns></returns>
        [HttpGet]
        public string GetAccessToken(string account, string pwd)
        {
            if (account != "admin" && pwd != "123456")
            {
                return JsonHelp.ToJsonString(new
                {
                    status = "error",
                    msg = "获取失败",
                    token = ""
                });
            }
            string secretKey = Configuration["SecretKey"];

            //1.加密
            //1.1 额外的header参数也可以不设置
            var extraHeaders = new Dictionary<string, object>
                    {
                         {"myName", "ypf" },
                    };
            //过期时间(可以不设置,下面表示签名后 20分钟过期)
            double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;
            //进行组装
            var payload = new Dictionary<string, object>
                    {
                         {"userId", "00000000001" },
                         {"userAccount", account },
                         {"exp",exp }
                    };

            //1.2 进行JWT签名
            var myToken = JWTHelp.JWTJiaM(payload, secretKey, extraHeaders);

            return JsonHelp.ToJsonString(new
            {
                status = "ok",
                msg = "获取成功",
                token = myToken
            });
        }

 (2). GoodsService和OrderService分别以7001 和 7004端口启动,注册到Consul中。

 (3). 新建GateWay1为网关服务器,这里主要使用路由功能进行演示,用于将客户端的请求转发给 GoodsService和OrderService 。

配置文件如下:

//模式三:将Ocelot与consul结合处理,在consul中已经注册业务服务器地址,在Ocelot端不需要再注册了(推荐用法)
{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/{url}",
      "DownstreamScheme": "http",
      "ServiceName": "GoodsService", //Consul中的服务名称
      "LoadBalancerOptions": {
        "Type": "RoundRobin" //轮询算法:依次调用在consul中注册的服务器
      },
      "UseServiceDiscovery": true, //启用服务发现(可以省略,因为会默认赋值)
      "UpstreamPathTemplate": "/GoodsService/{url}",
      "UpstreamHttpMethod": [ "Get", "Post" ]
    },
    {
      "DownstreamPathTemplate": "/api/{url}",
      "DownstreamScheme": "http",
      "ServiceName": "OrderService",
      "LoadBalancerOptions": {
        "Type": "LeastConnection" //最小连接数算法
      },
      "UseServiceDiscovery": true,
      "UpstreamPathTemplate": "/OrderService/{url}",
      "UpstreamHttpMethod": [ "Get", "Post" ]
    }
  ],
  //下面是配置Consul的地址和端口
  "GlobalConfiguration": {
    //对应Consul的ip和Port(可以省略,因为会默认赋值)
    "ServiceDiscoveryProvider": {
      "Host": "127.0.0.1",
      "Port": 8500
    }
  }
}

 (4). 核心:在GateWay1的Configure管道中 自定义jwt校验中间件,eg: app.UseMiddleware<JwtSafeMiddleware>(); 该中间件中拦截Get和Post请求,获取Header中的token,然后进行jwt校验,校验通过,则await _next.Invoke(context);继续走后面的中间件;校验不通过则 返回401未授权。

代码如下:

 public class JwtSafeMiddleware
    {
        private readonly RequestDelegate _next;
        public IConfiguration _configuration;
        public JwtSafeMiddleware(RequestDelegate next, IConfiguration configuration)
        {
            _next = next;
            _configuration = configuration;
        }
       
        public async Task Invoke(HttpContext context)
        {
            //表示如果RequestTokenServer1配置在网关下,则访问它获取token的请求不走jwt校验哦
            //if(!context.Request.Path.Value.StartsWith("/auth")) 

            if (context.Request.Method == "GET" || context.Request.Method == "POST")
            {
                string myToken = context.Request.Headers["token"].FirstOrDefault();
                if (string.IsNullOrEmpty(myToken))
                {
                    context.Response.StatusCode = 401; //401未授权
                    await context.Response.WriteAsync("token为空");
                    return;
                }
                //校验auth的正确性
                var result = JWTHelp.JWTJieM(myToken, _configuration["SecretKey"]);
                if (result == "expired")
                {
                    context.Response.StatusCode = 401; //401未授权
                    await context.Response.WriteAsync("非法请求,参数已经过期");
                    return;
                }
                else if (result == "invalid")
                {
                    context.Response.StatusCode = 401; //401未授权
                    await context.Response.WriteAsync("非法请求,未通过校验");
                    return;
                }
                else if (result == "error")
                {
                    context.Response.StatusCode = 401; //401未授权
                    await context.Response.WriteAsync("非法请求,未通过校验");
                    return;
                }
                else
                {
                    //表示校验通过
                    
                }

            }
            await _next.Invoke(context);
        }
  }

PS:

  这里RequestTokenServer1认证服务器没有配置在GateWay1网关下,客户端可以直接访问该认证服务器。 当然,配置在网关下也行(eg: /auth/{url}代表认证server),那么只是需要在JwtSafeMiddleware中间件中加一层判断,比如if(!context.Request.Path.Value.StartsWith("/auth")) 即可。

4. 用PostMan充当客户端进行测试

 (1).通过【consul.exe agent -dev】启动Consul。

 (2).分别启动业务服务器:【dotnet GoodsService.dll --urls="http://*:7001" --ip="127.0.0.1" --port=7001 】

              【dotnet OrderService.dll --urls="http://*:7004" --ip="127.0.0.1" --port=7004】

  启动网关服务器:【dotnet GateWay1.dll --urls="http://*:7030" --ip="127.0.0.1" --port=7030】

  启动认证服务器:【dotnet RequestTokenServer1.dll --urls="http://*:7031" --ip="127.0.0.1" --port=7031】

 (3).测试1:

  用PostMan以Get请求访问:http://127.0.0.1:7030/GoodsService/Catalog/GetMsg 报401,错误内容为“token为空”

  用PostMan以Post且表单提交的方式访问:http://127.0.0.1:7030/OrderService/Buy/pOrder1 报401,错误内容为“token为空”。

 (4). 测试2:

  用PostMan以Get请求访问:http://127.0.0.1:7031/api/Auth/GetAccessToken?account=admin&pwd=123456 获取返回值中的token,然后把该token值放到header中,即token =“xxxx”, 然后再

 

  用PostMan以Get请求访问:http://127.0.0.1:7030/GoodsService/Catalog/GetMsg 访问成功

  用PostMan以Post且表单提交的方式访问:http://127.0.0.1:7030/OrderService/Buy/pOrder1 访问成功

 

 

二. Ocelot+jwt 方案2

  利用【System.IdentityModel.Tokens.Jwt】程序集写的那套,由于需要加[Authorize],这个验证只能写在各个业务服务器上, 不是很好,此处不再重复了,如果需要,参照:https://www.cnblogs.com/yaopengfei/p/12162507.html

 

   后续将介绍 Ocelot+ID4 做授权校验

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

 

posted @ 2020-06-13 15:13  Yaopengfei  阅读(1554)  评论(4编辑  收藏  举报