内测之家-安全机制-署名(二)

打印 上一主题 下一主题

主题 967|帖子 967|积分 2901

内测之家-安全机制-署名(一)简要的先容了署名的大致情况
本章先容的是署名的实现细节

HTTP请求参与署名(需要被保护,防止被篡改的信息)的信息布局:
  1. public class HttpSignatureRawData {
  2.     /**
  3.      * 方式
  4.      */
  5.     private String method;
  6.     /**
  7.      * 路径加请求参数
  8.      */
  9.     private String uri;
  10.     /**
  11.      * url QueryString参数
  12.      */
  13.     private MultiValueMap<String, String> parameters;
  14.     /**
  15.      * headers
  16.      */
  17.     private MultiValueMap<String, String> headers;
  18.     /**
  19.      * 数据体
  20.      */
  21.     private byte[] body;
  22.     /**
  23.      * 密钥
  24.      */
  25.     private String secret;
  26.     public void setHeaders(MultiValueMap<String, String> headers, List<String> includeTags){
  27.         if (includeTags != null && includeTags.size() > 0) {
  28.             if (headers != null && headers.size() > 0) {
  29.                 MultiValueMap<String, String> includeHeaders = new LinkedMultiValueMap<>();
  30.                 includeTags.forEach(key -> {
  31.                     final List<String> values = headers.get(key);
  32.                     includeHeaders.put(key, values);
  33.                 });
  34.                 this.headers = includeHeaders;
  35.             }
  36.         }
  37.     }
  38.     public void setParameters(MultiValueMap<String, String> params, List<String> excludeTags) {
  39.         if (params != null && params.size() > 0){
  40.             if (excludeTags != null && excludeTags.size() > 0){
  41.                 excludeTags.forEach(params::remove);
  42.             }
  43.         }
  44.         this.parameters = params;
  45.     }
  46.     public void setParameters(Map<String, String[]> params, List<String> excludeTags){
  47.         if (params == null || params.size() ==0 ){
  48.             return;
  49.         }
  50.         MultiValueMap<String,String> parameters = new LinkedMultiValueMap(8) {};
  51.         params.forEach((item,value) ->{
  52.             parameters.put(item, Arrays.asList(value));
  53.         });
  54.         // 设置排除 参与签名的参数(query)
  55.         Optional.ofNullable(excludeTags).ifPresent(value->{
  56.            parameters.remove(value);
  57.         });
  58.         this.parameters = parameters;
  59.     }
  60. }
复制代码
method: 请求方式(GET、POST、PUT等)
URI:只包罗PATH
parameters: 为 QueryString 的 Key=Value 包罗的信息是需要参与署名的查询参数
headers: 为请求头的Key:Value  包罗的信息是需要参与署名的请求头
body: 请求体
secret: APPKey 对应的密钥, 该信息只参与署名或验签、不会进行传输

构建【署名内容】实现如下:
  1. public class DefaultHttpSignatureContentBuilder implements HttpSignatureContentBuilder {
  2.     //换行符
  3.     private static String LF = "\n";
  4.     //回车换行
  5.     private static String CRLF = "\r\n";
  6.     private static String COLON = ":";
  7.     /** HTTP HEADER是否转换成小写(部分WEB容器中接受到的所有HEADER的KEY都是小写)**/
  8.     private static final boolean HTTP_HEADER_TO_LOWER_CASE = true;
  9.     private HttpSignatureElement httpSignatureElement;
  10.     public DefaultHttpSignatureContentBuilder(HttpSignatureElement httpSignatureElement) {
  11.         this.httpSignatureElement = httpSignatureElement;
  12.     }
  13.     @Override
  14.     public String getResult(HttpSignatureRawData rawData) {
  15.         if (rawData != null) {
  16.             StringBuilder builder = new StringBuilder();
  17.             this.buildMethod(builder, rawData.getMethod());
  18.             this.buildUri(builder, rawData.getUri());
  19.             this.buildParameters(builder, rawData.getParameters());
  20.             this.buildHeaders(builder, rawData.getHeaders());
  21.             this.buildBody(builder, rawData.getBody());
  22.             this.buildSecret(builder, rawData.getSecret());
  23.             return builder.toString();
  24.         }
  25.         return null;
  26.     }
  27.     public HttpSignatureContentBuilder buildUri(StringBuilder builder, String uri) {
  28.         appendItem(builder, uri, this.httpSignatureElement.getUriTag());
  29.         return this;
  30.     }
  31.     public HttpSignatureContentBuilder buildMethod(StringBuilder builder, String method) {
  32.         appendItem(builder, method, this.httpSignatureElement.getMethodTag());
  33.         return this;
  34.     }
  35.     public HttpSignatureContentBuilder buildHeaders(StringBuilder builder, MultiValueMap<String, String> headers) {
  36.         /** 【有】参与加签的请求头 **/
  37.         List<String> includeHeaders = httpSignatureElement.getIncludeHeaderTags();
  38.         if (includeHeaders != null && headers != null && headers.size() > 0) {
  39.             LinkedMultiValueMap candidateHeaders = new LinkedMultiValueMap<>(8);
  40.             for (String includeHeader : includeHeaders) {
  41.                 final List<String> values = headers.get(includeHeader);
  42.                 if (values != null){
  43.                     /** 因为http请求头不区分大小写 ,统一转为小写 **/
  44.                     candidateHeaders.put(HTTP_HEADER_TO_LOWER_CASE ? includeHeader.toLowerCase() : includeHeader, values);
  45.                 }
  46.             }
  47.             /**
  48.              * 形式如下: HeaderKey1.toLowerCase() 因为http请求头不区分大小写
  49.              * String headers = HeaderKey1.toLowerCase() + ":" + HeaderValue1 +"\n"+
  50.              *  HeaderKey2.toLowerCase() + ":" + HeaderValue2 +"\n"+
  51.              *  ... +
  52.              * HeaderKeyN.toLowerCase() + ":" + HeaderValueN + "\n"
  53.              */
  54.             String headerString = LinkStringMultiValueKit.instance.createLinkString(candidateHeaders, LF, COLON, this.httpSignatureElement.getHeadersTag()).toString();
  55.             appendItem(builder, headerString, this.httpSignatureElement.getHeadersTag());
  56.         }
  57.         return this;
  58.     }
  59.     public HttpSignatureContentBuilder buildParameters(StringBuilder builder, MultiValueMap<String, String> parameters) {
  60.         if (parameters != null && parameters.size() > 0){
  61.             MultiValueMap<String, String> candidateParameters = parameters;
  62.             /** 【不】参与加签的 queryString **/
  63.             List<String> excludeQueryItems = httpSignatureElement.getExcludeParameterTags();
  64.             if (excludeQueryItems != null && excludeQueryItems.size() > 0){
  65.                 candidateParameters = new LinkedMultiValueMap<>(parameters);
  66.                 for (String excludeQueryItem : excludeQueryItems) {
  67.                     parameters.remove(excludeQueryItem);
  68.                 }
  69.             }
  70.             String parameterString = LinkStringMultiValueKit.instance.createDefaultLinkString(candidateParameters, this.httpSignatureElement.getParametersTag()).toString();
  71.             appendItem(builder, parameterString, this.httpSignatureElement.getParametersTag());
  72.         }
  73.         return this;
  74.     }
  75.     public HttpSignatureContentBuilder buildBody(StringBuilder builder, byte[] body) {
  76.         if (body != null) {
  77.             String bodyString = new String(body);
  78.             appendItem(builder, bodyString, this.httpSignatureElement.getBodyTag());
  79.         }
  80.         return this;
  81.     }
  82.     public HttpSignatureContentBuilder buildSecret(StringBuilder builder, String secret) {
  83.         appendItem(builder, secret, this.httpSignatureElement.getAppSecretTag());
  84.         return this;
  85.     }
  86.     protected void appendItem(StringBuilder builder, String itemValue, String tag) {
  87.         if (itemValue != null && !itemValue.isEmpty()) {
  88.             builder.append(tag);
  89.             builder.append(COLON);
  90.             builder.append(itemValue);
  91.             builder.append(CRLF);
  92.         }
  93.     }
  94. }
复制代码
署名的对象定义:
  1. @Data
  2. public class SignatureEntity {
  3.     /**
  4.      * 应用的AK
  5.      */
  6.     @NotBlank
  7.     protected String appKey;
  8.     /**
  9.      * 版本
  10.      */
  11.     @NotBlank
  12.     protected String version;
  13.     /**
  14.      * 签名 不参与加签
  15.      */
  16.     @NotBlank
  17.     protected String signature;
  18.     /**
  19.      * unix时间(国际时间 有区域的 毫秒级)
  20.      */
  21.     @NotNull
  22.     protected Long timestamp;
  23.     /**
  24.      * 唯一值n
  25.      * 长度不能超过256个字符
  26.      * 请求唯一标识, appKey+nonce 不能重复,与时间戳结合使用才能起到防重放作用。
  27.      */
  28.     @NotBlank
  29.     @Size(max = 256)
  30.     protected String nonce;
  31.     /**
  32.      *  bizContent 是由 DefaultApiHttpServletSignatureContentDirector->buildRequest 构建而成的
  33.      **/
  34.     @NotBlank
  35.     protected String bizContent;
  36. }
复制代码
验签过程:
  1. public boolean verify(Map<String, String> mapParam, HttpSignatureElement httpSignatureElement, IAppSignatureConfig signatureConfig){
  2.         SignatureResponseCodeEnum.SIGNATURE_CONFIG_NOT_EXIST.assertNotNull(signatureConfig);
  3.         String timeStampString= mapParam.get(httpSignatureElement.getTimestampTag());
  4.         long timeStamp = 0;
  5.         if (StringUtils.isNotBlank(timeStampString)){
  6.             timeStamp = Long.valueOf(timeStampString);
  7.         }
  8.         String signature = mapParam.get(httpSignatureElement.getSignatureTag());
  9.         String nonce = mapParam.get(httpSignatureElement.getNonceTag());
  10.         String contentString = mapParam.get(httpSignatureElement.getBizContentTag());
  11.         /**
  12.          * 纯粹的 时间戳有效性判断,对于过期的,可快速的过滤,从而提高性能
  13.          * 性能损耗(小)
  14.          * 第一步:再验证timestamp是否过期,证明请求是在最近60s被发出的
  15.          **/
  16.         boolean bAccess = this.isExpired(timeStamp, getValidityPeriod());
  17.         SignatureResponseCodeEnum.SIGNATURE_VERIFY_TIMESTAMP_ERROR.assertIsFalse(bAccess);
  18.         //////////////////////////////////////////////////////////////////////////
  19.         /** 获取LinkString **/
  20.         /** 对方的公钥验 **/
  21.         String peerVerifyKey = signatureConfig.fetchVerifyKeyString();
  22.         /** 配置的签名算法类型 **/
  23.         String signType = signatureConfig.fetchAlgorithm();
  24.         /** 根据签名类型,获取签名服务 **/
  25.         SignValidateInterface service = HttpServletSignatureUtils.fetchSignValidateService(signType);
  26.         SignatureResponseCodeEnum.SIGNATURE_TYPE_NOT_SERVICE_SUPPORT.assertNotNull(service);
  27.         /**
  28.          * 通过算法进行校验,
  29.          * 性能损耗(中)
  30.          * 第二步:先验证sign签名是否合理,证明请求参数没有被中途篡改
  31.          **/
  32.         bAccess = service.verify(contentString, CHARSET, signature, peerVerifyKey, signType);
  33.         if (!bAccess){
  34.             logger.error("signedContent:"+ contentString);
  35.         }
  36.         SignatureResponseCodeEnum.SIGNATURE_VERIFY_ERROR.assertIsTrue(bAccess);
  37.         /**
  38.          * 放在第三步, 可以有效的减少 通讯 如(redis 或 zookeeper等 缓存服务)
  39.          * 如 【一直重入提交相同的数据】
  40.          * 性能损耗(高)
  41.          * 第三步:最后验证nonce是否已经有了,证明这个请求不是VALIDITY_PERIOD秒内的重放请求
  42.          * **/
  43.         String appKey = mapParam.get(httpSignatureElement.getAppKeyTag());
  44.         String nonceKey = buildNonceKey(appKey, nonce);
  45.         bAccess = this.verifyNonce(nonceKey, getValidityPeriod()+PERIOD_OFFSET);
  46.         SignatureResponseCodeEnum.SIGNATURE_VERIFY_NONCE_ERROR.assertIsTrue(bAccess);
  47.         return true;
  48.     }
复制代码
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

笑看天下无敌手

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表