当前位置:   article > 正文

实现开放API接口签名验证_java 火山引擎 api签名

java 火山引擎 api签名

1、新建Certificate类

package com.wise.medical.common.utils;

import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.*;

/**
 * 实现开放API接口签名验证
 * 参考链接:https://www.jianshu.com/p/ad410836587a
 */
public class Certificate {

    public static Logger logger = LoggerFactory.getLogger(LoggerFactory.class);
    /**
     * Certificate构造对象,存储签名相关的参数
     */
    private CertificateBuilder property;
    /**
     * 假设允许客户端和服务端最多能存在15分钟的时间差,同时追踪记录在服务端的nonce集合。
     * 当有新的请求进入时,
     * 首先检查携带的timestamp是否在15分钟内,如超出时间范围,则拒绝,
     * 然后查询携带的nonce,如存在已有集合,则拒绝。否则,记录该nonce,并删除集合内时间戳大于15分钟的nonce
     * (可以使用redis的expire,新增nonce的同时设置它的超时失效时间为15分钟)。
     */
    private String timestamp;
    /**
     * 唯一的随机字符串,用来标识每个被签名的请求
     */
    private String nonce;
    /**
     * 签名
     */
    private String sign;

    public Certificate(CertificateBuilder builder) {
        this.property = builder;
    }

    /**
     * 获取最终请求参数列表
     * 注意:外部不要将timestamp、nonce、accessKey和sign这4个参数添加到方法参数params集合中
     */
    public String getUrlParamsWithSign(final LinkedHashMap<String, String> params) {
        this.sign = getSign(params);

        //6、最终请求Url参数
        params.put("timestamp", this.timestamp);
        params.put("nonce", this.nonce);
        params.put("sign", this.sign);
        params.put("token", property.token);

        List<String> urlParams = new ArrayList<>();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            String strParam = MessageFormat.format("{0}={1}", entry.getKey(), entry.getValue());
            urlParams.add(strParam);
        }
        return String.join("&", urlParams);
    }

    /**
     * 获取签名
     * 注意:外部不要将timestamp、nonce、accessKey和sign这4个参数添加到方法参数params集合中
     */
    public String getSign(final LinkedHashMap<String, String> params) {
        if (this.sign == null || "".equals(this.sign))
        {
            this.sign = createSign(params);
        }
        return this.sign;
    }

    /**
     * 创建签名
     * 注意:外部不要将timestamp、nonce、accessKey和sign这4个参数添加到方法参数params集合中
     */
    private String createSign(final LinkedHashMap<String, String> params) {
        //1、除去空值请求参数
        LinkedHashMap<String, String> newRequestParams = removeEmptyParam(params);

        //2、按照请求参数名的字母升序排列非空请求参数(包含AccessKey),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串
        newRequestParams.put("token", property.token);
        String strUrlParams = parseUrlString(newRequestParams);
        logger.info ("strUrlParams === "+strUrlParams);

        //3、生成当前时间戳timestamp=now和唯一随机字符串nonce=random
        strUrlParams = addUniqueParam(strUrlParams);
        logger.info("strUrlParams2 === "+strUrlParams);
        if (strUrlParams.indexOf ("&") == 0){
            strUrlParams = strUrlParams.substring (1);
        }
        //4、最后拼接上Secretkey得到字符串stringSignTemp
        String stringSignTemp = MessageFormat.format("{0}&SecretKey={1}", strUrlParams, property.secretKey);
        logger.info("stringSignTemp === "+stringSignTemp);

        //5、对stringSignTemp进行MD5运算,并将得到的字符串所有字符转换为大写,得到sign值
        return DigestUtils.md5Hex(stringSignTemp.getBytes(property.charset)).toUpperCase();
    }

    /**
     * 移除空请求参数
     */
    private LinkedHashMap<String, String> removeEmptyParam(final LinkedHashMap<String, String> params) {
        LinkedHashMap<String, String> newParams = new LinkedHashMap<>();
        if (params == null || params.size() <= 0) {
            return newParams;
        }
        for (String key : params.keySet()) {
            String value = params.get(key);
            if (value == null || value.equals("")){
                continue;
            }
            newParams.put(key, value);
        }
        return newParams;
    }

    /**
     * 使用URL键值对的格式(即key1=value1&key2=value2…)将参数列表拼接成字符串
     */
    private String parseUrlString(final Map<String, String> requestMap) {
        List<String> keyList = new ArrayList<>(requestMap.keySet());
        Collections.sort(keyList);

        List<String> entryList = new ArrayList<>();
        for (String key : keyList) {
            String value = requestMap.get(key);
//            try {
//                value = URLEncoder.encode(value, property.charset.name());
//            } catch (UnsupportedEncodingException e) {
//                e.printStackTrace();
//            }
            entryList.add(MessageFormat.format("{0}={1}", key, value));
        }
        return String.join("&", entryList);
    }

    /**
     * timestamp+nonce方案标识每个被签名的请求
     */
    private String addUniqueParam(final String urlParams) {
        if (property.requestTimestamp == null){
            this.timestamp = String.valueOf(System.currentTimeMillis());
        }else{
            this.timestamp = property.requestTimestamp;
        }

        if (property.requestNonce == null){
            this.nonce = UUID.randomUUID().toString().replaceAll("-", "");
        } else{
            this.nonce = property.requestNonce;
        }

        return MessageFormat.format("{0}&timestamp={1}&nonce={2}", urlParams, this.timestamp, this.nonce);
    }

    public static class CertificateBuilder {

        /**
         * 公匙
         */
        private String token;

        /**
         * 私匙
         */
        private String secretKey;

        /**
         * 字符编码
         */
        private Charset charset = StandardCharsets.UTF_8;

        /**
         * 服务端接收到的请求参数
         */
        private String requestTimestamp;

        /**
         * 服务端接收到的请求参数
         */
        private String requestNonce;

        public CertificateBuilder setToken(String token) {
            this.token = token;
            return this;
        }

        public CertificateBuilder setSecretKey(String secretKey) {
            this.secretKey = secretKey;
            return this;
        }

        public CertificateBuilder setCharset(Charset charset) {
            this.charset = charset;
            return this;
        }

        public CertificateBuilder setRequestTimestamp(String requestTimestamp) {
            this.requestTimestamp = requestTimestamp;
            return this;
        }

        public CertificateBuilder setRequestNonce(String requestNonce) {
            this.requestNonce = requestNonce;
            return this;
        }

        public Certificate builder() {
            return new Certificate(this);
        }

    }

}

  • 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
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222

2、编写拦截器

@Component
public class AuthorizationInterceptor implements HandlerInterceptor {

    public static Logger logger = LoggerFactory.getLogger(LoggerFactory.class);

    @Autowired
    private AppTokenService appTokenService;
    @Autowired
    private SignConfig signConfig;

    public static final String USER_KEY = "userId";
    public static final String LOGIN_TOKEN_KEY = "token";
    /**
     *
     * @param request
     * @return
     */
    private LinkedHashMap<String, String> getHeadersInfo(HttpServletRequest request) {
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String> ();
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }

    /**
     *
     * @param request
     * @return
     */
    private LinkedHashMap<String, String> getParameterMap(HttpServletRequest request) {
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
        Enumeration parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String key = (String) parameterNames.nextElement();
            String value = request.getParameter(key);
            map.put(key, value);
        }
        return map;
    }

    /**
     * 功能描述 获取API接口签名实现类
     * @author Hades
     * @date 2020/6/2
     * @param  token token
     * @param  timestamp 当前时间戳
     * @param  nonce 随机字符串
     * @return com.wise.medical.common.utils.Certificate
     */
    private Certificate getCertificate(String token , String timestamp , String nonce) {
        Certificate.CertificateBuilder certificateBuilder = new Certificate.CertificateBuilder ();
        certificateBuilder.setToken (token);
        certificateBuilder.setRequestNonce (nonce);
        certificateBuilder.setRequestTimestamp (timestamp);
        certificateBuilder.setSecretKey (SystemConstant.SECRET_KEY);
        return new Certificate (certificateBuilder);
    }


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Login annotation;
        if(handler instanceof HandlerMethod) {
            annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
        }else{
            return true;
        }
        if(annotation != null){
            return true;
        }

        //从header中获取token
        String token = request.getHeader("token");
        //如果header中不存在token,则从参数中获取token
        if(StringUtils.isBlank(token)){
            token = request.getParameter("token");
        }

        //token为空
        if(StringUtils.isBlank(token)){
            throw new RRException("token不能为空");
        }

        //查询token信息
        AppTokenEntity appTokenEntity = appTokenService.queryByToken(token);
        if(appTokenEntity == null || appTokenEntity.getExpiretime().getTime() < System.currentTimeMillis()){
            throw new RRException("登录已过期,请重新登录",-1);
        }

        //判断签名是否开启
        if (signConfig.getOpen ()){
            logger.info (JSONUtils.beanToJson (getParameterMap(request)));
            logger.info (JSONUtils.beanToJson (getHeadersInfo(request)));
            //获取所有请求参数
            LinkedHashMap<String, String> parameterMap = getParameterMap (request);
            //当前请求时间戳
            String timestamp = parameterMap.get ("timestamp");
            if (timestamp==null){
                throw new RRException ("请求超时");
            }
            long now = System.currentTimeMillis ();
            //判断timestamp是否在规定时间范围内 5分钟 如超出时间范围,则拒绝
            if (now - Long.parseLong (timestamp) >= DateUtils.FIVE_MINUTES){
                throw new RRException ("请求超时");
            }

            //查询携带的随机字符串nonce
            String nonce = parameterMap.get ("nonce");
            if (nonce==null){
                throw new RRException ("请求错误,请检查后再试");
            }
            //从缓存中查找是否有相同请求,,如存在已有集合 ,则拒绝
            String nonceCache = (String) ConcurrentHashMapCacheUtils.getCache(nonce);
            if (nonceCache != null){
                throw new RRException ("请求错误,请检查后再试");
            }else {
                // 否则,记录该nonce,并删除集合内时间戳大于5分钟的nonce,新增nonce的同时设置它的超时失效时间为5分钟
                ConcurrentHashMapCacheUtils.setCache(nonce,nonce,System.currentTimeMillis());
            }

            //获取签名
            String sign = parameterMap.get ("sign");
            if (sign==null){
                throw new RRException ("sign为空");
            }
            Certificate certificate = getCertificate (token , timestamp , nonce);

            //删除timestamp、nonce、token和sign这4个参数
            parameterMap.remove ("timestamp");
            parameterMap.remove ("nonce");
            parameterMap.remove ("sign");
            if (parameterMap.get (LOGIN_TOKEN_KEY)!=null){
                parameterMap.remove ("token");
            }
            //拼接密钥SecretKey
            String signStr = certificate.getSign (parameterMap);
            logger.info (signStr);
            //签名加密进行比对
            if (!sign.equals (signStr)){
                throw new RRException ("请求参数被篡改");
            }
        }

        appTokenService.updateExpireTime(appTokenEntity);
        return true;
    }
}
  • 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
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/68977
推荐阅读
相关标签
  

闽ICP备14008679号