欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

内测之家-安全机制-签名(二),*/@NotBlan

来源: javaer 分享于  点击 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;
    }

  

相关栏目:

用户点评