内测之家-安全机制-签名(二),*/@NotBlan
分享于 点击 30307 次 点评:157
内测之家-安全机制-签名(二),*/@NotBlan
内测之家-安全机制-签名(一)简要的介绍了签名的大致情况本章介绍的是签名的实现细节
HTTP请求参与签名(需要被保护,防止被篡改的信息)的信息结构:
public class HttpSignatureRawData { /** * 方式 */ private String method; /** * 路径加请求参数 */ private String uri; /** * url QueryString参数 */ private MultiValueMap<String, String> parameters; /** * headers */ private MultiValueMap<String, String> headers; /** * 数据体 */ private byte[] body; /** * 密钥 */ private String secret; public void setHeaders(MultiValueMap<String, String> headers, List<String> includeTags){ if (includeTags != null && includeTags.size() > 0) { if (headers != null && headers.size() > 0) { MultiValueMap<String, String> includeHeaders = new LinkedMultiValueMap<>(); includeTags.forEach(key -> { final List<String> values = headers.get(key); includeHeaders.put(key, values); }); this.headers = includeHeaders; } } } public void setParameters(MultiValueMap<String, String> params, List<String> excludeTags) { if (params != null && params.size() > 0){ if (excludeTags != null && excludeTags.size() > 0){ excludeTags.forEach(params::remove); } } this.parameters = params; } public void setParameters(Map<String, String[]> params, List<String> excludeTags){ if (params == null || params.size() ==0 ){ return; } MultiValueMap<String,String> parameters = new LinkedMultiValueMap(8) {}; params.forEach((item,value) ->{ parameters.put(item, Arrays.asList(value)); }); // 设置排除 参与签名的参数(query) Optional.ofNullable(excludeTags).ifPresent(value->{ parameters.remove(value); }); this.parameters = parameters; } }method: 请求方式(GET、POST、PUT等)
URI:只包含PATH
parameters: 为 QueryString 的 Key=Value 包含的信息是需要参与签名的查询参数
headers: 为请求头的Key:Value 包含的信息是需要参与签名的请求头
body: 请求体
secret: APPKey 对应的密钥, 该信息只参与签名或验签、不会进行传输
构建【签名内容】实现如下:
public class DefaultHttpSignatureContentBuilder implements HttpSignatureContentBuilder { //换行符 private static String LF = "\n"; //回车换行 private static String CRLF = "\r\n"; private static String COLON = ":"; /** HTTP HEADER是否转换成小写(部分WEB容器中接受到的所有HEADER的KEY都是小写)**/ private static final boolean HTTP_HEADER_TO_LOWER_CASE = true; private HttpSignatureElement httpSignatureElement; public DefaultHttpSignatureContentBuilder(HttpSignatureElement httpSignatureElement) { this.httpSignatureElement = httpSignatureElement; } @Override public String getResult(HttpSignatureRawData rawData) { if (rawData != null) { StringBuilder builder = new StringBuilder(); this.buildMethod(builder, rawData.getMethod()); this.buildUri(builder, rawData.getUri()); this.buildParameters(builder, rawData.getParameters()); this.buildHeaders(builder, rawData.getHeaders()); this.buildBody(builder, rawData.getBody()); this.buildSecret(builder, rawData.getSecret()); return builder.toString(); } return null; } public HttpSignatureContentBuilder buildUri(StringBuilder builder, String uri) { appendItem(builder, uri, this.httpSignatureElement.getUriTag()); return this; } public HttpSignatureContentBuilder buildMethod(StringBuilder builder, String method) { appendItem(builder, method, this.httpSignatureElement.getMethodTag()); return this; } public HttpSignatureContentBuilder buildHeaders(StringBuilder builder, MultiValueMap<String, String> headers) { /** 【有】参与加签的请求头 **/ List<String> includeHeaders = httpSignatureElement.getIncludeHeaderTags(); if (includeHeaders != null && headers != null && headers.size() > 0) { LinkedMultiValueMap candidateHeaders = new LinkedMultiValueMap<>(8); for (String includeHeader : includeHeaders) { final List<String> values = headers.get(includeHeader); if (values != null){ /** 因为http请求头不区分大小写 ,统一转为小写 **/ candidateHeaders.put(HTTP_HEADER_TO_LOWER_CASE ? includeHeader.toLowerCase() : includeHeader, values); } } /** * 形式如下: HeaderKey1.toLowerCase() 因为http请求头不区分大小写 * String headers = HeaderKey1.toLowerCase() + ":" + HeaderValue1 +"\n"+ * HeaderKey2.toLowerCase() + ":" + HeaderValue2 +"\n"+ * ... + * HeaderKeyN.toLowerCase() + ":" + HeaderValueN + "\n" */ String headerString = LinkStringMultiValueKit.instance.createLinkString(candidateHeaders, LF, COLON, this.httpSignatureElement.getHeadersTag()).toString(); appendItem(builder, headerString, this.httpSignatureElement.getHeadersTag()); } return this; } public HttpSignatureContentBuilder buildParameters(StringBuilder builder, MultiValueMap<String, String> parameters) { if (parameters != null && parameters.size() > 0){ MultiValueMap<String, String> candidateParameters = parameters; /** 【不】参与加签的 queryString **/ List<String> excludeQueryItems = httpSignatureElement.getExcludeParameterTags(); if (excludeQueryItems != null && excludeQueryItems.size() > 0){ candidateParameters = new LinkedMultiValueMap<>(parameters); for (String excludeQueryItem : excludeQueryItems) { parameters.remove(excludeQueryItem); } } String parameterString = LinkStringMultiValueKit.instance.createDefaultLinkString(candidateParameters, this.httpSignatureElement.getParametersTag()).toString(); appendItem(builder, parameterString, this.httpSignatureElement.getParametersTag()); } return this; } public HttpSignatureContentBuilder buildBody(StringBuilder builder, byte[] body) { if (body != null) { String bodyString = new String(body); appendItem(builder, bodyString, this.httpSignatureElement.getBodyTag()); } return this; } public HttpSignatureContentBuilder buildSecret(StringBuilder builder, String secret) { appendItem(builder, secret, this.httpSignatureElement.getAppSecretTag()); return this; } protected void appendItem(StringBuilder builder, String itemValue, String tag) { if (itemValue != null && !itemValue.isEmpty()) { builder.append(tag); builder.append(COLON); builder.append(itemValue); builder.append(CRLF); } } }
签名的对象定义:
@Data public class SignatureEntity { /** * 应用的AK */ @NotBlank protected String appKey; /** * 版本 */ @NotBlank protected String version; /** * 签名 不参与加签 */ @NotBlank protected String signature; /** * unix时间(国际时间 有区域的 毫秒级) */ @NotNull protected Long timestamp; /** * 唯一值n * 长度不能超过256个字符 * 请求唯一标识, appKey+nonce 不能重复,与时间戳结合使用才能起到防重放作用。 */ @NotBlank @Size(max = 256) protected String nonce; /** * bizContent 是由 DefaultApiHttpServletSignatureContentDirector->buildRequest 构建而成的 **/ @NotBlank protected String bizContent; }
验签过程:
public boolean verify(Map<String, String> mapParam, HttpSignatureElement httpSignatureElement, IAppSignatureConfig signatureConfig){ SignatureResponseCodeEnum.SIGNATURE_CONFIG_NOT_EXIST.assertNotNull(signatureConfig); String timeStampString= mapParam.get(httpSignatureElement.getTimestampTag()); long timeStamp = 0; if (StringUtils.isNotBlank(timeStampString)){ timeStamp = Long.valueOf(timeStampString); } String signature = mapParam.get(httpSignatureElement.getSignatureTag()); String nonce = mapParam.get(httpSignatureElement.getNonceTag()); String contentString = mapParam.get(httpSignatureElement.getBizContentTag()); /** * 纯粹的 时间戳有效性判断,对于过期的,可快速的过滤,从而提高性能 * 性能损耗(小) * 第一步:再验证timestamp是否过期,证明请求是在最近60s被发出的 **/ boolean bAccess = this.isExpired(timeStamp, getValidityPeriod()); SignatureResponseCodeEnum.SIGNATURE_VERIFY_TIMESTAMP_ERROR.assertIsFalse(bAccess); ////////////////////////////////////////////////////////////////////////// /** 获取LinkString **/ /** 对方的公钥验 **/ String peerVerifyKey = signatureConfig.fetchVerifyKeyString(); /** 配置的签名算法类型 **/ String signType = signatureConfig.fetchAlgorithm(); /** 根据签名类型,获取签名服务 **/ SignValidateInterface service = HttpServletSignatureUtils.fetchSignValidateService(signType); SignatureResponseCodeEnum.SIGNATURE_TYPE_NOT_SERVICE_SUPPORT.assertNotNull(service); /** * 通过算法进行校验, * 性能损耗(中) * 第二步:先验证sign签名是否合理,证明请求参数没有被中途篡改 **/ bAccess = service.verify(contentString, CHARSET, signature, peerVerifyKey, signType); if (!bAccess){ logger.error("signedContent:"+ contentString); } SignatureResponseCodeEnum.SIGNATURE_VERIFY_ERROR.assertIsTrue(bAccess); /** * 放在第三步, 可以有效的减少 通讯 如(redis 或 zookeeper等 缓存服务) * 如 【一直重入提交相同的数据】 * 性能损耗(高) * 第三步:最后验证nonce是否已经有了,证明这个请求不是VALIDITY_PERIOD秒内的重放请求 * **/ String appKey = mapParam.get(httpSignatureElement.getAppKeyTag()); String nonceKey = buildNonceKey(appKey, nonce); bAccess = this.verifyNonce(nonceKey, getValidityPeriod()+PERIOD_OFFSET); SignatureResponseCodeEnum.SIGNATURE_VERIFY_NONCE_ERROR.assertIsTrue(bAccess); return true; }
用户点评