
# 开发Authorization签名生成机制
您需要开发一个生成Authorization签名的认证机制，云客服需要用您生成的authorization签名来进行鉴权。
在开发前，请您先了解生成Authorization签名的内部规则，如[图1]所示。
图1Authorization签名生成机制的主要规则   
![](https://support.huaweicloud.com/devg-weblite-cec/figure/zh-cn_image_0000001136113022.png)
了解完以上原理与规则后，请您依据以下步骤进行Authorization签名认证机制的开发：
1. 生成SignedHeaders：遍历参与编码的HttpHead里面的Header name。
   
   1. 将Header name都改为小写，即调用lowerCase()函数，SignerUtils工具类请参考[1]，参考如下：
      ```
      private Map<String, String> lowerCaseSignedHeaders(Map<String, String> signedHeaders) {
           if ((null == signedHeaders) || signedHeaders.isEmpty()) {
               throw new IllegalArgumentException("signedHeaders cann't be null.");
           }
           Map<String, String> headers = new HashMap<>(SignerUtils.HASH_MAP_INITIALIZATION_SIZE);
           for (Map.Entry<String, String> e : signedHeaders.entrySet()) {
               String name = e.getKey();
               String value = e.getValue();
               headers.put(name.toLowerCase(Locale.ENGLISH), value.trim());
           }
           return headers; 
      }
      ```
      
   
   2. 将[步骤1-1]中Header name转换后的字符后面追加分割符";"，生成一条记录,注意最后一个字段不追加";"。
   
   3. 将[步骤1-1]中所有记录按照字典排序，然后按照顺序连接成一个大字符串，参考如下。
      ```
      private String appendSignedHeaders(StringBuilder buffer) {
           int start = buffer.length();
            Set<String> headerNames = new TreeSet<>(this.signedHeaders.keySet());
           for (String name : headerNames) {
               buffer.append(name).append(';');
           }
           buffer.deleteCharAt(buffer.length() - 1);
            int end = buffer.length();
           String signedHeadersStr = buffer.substring(start, end);
           return signedHeadersStr; 
      }
      ```
      ![](https://support.huaweicloud.com/devg-weblite-cec/public_sys-resources/note_3.0-zh-cn.png)
      请对以下Header进行编码：
      Content-Length="\*\*\*"
      Content-Type="application/json;charset=UTF-8"
      
   
   
   
   
2. 生成authStringPrefix。
   
   将以下字段用"/"进行拼接authVersion、accessKey、timestamp、SignedHeaders。格式如下：
   ```
   authStringPrefix="auth-v2/{accessKey}/{timestamp}/{SignedHeaders}";
   ```
   ![](https://support.huaweicloud.com/devg-weblite-cec/public_sys-resources/note_3.0-zh-cn.png)
   - auth-v2：鉴权版本号，当期版本为固定字符串"auth-v2"。
   
   - accessKey：第三方系统使用configId（渠道ID）作为唯一标识。
   
   - timestamp：取第三方发起服务时的时间，timestamp为String类型。时间字符串格式化为"yyyy-MM-dd'T'HH:mm:ss.SSS'Z"。
   
   - SignedHeaders：参与编码的HttpHead里面的Header name，由[步骤1]生成。
    
   
   
3. 生成signingKey。
   
   将[步骤2]生成的authStringPrefix按照sha256Hex进行加密，其中密钥SecretKey为第三方系统在渠道配置页面配置的密钥。sha256Hex算法参考如下，SignerUtils工具类请参考[1]：
   ```
   public static String sha256Hex(String key, String toSigned) throws NoSuchAlgorithmException,InvalidKeyException, UnsupportedEncodingException {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(key.getBytes(SignerUtils.CHARSET), "HmacSHA256"));
        String digit = new String(SignerUtils.encodeHex(mac.doFinal(toSigned.getBytes(SignerUtils.CHARSET))));
        return digit; 
   }
   ```
   
   
4. 生成CanonicalHeaders。
   
   ![](https://support.huaweicloud.com/devg-weblite-cec/public_sys-resources/note_3.0-zh-cn.png)
   要求计算的编码规则与SignedHeaders一致，但增加了head value的编码。
   1. 遍历参与编码的HttpHead里面的head name，将Header name都改为小写，即调用lowerCase()函数（请参考[步骤1]）。
   
   2. 调用normalize函数，对刚才转换后的小写字符串进行格式化，PathUtils工具类请参考[2]。
      ```
      /**
        * normalize
        * @param value payload信息
        * @return builder
        */
      public static String normalize(String value) {
           try {
               StringBuilder builder = new StringBuilder(PathUtils.DEFAULT_CAPACIT);
               for (byte b : value.getBytes(PathUtils.CHARSET)) {
                   if (PathUtils.URI_UNRESERVED_CHARACTERS.get(b & 0xFF)) {
                       builder.append((char) b);
                   } else {
                       builder.append(PathUtils.PERCENT_ENCODED_STRINGS[b & 0xFF]);
                   }
               }
               return builder.toString();
           } catch (UnsupportedEncodingException e) {
               throw new RuntimeException(e);
           } 
      }
      ```
      
   
   3. 将[步骤4-1]中的记录按照字典排序，进行排序。
   
   4. 遍历排序后的记录，中间追加字符串"\\n"连接成一个大的字符串，最后一条记录不追加"\\n"。
   
   
   
   
5. 生成canonicalRequest。
   
   将字段"HttpMethod"、"HttpURI"、"SignedHeaders"、"CanonicalHeaders"、"NormalizePath"用"\\n"拼接，最后一条记录不追加"\\n"，PathUtils工具类请参考[2]。。
   ```
   private String canonicalRequest() {
        StringBuilder buffer = new StringBuilder(PathUtils.DEFAULT_CAPACITY);
        buffer.append(this.httpMethod).append(System.lineSeparator());
        buffer.append(this.uri).append(System.lineSeparator());
          this.appendSignedHeaders(buffer);
        buffer.append(System.lineSeparator());
          this.appendCanonicalHeaders(buffer);
        buffer.append(System.lineSeparator());
          if (this.isNotEmpty(this.payload))
         {
            buffer.append(PathUtils.normalize(this.payload));
         }
         return buffer.toString();
    }
   ```
   格式参考如下：
   ```
   CanonicalRequest = $HttpMethod + "\n" + $HttpURI+ "\n" + SignedHeaders($HttpHeaders) + "\n" + CanonicalHeaders ($HttpHeaders) + "\n" + NormalizePath($HttpBody)
   ```
   ![](https://support.huaweicloud.com/devg-weblite-cec/public_sys-resources/note_3.0-zh-cn.png)
   - CanonicalRequest参数说明： $HttpMethod：指HTTPS协议中定义的GET、PUT、POST等请求，必须使用全大写的形式。
     $HttpURI：指接口请求的URI，必须以"/"开头，不以"/"开头的需要补充上，空路径为"/"，样例：/service-cloud/webclient/chat_client/js/newThirdPartyClient.js。
     SignedHeaders：[步骤1]生成的SignedHeaders。
     CanonicalHeaders：[步骤4]生成的CanonicalHeaders。
     NormalizePath：格式化处理后的Body体。
     
   
   - 仅对NormalizePath中的如下参数进行编码： thirdUserName：企业的用户名。
     thirdUserId：企业用户ID。
     tenantSpaceId：企业系统提供的租间ID。
     channelConfigId：企业接入的渠道ID。
     
    
   
   
6. 生成signature。将[步骤5]生成的canonicalRequest再按照sha256Hex进行加密，此处加密的密钥key为[步骤3]生成的signingKey。
7. 生成签名authorization：将[步骤2]生成的authStringPrefix和[步骤6]生成的signature用"/"进行拼接。格式参考如下：
   
   ```
   Authorization:$authStringPrefix/$Signature
   ```
   
   
#### 相关参考
生成Authorization签名的认证机制过程中，涉及到SignerUtils与PathUtils工具类，格式参考如下：
1. SignerUtils
   ```
   import java.nio.charset.StandardCharsets;
   import java.util.HashMap;
   import java.util.Locale;
   import java.util.Map;
   public class SignerUtils {
       private static final int HASH_MAP_INITIALIZATION_SIZE = 5;
       private static final int ONE_CHAR_BITS_NUM = 4;
       private static final String CHARSET = "UTF-8";
       private static final char[] DIGITS_LOWERS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
           'b', 'c', 'd', 'e', 'f'};
       private SignerUtils() {
       }
       private static char[] encodeHex(final byte[] data) {
           final int le = data.length;
           final char[] outs = new char[le << 1];
           for (int i = 0, j = 0; i < le; i++) {
               outs[j++] = SignerUtils.DIGITS_LOWERS[(0xF0 & data[i]) >>> ONE_CHAR_BITS_NUM];
               outs[j++] = SignerUtils.DIGITS_LOWERS[0x0F & data[i]];
           }
           return outs;
       }
   }
   ```
   
2. PathUtils
   ```
   import java.io.UnsupportedEncodingException;
   import java.util.BitSet;
   import java.util.Locale;
   import java.util.concurrent.CompletionException;
   public class PathUtils {
       private static final String CHARSET = "UTF-8";
       private static final int NUM_256 = 256;
       private static final int DEFAULT_CAPACITY = 16;
       private static final BitSet URI_UNRESERVED_CHARACTERS = new BitSet();
       private static final String[] PERCENT_ENCODED_STRINGS = new String[NUM_256];
       static {
           for (int i = 97; i <= 122; i++) {
               PathUtils.URI_UNRESERVED_CHARACTERS.set(i);
           }
           for (int i = 65; i <= 90; i++) {
               PathUtils.URI_UNRESERVED_CHARACTERS.set(i);
           }
           for (int i = 48; i <= 57; i++) {
               PathUtils.URI_UNRESERVED_CHARACTERS.set(i);
           }
           PathUtils.URI_UNRESERVED_CHARACTERS.set(45);
           PathUtils.URI_UNRESERVED_CHARACTERS.set(46);
           PathUtils.URI_UNRESERVED_CHARACTERS.set(95);
           PathUtils.URI_UNRESERVED_CHARACTERS.set(126);
           for (int i = 0; i < PathUtils.PERCENT_ENCODED_STRINGS.length; i++) {
               PathUtils.PERCENT_ENCODED_STRINGS[i] = String.format(Locale.ROOT, "%%%02X", new Object[]{Integer.valueOf(i)});
           }
       }
       private PathUtils() {}
   }
   ```
   
 
