内测之家-安全机制-签名(二)
内测之家-安全机制-签名(一)简要的介绍了签名的大致情况
本章介绍的是签名的实现细节
HTTP请求参与签名(需要被保护,防止被篡改的信息)的信息结构:
method: 请求方式(GET、POST、PUT等)
URI:只包含PATH
parameters: 为 QueryString 的 Key=Value 包含的信息是需要参与签名的查询参数
headers: 为请求头的Key:Value 包含的信息是需要参与签名的请求头
body: 请求体
secret: APPKey 对应的密钥, 该信息只参与签名或验签、不会进行传输
构建【签名内容】实现如下:
本章介绍的是签名的实现细节
HTTP请求参与签名(需要被保护,防止被篡改的信息)的信息结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | 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; } } |
URI:只包含PATH
parameters: 为 QueryString 的 Key=Value 包含的信息是需要参与签名的查询参数
headers: 为请求头的Key:Value 包含的信息是需要参与签名的请求头
body: 请求体
secret: APPKey 对应的密钥, 该信息只参与签名或验签、不会进行传输
构建【签名内容】实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | 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); } } } |
签名的对象定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | @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; } |
验签过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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 ; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)