内测之家-安全机制-署名(一)简要的先容了署名的大致情况
本章先容的是署名的实现细节
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;
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |