zoukankan      html  css  js  c++  java
  • 第七节:基于Ocelot网关层的微服务校验(手写jwt校验中间件和利用IdentityModel.Tokens.Jwt校验)

    一. Ocelot+jwt 方案1

    1. 方案背景

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

    2. 涉及到的项目

      GateWay1、RequestTokenServer1、GoodsService、OrderService

    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端不需要再注册了(推荐用法)
    {
      "ReRoutes": [
        {
          "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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    【UOJ 121】Hzwer的陨石
    【UOJ 666】古老的显示屏
    【UOJ 222】正方形二分型
    【UOJ 654】虫洞问题
    【UOJ 226】最近公共祖先
    【UOJ 92】有向图的强连通分量
    poj2139 Floyd
    poj1631 dp,最长上升子序列
    poj1065&1548 dp,最长上升子序列,偏序与反偏序
    poj1458(裸LCS)
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/13114620.html
Copyright © 2011-2022 走看看