开发Authorization签名生成机制
您需要开发一个生成Authorization签名的认证机制,云客服需要用您生成的authorization签名来进行鉴权。
在开发前,请您先了解生成Authorization签名的内部规则,如图1所示。
了解完以上原理与规则后,请您依据以下步骤进行Authorization签名认证机制的开发:
- 生成SignedHeaders:遍历参与编码的HttpHead里面的Header name。
- 将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; }
- 将步骤1-1中Header name转换后的字符后面追加分割符";",生成一条记录,注意最后一个字段不追加";"。
- 将步骤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; }
请对以下Header进行编码:
Content-Length="***"
Content-Type="application/json;charset=UTF-8"
- 将Header name都改为小写,即调用lowerCase()函数,SignerUtils工具类请参考1,参考如下:
- 生成authStringPrefix。
将以下字段用“/”进行拼接authVersion、accessKey、timestamp、SignedHeaders。格式如下:
authStringPrefix="auth-v2/{accessKey}/{timestamp}/{SignedHeaders}";
- auth-v2:鉴权版本号,当期版本为固定字符串“auth-v2”。
- accessKey:第三方系统使用configId(渠道ID)作为唯一标识。
- timestamp:取第三方发起服务时的时间,timestamp为String类型。时间字符串格式化为"yyyy-MM-dd'T'HH:mm:ss.SSS'Z"。
- SignedHeaders:参与编码的HttpHead里面的Header name,由步骤1生成。
- 生成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; }
- 生成CanonicalHeaders。
要求计算的编码规则与SignedHeaders一致,但增加了head value的编码。
- 遍历参与编码的HttpHead里面的head name,将Header name都改为小写,即调用lowerCase()函数(请参考步骤1)。
- 调用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); } }
- 将步骤4-1中的记录按照字典排序,进行排序。
- 遍历排序后的记录,中间追加字符串"\n"连接成一个大的字符串,最后一条记录不追加"\n"。
- 生成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)
- 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中的如下参数进行编码:
thirdUserId:企业用户ID。
tenantSpaceId:企业系统提供的租间ID。
channelConfigId:企业接入的渠道ID。
- CanonicalRequest参数说明:
- 生成signature。将步骤5生成的canonicalRequest再按照sha256Hex进行加密,此处加密的密钥key为步骤3生成的signingKey。
- 生成签名authorization:将步骤2生成的authStringPrefix和步骤6生成的signature用“/”进行拼接。格式参考如下:
Authorization:$authStringPrefix/$Signature
相关参考
生成Authorization签名的认证机制过程中,涉及到SignerUtils与PathUtils工具类,格式参考如下:
- 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; } }
- 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() {} }