1. 程式人生 > >用Token令牌維護微服務之間的通信安全的實現

用Token令牌維護微服務之間的通信安全的實現

count idc 數據 username cto blue 網絡 api bject

原文:用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下載具體代碼~~~

用Token令牌維護微服務之間的通信安全的實現