用Token令牌维护微服务之间的通信安全的实现

原文:用Token令牌维护微服务之间的通信安全的实现

在微服务架构中,如果忽略服务的安全性,任由接口暴露在网络中,一旦遭受攻击后果是不可想象的、

保护微服务键安全的常见方案有:1.JWT令牌(token) 2.双向SSL 3.OAuth 2.0 等

本文主要介绍使用Token的实现方式

源码地址:https://github.com/Mike-Zrw/TokenApiAuth

基本流程:

上图中有两个服务,服务A和服务B,我们模拟的是服务A来调用服务B的过程,也可以反过来让服务B来调用服务A。

整个流程简单来说只有两步

  • 获取token
  • 携带token请求数据

详细流程为

  1. 客户端请求服务B
  2. 客户端检测本地缓存是否有服务B的token缓存
  3. 客户端检测本地不存在对应的token缓存或者token缓存已超时,则调用接口重新获取token
  4. 客户端调用接口获取token,参数为:时间戳+请求身份标识10位+guid,用rsa非对称加密
  5. 服务B获取客户端获取token的请求,用ras密钥解密客户端的参数,验证请求是否超时以及标识是否有效
  6. 服务端验证客户端参数有效则生成token,返回给客户端,token为一个包含了用户名称,过期时间等字段的加密字符串
  7. 客户端接收到token将token存入本地缓存
  8. 客户端将加密的token放入HTTP header中像服务端请求获取数据
  9. 服务端验证客户端的HTTP header中的token信息是否有效,如果有效,则成功返回数据

获取token

服务端会提供一个产生token的接口供客户端来调用,而对于调用该接口的请求同样需要认证,否则岂不是所有人都可以随意调用该接口来生成token了。

我的思路是每个客户端会有一个权限标识,可以是一样的。然后将权限,时间戳和一个随机数组成一个字符串,然后将该字符串以非对称加密。加密后的字符就是调用接口的参数了

在token生成的服务端,会解密客户端传来的数据,并进行权限及时间的校验,验证通过就会生成一个token,该token包含了用户信息及过期时间等数据,然后用HA256加密返回给客户端

一个token包含的结构如下

public class TokenClaims
{
    /// <summary>
    /// token的发行者
    /// </summary>
    public string Iss { get; set; }
    /// <summary>
    /// 用户权限
    /// </summary>
    public string Role { get; set; }
    /// <summary>
    /// 用户名
    /// </summary>
    public string Usr { get; set; }
    /// <summary>
    /// 签发时间 秒,时间点
    /// </summary>
    public long Iat { get; set; }
    /// <summary>
    /// 到期时间 秒,时间点
    /// </summary>
    public long Exp { get; set; }
    /// <summary>
    /// 唯一标识
    /// </summary>
    public string SingleStr { get; set; }
}

其中用户名是服务端生成的,服务端会将该用户名作为键,将该token存储到缓存中。 所以对于每一个请求都会生成一个唯一的用户名

    public static TokenResult MakeToken(string RequestParam, string PrimaryKey = null)
    {
        try
        {
            dynamic p = JsonConvert.DeserializeObject(RequestParam);
            string RequestAuth = p.RequestAuth;//请求人信息
            string DesAuth;//解密后的author
            if (PrimaryKey == null)
                DesAuth = RSAHelper.Decrypt(RequestAuth, Config_PrimaryKey);
            else
                DesAuth = RSAHelper.Decrypt(RequestAuth, PrimaryKey);

            #region 请求历史是否有重复
            if (MakeTokenParamHistory.Contains(DesAuth))
            {
                ToolFactory.LogHelper.Info("生成token身份验证失败:该请求的字符串与之前重复:" + DesAuth);
                return new TokenResult() { Success = false, Error_Message = "请求数据非法" };
            }
            MakeTokenParamHistory.Insert(0, DesAuth);
            if (MakeTokenParamHistory.Count > 1000)
                MakeTokenParamHistory.RemoveRange(1000, MakeTokenParamHistory.Count - 1000);
            #endregion

            string ReqAuthId = DesAuth.Substring(DesAuth.Length - 46, 10);//请求人身份标识
            long reqTimespan = long.Parse(DesAuth.Substring(0, DesAuth.Length - 46));  //客户端请求时间秒数

            if (!ValidTokenAuth(ReqAuthId))
            {
                ToolFactory.LogHelper.Info("生成token身份验证失败:DesAuth" + DesAuth);
                return new TokenResult() { Success = false, Error_Message = "身份验证失败" };
            }

            if ((TimeHelper.GetTimeSecond() - reqTimespan) > ReqToken_OverTime)
            {
                ToolFactory.LogHelper.Info("生成token请求时间超时:DesAuth" + DesAuth);
                return new TokenResult() { Success = false, Error_Message = "请求时间超时" };
            }
            string uname = TokenBuilder.CreateUserName(ReqAuthId);
            long TokenOverTime = Token_OverTime;
            if (AuthMapOverTime != null && AuthMapOverTime.ContainsKey(ReqAuthId))
                TokenOverTime = AuthMapOverTime[ReqAuthId];
            string tokenStr = TokenBuilder.MakeToken(uname, ReqAuthId, TokenOverTime);
            ToolFactory.LogHelper.Notice("生成token:" + tokenStr);
            ToolFactory.CacheHelper.SetCache("ServiceTokenCacheKey_" + uname, tokenStr, TimeSpan.FromSeconds(TokenOverTime + 30)); //多存30秒,用于判断token的错误类型
            return new TokenResult() { Success = true, Token = tokenStr }; ;
        }
        catch (Exception ex)
        {
            ToolFactory.LogHelper.Error("生成token出现异常", ex);
            return new TokenResult() { Success = false, Error_Message = "错误的请求:" + ex.Message };
        }
    }

请求数据

对于携带token的请求,我将token放在http的header中,尽量减少验证对于业务代码的侵入性。

服务端将token取出,并或得token中存储的用户名,然后将服务端缓存的数据取出来判断该token是否有效

    /// <summary>
    /// 验证客户端发来的token是否有效
    /// </summary>
    /// <param name="header"></param>
    /// <returns></returns>
    public static ValidTokenResult ValidClientToken(HttpRequestHeaders header)
    {
        if (header.Authorization == null || header.Authorization.Parameter == null)
        {
            return new ValidTokenResult() { Success = false, Message = "not exit token" };
        }
        string tokenStr = header.Authorization.Parameter;
        //ToolFactory.LogHelper.Notice("接收到带token的请求:" + tokenStr);
        TokenClaims tcParam = TokenBuilder.DecodeToken(tokenStr);
        TokenClaims tcCache = TokenBuilder.DecodeToken(ToolFactory.CacheHelper.GetCache<string>("ServiceTokenCacheKey_" + tcParam.Usr));
        if (tcCache != null)
        {
            if (TokenIsTimeLoss(tcCache.Exp))
            {
                ToolFactory.LogHelper.Info("token过时,token:" + tokenStr);
                return new ValidTokenResult() { Success = false, Message = "token过时" };
            }
            else if (tcCache.SingleStr != tcParam.SingleStr)
            {
                ToolFactory.LogHelper.Info("token不正确,token:" + tokenStr);
                return new ValidTokenResult() { Success = false, Message = "token不正确" };
            }
            else
            {
                return new ValidTokenResult() { Success = true };
            }
        }
        else
        {
            ToolFactory.LogHelper.Info("ValidClientToken未授权的用户,token:" + tokenStr);
            return new ValidTokenResult() { Success = false, Message = "未授权的用户" };
        }
    }

整个验证框架的主要流程大概就是这样,当然还有很多细节,比如缓存的刷新,请求超时配置等等,有兴趣的可以到github下载具体代码~~~

原文地址:https://www.cnblogs.com/lonelyxmas/p/10336468.html

时间: 2024-11-10 11:54:40

用Token令牌维护微服务之间的通信安全的实现的相关文章

JHipster技术栈定制 - 基于UAA的微服务之间安全调用

本文通过代码实例演示如何通过UAA实现微服务之间的安全调用. uaa: 身份认证服务,同时也作为被调用的资源服务.服务端口9999. microservice1: 调用uaa的消费者服务,服务端口8081. 1 准备工作 1.1 工程目录 --| appstack |-- uaa |-- microservice1 1.2 启动相关组件 为了简单起见,这里都使用容器启动相关组件,需要2个镜像,最好提前下载好. jhipster/jhipster-registry:v4.0.0 mysql:5 a

spring cloud实战与思考(三) 微服务之间通过fiegn上传一组文件(下)

需求场景: 用户调用微服务1的接口上传一组图片和对应的描述信息.微服务1处理后,再将这组图片上传给微服务2进行处理.各个微服务能区分开不同的图片进行不同处理. 上一篇博客已经讨论了在微服务之间传递一组图片和对应参数的解决方案.现在来看看如何对组内文件进行区分.当前项目中使用了"commons-fileupload"和"feign-form"两个库进行文件传输. "commons-fileupload"库可以将http request转换成&quo

Spring Cloud下使用Feign Form实现微服务之间的文件上传

背景 ? Spring Cloud现在已经被越来越多的公司采用了,微服务架构比传统意义上的单服务架构从复杂度上多了很多,出现了很多复杂的场景.比如,我们的产品是个app,支持第三方登录功能,在手机端调用第三方授权接口之后,返回了用户的相关信息,比如open_id,性别,头像等.这些信息我们需要保存在我们服务器上,当时针对头像是应该保存图片的url还是图片本身发生了歧义,在一番讨论之后,得出的结果是,我们需要通过url将图片下载到我们本地,然后调用我们自己的文件微服务中上传功能保存起来. 工具 I

spring cloud实战与思考(二) 微服务之间通过fiegn上传多个文件1

需求场景: 微服务之间调用接口一次性上传多个文件. 上传文件的同时附带其他参数. 多个文件能有效的区分开,以便进行不同处理. Spring cloud的微服务之间接口调用使用Feign.原装的Feign不支持文件的传输.需要借助"Feign-form"库才行.但是貌似"Feign-form"库(至少是3.0.3版本)只支持单文件上传.在接口中使用多文件参数时会报异常: feign.codec.EncodeException: class [Lorg.springfr

Spring Cloud微服务安全实战_6-1_微服务之间的通讯安全之概述

到目前为止已经实现了一个基于微服务的,前后端分离(这里我用的jquery做的,并不是真的前后端分离,因为我不会vue和angular所以没用)的架构.在网关上做了限流.认证.审计.授权等安全机制,在前端应用上也做了SSO单点登录, 现在的架构存在的问题是: 1,在网关做限流. 在网关上做限流是有问题的,比如订单服务限流是100,库存服务限流也是100,订单服务又调了库存服务.如果网关上给订单转了100个请求,给库存转了100个请求,订单又调了库存,这时候库存就同时接到了200个请求,库存服务就可

SpringBoot+SpringCloud实现登录用户信息在微服务之间的传递

实现思路: 1:准备一个ThreadLocal变量,供线程之间共享. 2:每个微服务对所有过来的Feign调用进行过滤,然后从请求头中获取User用户信息,并存在ThreadLocal变量中. 3:每个微服务在使用FeignClient调用别的微服务时,先从ThreadLocal里面取出user信息,并放在request的请求头中. 4:封装为一个注解,在启动类上标记即可. 代码样例: 1:ThreadLocal工具类 :UserInfoContext package com.test.domi

spring cloud中微服务之间的调用以及eureka的自我保护机制

上篇讲了spring cloud注册中心及客户端的注册,所以这篇主要讲一下服务和服务之间是怎样调用的 不会搭建的小伙伴请参考我上一篇博客:idea快速搭建spring cloud-注册中心与注册 基于上一篇的搭建我又自己搭建了一个客户端微服务: 所以现在有两个微服务,我们所实现的就是微服务1和微服务2之间的调用 注册中心就不用多说了,具体看一下两个微服务 application.yml配置也不用说了,不知道怎么配置的请参考我上篇博客 在project-solr中的constroller中: @R

【转】SpringBoot+SpringCloud实现登录用户信息在微服务之间的传递

实现思路: 1:准备一个ThreadLocal变量,供线程之间共享. 2:每个微服务对所有过来的Feign调用进行过滤,然后从请求头中获取User用户信息,并存在ThreadLocal变量中. 3:每个微服务在使用FeignClient调用别的微服务时,先从ThreadLocal里面取出user信息,并放在request的请求头中. 4:封装为一个注解,在启动类上标记即可. 代码样例: 1:ThreadLocal工具类 :UserInfoContext package com.test.domi

微服务之间的通讯安全(六)-Sentinel入门之注解及熔断降级

1.Sentinel注解支持 在学习熔断降级之前,我们先来看一下Sentinel的注解支持,我们使用spring-cloud-starter-alibaba-sentinel依赖,无需额外配置即可使用@SentinelResource注解定义资源. @SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项.常用属性如下: value:资源名称,必需项(不能为空): blockHandler / blockHandlerClass: blockHandle