• apollo源码分析过程:构造apollo签名请求


    apollo 1.6之后的秘钥访问原理

    参考:
    秘钥访问的通用原理:https://blog.csdn.net/qq_25595025/article/details/82627349
    apollo秘钥访问的原理:https://blog.csdn.net/qq_38385659/article/details/105292458


    2020年3月份,携程发布了apollo配置中心1.6.0版本,这个版本一大亮点就是增加了密钥的验证以及管理功能,也就是说客户端必须用密钥对http请求签名才可以访问配置信息,这样一来,不但提高了配置中心的安全性,也让配置中心部署到公共环境成为可能。

    一、总体源码分析

    apolloapollo-configservicesrcmainjavacomctripframeworkapolloconfigservicefilterClientAuthenticationFilter.java
    55-74行:

        if (!CollectionUtils.isEmpty(availableSecrets)) {
          String timestamp = request.getHeader(Signature.HTTP_HEADER_TIMESTAMP);
          String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
    
          // check timestamp, valid within 1 minute
          if (!checkTimestamp(timestamp)) {
            logger.warn("Invalid timestamp. appId={},timestamp={}", appId, timestamp);
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "RequestTimeTooSkewed");
            return;
          }
    
          // check signature
          String uri = request.getRequestURI();
          String query = request.getQueryString();
          if (!checkAuthorization(authorization, availableSecrets, timestamp, uri, query)) {
            logger.warn("Invalid authorization. appId={},authorization={}", appId, authorization);
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
            return;
          }
        }
    

      

    秘钥安全访问机制作了2个方面的检查:
    1、时间戳检查。前后不超过一分钟。header头中的键名Timestamp。
    2、签名检查。secret、时间戳、uri、query作为入参,计算出签名验证client端的签名。header头中的键名Authorization。

    二、时间戳相关源码分析


    apolloapollo-configservicesrcmainjavacomctripframeworkapolloconfigservicefilterClientAuthenticationFilter.java

    56行:# 说明服务器端从请求体的header中获取时间戳。

    String timestamp = request.getHeader(Signature.HTTP_HEADER_TIMESTAMP);   
    

      

    87行:# 说明时间戳以毫秒为单位。

    requestTimeMillis = Long.parseLong(timestamp);
    

      

    apolloapollo-coresrcmainjavacomctripframeworkapollocoresignatureSignature.java

    20行:# 说明header中时间戳的键名为Timestamp。

    public static final String HTTP_HEADER_TIMESTAMP = "Timestamp";
    

      

    构造请求时间戳:

    在header中添加一个字段,值为毫秒时间戳,且不超过最近一分钟。

    Timestamp: 1609913782428


    三、签名相关源码分析

    apolloapollo-configservicesrcmainjavacomctripframeworkapolloconfigservicefilterClientAuthenticationFilter.java

    96-114行:

      private boolean checkAuthorization(String authorization, List<String> availableSecrets,
          String timestamp, String path, String query) {
    
        String signature = null;
        if (authorization != null) {
          String[] split = authorization.split(":");
          if (split.length > 1) {
            signature = split[1];
          }
        }
    
        for (String secret : availableSecrets) {
          String availableSignature = accessKeyUtil.buildSignature(path, query, timestamp, secret);
          if (Objects.equals(signature, availableSignature)) {
            return true;
          }
        }
        return false;
      }
    

      

    1、client端传来的签名值中有冒号,冒号后的一段为签名值Sign。
    2、服务端为了校验签名的计算签名availableSignature值,是由path、query、timestamp、secret这四个入参计算而得。


    apolloapollo-configservicesrcmainjavacomctripframeworkapolloconfigserviceutilAccessKeyUtil.java

    51-58行:

      public String buildSignature(String path, String query, String timestampString, String secret) {
        String pathWithQuery = path;
        if (!Strings.isNullOrEmpty(query)) {
          pathWithQuery += "?" + query;
        }
    
        return Signature.signature(timestampString, pathWithQuery, secret);
      }
    

      

    先将path和query中间用问号?拼接为pathWithQuery后,再计算timestamp、pathWithQuery、secret这三个入参的签名。


    apolloapollo-coresrcmainjavacomctripframeworkapollocoresignatureSignature.java

    17行:# header中签名的值,格式为"Apollo xx:yy"。

    private static final String AUTHORIZATION_FORMAT = "Apollo %s:%s";
    

      

    apolloapollo-coresrcmainjavacomctripframeworkapollocoresignatureSignature.java

    22-25行:

      public static String signature(String timestamp, String pathWithQuery, String secret) {
        String stringToSign = timestamp + DELIMITER + pathWithQuery;
        return HmacSha1Utils.signString(stringToSign, secret);
      }
    

      

    先将timestamp和pathWithQuery中间用换行符" "拼接成stringToSign后,再计算stringToSign、secret这两个入参的签名。

    35行:# 签名字段Authorization的构造方法,值中含appId和signature

    headers.put(HttpHeaders.AUTHORIZATION, String.format(AUTHORIZATION_FORMAT, appId, signature));
    

      

    apolloapollo-coresrcmainjavacomctripframeworkapollocoresignatureHmacSha1Utils.java

    13-31行:

    public class HmacSha1Utils {
    
      private static final String ALGORITHM_NAME = "HmacSHA1";
      private static final String ENCODING = "UTF-8";
    
      public static String signString(String stringToSign, String accessKeySecret) {
        try {
          Mac mac = Mac.getInstance(ALGORITHM_NAME);
          mac.init(new SecretKeySpec(
              accessKeySecret.getBytes(ENCODING),
              ALGORITHM_NAME
          ));
          byte[] signData = mac.doFinal(stringToSign.getBytes(ENCODING));
          return BaseEncoding.base64().encode(signData);
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException | InvalidKeyException e) {
          throw new IllegalArgumentException(e.toString());
        }
      }
    }
    

      

    signString方法实现了对stringToSign和secret的合并签名。依赖了maven中的一些google算法类。


    构造请求时间戳:
    在header中添加一个字段,值为带标识的sign,且值中含义冒号:。
    Authorization: Apollo xx:yy。
    xx为appId
    yy为signature

     四、构造签名请求的关键参数:java实现

     =================优美的分割线1:java构造参数=================

    apolloapollo-coresrc estjavacomctripframeworkapollocoresignatureSignatureTest.java

    public void testSignature() 方法和方法体,用如下代码替换

        @Test
        public void testSignature() {
            // header头中两个关键字段的构造格式
            final String TIMESTAMP_FORMAT = "Timestamp: %s";
            final String AUTHORIZATION_FORMAT = "Authorization: Apollo %s:%s";
    
            //构造时间戳
            long requestTimeMillis = System.currentTimeMillis();
            String Timestamp_str = String.format(TIMESTAMP_FORMAT, requestTimeMillis);
            System.out.println(Timestamp_str);
    
            //构造含有签名的字符串
            String timestamp = Long.toString(requestTimeMillis);
            String pathWithQuery = "/configs/dev01/saas/application";
            String appid = "dev01";
            String secret = "9c394bd3beef482e933e27225c740902";
    
            String actualSignature = Signature.signature(timestamp, pathWithQuery, secret);
            String Authorization_str = String.format(AUTHORIZATION_FORMAT, appid, actualSignature);
            System.out.println(Authorization_str);
    
            String expectedSignature = "EoKyziXvKqzHgwx+ijDJwgVTDgE=";
    //        assertEquals(expectedSignature, actualSignature);
            assertEquals("EoKyziXvKqzHgwx+ijDJwgVTDgE=", "EoKyziXvKqzHgwx+ijDJwgVTDgE=");
        }
    

      

    运行该签名测试:动态输出

    Timestamp: 1609924706320

    Authorization: Apollo dev01:TIaxkWvnay6pxVw1p+vyi0f2FMs=

     五、构造签名请求的关键参数:python2实现

     =================优美的分割线2:python2构造参数=================

    python2构造header中的两个关键参数

    实现如下:

    # -*- coding: utf-8 -*-
    import time
    import hmac
    from hmac import new as hmac
    from hashlib import sha1
    
    
    # 哈希算法
    def hash_hmac(secret, stringToSign):
        '''哈希算法
        :param secret: 秘钥。保密的值,不要在网络中传输。
        :param stringToSign: 待哈希的字串
        :return: 哈希值
        '''
        return str(hmac(secret, stringToSign, sha1).digest().encode('base64')[:-1])
    
    
    # 构造时间戳
    def get_timestamp_ms():
        # 1、构造毫秒级的时间戳
        ms = int(round(time.time() * 1000))  # 毫秒级时间戳
        # print(timestamp_millis)
        return ms
    
    
    # 构造签名
    def signature(timestamp, pathWithQuery, secret):
        '''构造签名
        :param timestamp: 毫秒级时间戳
        :param pathWithQuery: 待签名字串
        :param secret: 签名使用的秘钥
        :return: 签名
        '''
        # 2、构造签名
        # 2.1 构造待签名字串
        stringToSign = str(timestamp) + "
    " + pathWithQuery  # 待签名字串
    
        # 2.2 计算出一个新的签名
        secret = "9c394bd3beef482e933e27225c740902"  # 秘钥。保密的值,不要在网络中传输。
        sign = hash_hmac(secret, stringToSign)  # 通过哈希算法,输出一个新签名
        # print(sign)
    
        return sign
    
    
    # 构造header中两个关键的键值对
    def build_some_header(timestamp_str, appid, sign):
        '''构造header中两个关键的键值对
        :param timestamp_str:毫秒级时间戳
        :param appid:项目名称
        :param sign:签名
        :return:
        '''
        # 3、构造header中两个关键的键值对
        # 3.1 header头中两个关键字段的构造格式
        TIMESTAMP_FORMAT = "Timestamp: %s"
        AUTHORIZATION_FORMAT = "Authorization: Apollo %s:%s"
    
        # 3.2 输出header构建结果
        header_timestamp_str = TIMESTAMP_FORMAT % timestamp_str
        header_authorization_str = AUTHORIZATION_FORMAT % (appid, sign)
        print(header_timestamp_str)
        print(header_authorization_str)
    
    
    if __name__ == '__main__':
        pathWithQuery = "/configs/dev01/saas/application"
        secret = "9c394bd3beef482e933e27225c740902"  # 秘钥。保密的值,不要在网络中传输。
        appid = "dev01"
    
        # 1、构造毫秒级的时间戳
        timestamp_millis = get_timestamp_ms()
    
        # 2、构造签名
        sign = signature(timestamp_millis, pathWithQuery, secret)
    
        # 3、构造header中两个关键的键值对
        # 3.1 header头中两个关键字段的构造格式
        TIMESTAMP_FORMAT = "Timestamp: %s"
        AUTHORIZATION_FORMAT = "Authorization: Apollo %s:%s"
        # 3.2 输出header构建结果
        header_timestamp_str = TIMESTAMP_FORMAT % timestamp_millis
        header_authorization_str = AUTHORIZATION_FORMAT % (appid, sign)
        print(header_timestamp_str)
        print(header_authorization_str)
    

      

    输出(动态产生的,时间戳有效期1min)

    Timestamp: 1609924288391
    Authorization: Apollo dev01:Dj/ep/mZB/AMnL88Qp7mJx3cBcU=

    通过渗透测试工具burp测试:

     获得正确响应,说明构造有效

    六、构造签名请求的关键参数:python3实现

    =================优美的分割线3:python3构造参数=================

    python3构造header中的两个关键参数

    和python2的区别是哈希算法函数hash_hmac()不一样

    python3代码如下:

    # -*- coding: utf-8 -*-
    import time
    import base64
    import hmac
    from hashlib import sha1
    
    
    # 哈希算法
    def hash_hmac(secret, stringToSign):
        '''哈希算法
        :param secret: 秘钥。保密的值,不要在网络中传输。
        :param stringToSign: 待哈希的字串
        :return: 哈希值
        '''
        ENCODING = "utf-8"
        # 编码为字节流
        secret_bytes = secret.encode(ENCODING)
        stringToSign_bytes = stringToSign.encode(ENCODING)
        hmac_sha1 = hmac.new(
            secret_bytes,
            stringToSign_bytes,
            sha1,
        )
        # 以二进制的字节流返回。如:b'xcaxa2xd0xe6x1cgxca?eOSmxcbx9bx92xdexb0xdah
    '
        hmac_sha1_bin_bytes = hmac_sha1.digest()
        # print(hmac_sha1_bin_bytes)
    
        # 将二进制的字节流按照base64规则编码,返回base64的字节流。如:b'yqLQ5hxnyj9lT1Nty5uS3rDaaA0='
        hmac_sha1_base64_bytes = base64.b64encode(hmac_sha1_bin_bytes)
        # print(hmac_sha1_base64_bytes)
    
        # 返回哈希值。base64版,如:yqLQ5hxnyj9lT1Nty5uS3rDaaA0=
        hmac_sha1_base64 = hmac_sha1_base64_bytes.decode(ENCODING)
        # print(hmac_sha1_base64)
    
        return hmac_sha1_base64
    
    
    # 构造时间戳
    def get_timestamp_ms():
        # 1、构造毫秒级的时间戳
        ms = int(round(time.time() * 1000))  # 毫秒级时间戳
        # print(timestamp_millis)
        return ms
    
    
    # 构造签名
    def signature(timestamp, pathWithQuery, secret):
        '''构造签名
        :param timestamp: 毫秒级时间戳
        :param pathWithQuery: 待签名字串
        :param secret: 签名使用的秘钥
        :return: 签名
        '''
        # 2、构造签名
        # 2.1 构造待签名字串
        stringToSign = str(timestamp) + "
    " + pathWithQuery  # 待签名字串
    
        # 2.2 计算出一个新的签名
        sign = hash_hmac(secret, stringToSign)  # 通过哈希算法,输出一个新签名
        # print(sign)
    
        return sign
    
    
    # 构造header中两个关键的键值对
    def build_some_header(timestamp_str, appid, sign):
        '''构造header中两个关键的键值对
        :param timestamp_str:毫秒级时间戳
        :param appid:项目名称
        :param sign:签名
        :return:
        '''
        # 3、构造header中两个关键的键值对
        # 3.1 header头中两个关键字段的构造格式
        TIMESTAMP_FORMAT = "Timestamp: %s"
        AUTHORIZATION_FORMAT = "Authorization: Apollo %s:%s"
    
        # 3.2 输出header构建结果
        header_timestamp_str = TIMESTAMP_FORMAT % timestamp_str
        header_authorization_str = AUTHORIZATION_FORMAT % (appid, sign)
        print(header_timestamp_str)
        print(header_authorization_str)
    
    
    if __name__ == '__main__':
        pathWithQuery = "/configs/dev01/saas/application"
        secret = "9c394bd3beef482e933e27225c740902"  # 秘钥。保密的值,不要在网络中传输。
        appid = "dev01"
    
        # 1、构造毫秒级的时间戳
        timestamp_millis = get_timestamp_ms()
    
        # 2、构造签名
        sign = signature(timestamp_millis, pathWithQuery, secret)
    
        # 3、构造header中两个关键的键值对
        # 3.1 header头中两个关键字段的构造格式
        TIMESTAMP_FORMAT = "Timestamp: %s"
        AUTHORIZATION_FORMAT = "Authorization: Apollo %s:%s"
        # 3.2 输出header构建结果
        header_timestamp_str = TIMESTAMP_FORMAT % timestamp_millis
        header_authorization_str = AUTHORIZATION_FORMAT % (appid, sign)
        print(header_timestamp_str)
        print(header_authorization_str)
    

      

    输出:

    Timestamp: 1609930548812
    Authorization: Apollo dev01:qRegdutePskKrlm6byTFaPeNebQ=

  • 相关阅读:
    2017暑期集训Day 1
    17-06-28模拟赛
    17-06-26模拟赛
    平衡树学习笔记
    指针学习笔记
    17-06-14模拟赛
    17-06-11模拟赛
    17-06-02模拟赛
    17-05-31模拟赛
    培训补坑(day1:最短路&two-sat)
  • 原文地址:https://www.cnblogs.com/andy9468/p/14241284.html
Copyright © 2020-2023  润新知