zoukankan      html  css  js  c++  java
  • 用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下载具体代码~~~

  • 相关阅读:
    python内置的魔术命令(builtin magic commands)
    绘制ROC曲线
    python with语句中的变量有作用域吗?
    Visual Studio上编译ncnn
    loss函数学习笔记
    Install zeal on ubuntu16.04
    cmake方式使用vlfeat
    整理读研期间用过、改进过、写过的代码
    ubuntu下使用matplotlib绘图无法显示中文label
    error: each element of 'ext_modules' option must be an Extension instance or 2-tuple
  • 原文地址:https://www.cnblogs.com/bluesummer/p/8309608.html
Copyright © 2011-2022 走看看