当前位置:   article > 正文

Sign签名计算、验签实例(对外提供接口)_signm3

signm3

对外提供接口签名sign计算、验签实例

一、简述

1、对外提供接口,为保护参数不被修改,保护数据的安全性,需要在客户端调用时接口添加签名、服务端对接口进行验签;
2、本实例中authKey是不参与通信,整个过程中authKey是不参与通信的,所以只要保证authKey不泄露,即使请求参数被其他原因泄露,请求也不会被伪造。

二、签名设计及计算过程

1.己方系统提供
clientId: 由于己方系统可能会对外不同角色方提供接口(第三方系统app、第三方系统PC、定时任务调用等),所以针对不同角色类型的外部系统做了配置化处理,
每个clientId对应某一外部角色类型方,并在后台库中存储了该clientId对应的authKey、authorization及其具有的权限信息,配置信息写入redis中,后期从redis中获取这些信息;
authKey: 某一clientId对应的authKey,专门进行加签、验签操作,为接口安全考虑,不参与通信;
authorization: 接口header中携带的token,因为系统中接口权限验证需要校验token,所以为了匹配整个系统中的访问权限验证需要这个参数;
2.url参数如下:
signTimestamp 签名失效时间,时间戳,单位毫秒,13位【必填】
sign 签名
3.签名的计算规则如下:
a、对所有入参【注意:参数值非空的才会参与签名的计算】clientId、authKey、authorization、signTimestamp 按照字段名的 ASCII 码从小到大排序(字典序)后,
使用 URL 键值对的格式(即 key1=value1&key2=value2…)拼接成字符串 str;
b、对 str 进行 md5 运算,再将得到的字符串所有字符转换为大写,得到 sign 值
4.下面定义了一段生成 sign 字符串的示范过程:
1)、POST请求

URL:http://localhost:7777/xxxxx-service/testResource/test?param3=456

header: key :Authorization value :201295823105949696

body:

{
“param1”:“参数1”,
“param2”:“参数2”,
“param5”:[“www”,“wfawefwe”,“jagjergre”]
}

2)、获取当前时间戳

signTimestamp:1615458960605

3)、按照a规则,排序后的字符串 str:

authKey=303e6bd7-472d-11ea-a802-fa163ecd8c7a&authorization=201295823105949696¶m1=参数1¶m2=参数2¶m3=456¶m5=[“哈哈哈”,“呜呜呜”,“急急急”]&signTimestamp=1615458960605

4)、按照b规则得到签名 sign:

DigestUtils.md5DigestAsHex(“authKey=303e6bd7-472d-11ea-a802-fa163ecd8c7a&authorization=201295823105949696¶m1=参数1¶m2=参数2¶m3=456¶m5=[“哈哈哈”,“呜呜呜”,“急急急”]&signTimestamp=1615458960605”).toUpperCase()
输出:“EBD4B596A4DDDFB6ACBCFAF3E5C6BE6A”

5)、最终请求如下:
URL:http://localhost:7777/xxxxx-service/testResource/test?param3=456&signTimestamp=1615458960605&sign=EBD4B596A4DDDFB6ACBCFAF3E5C6BE6A

header: key :Authorization value :201295823105949696

body:

{
“param1”:“参数1”,
“param2”:“参数2”,
“param5”:[“www”,“wfawefwe”,“jagjergre”]
}

三、UML图

外部接口验签时序图

四、代码实例

1、生成签名

String signTimestamp = SignUtil.getSignTimestamp();
        String getTestDataUrlStr = "/xxxxx-service/roomTest/getTestData?signTimestamp="+signTimestamp+"&sign="+SignUtil.getSign(token,authKey,signTimestamp);
  • 1
  • 2
/**
 * @author
 * @Description 生成签名
 * @Date
 */
public class SignUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(SignUtil.class);

    //生成签名;
    public static String getSign(String token,String authKey,String signTimestamp){

    	//获取签名;
        SortedMap<String, String> allParams = new TreeMap<>();
        allParams.put("authorization",token);
        allParams.put("authKey",authKey);

        //String signTimestampStr = signTimestamp;
        allParams.put("signTimestamp",signTimestamp);

        StringBuilder stringBuilder = new StringBuilder(150);
        for (Map.Entry<String, String> entry : allParams.entrySet()) {
            if (!StringUtils.isEmpty(entry.getValue())) {
                stringBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            }
        }
        String paramsStr = stringBuilder.toString();
        if (StringUtils.isNotBlank(paramsStr)) {
            paramsStr = paramsStr.substring(0, paramsStr.length() - 1);
        }

        return DigestUtils.md5DigestAsHex(paramsStr.getBytes()).toUpperCase();
    }

    public static String getSignTimestamp(){
        //取得指定时区的时间(东八区)
        TimeZone zone = TimeZone.getTimeZone("GMT-8:00");
        Calendar cal = Calendar.getInstance(zone);
        long currentSecond = cal.getTime().getTime();
        String currentTimestamp = String.valueOf(currentSecond);
        return currentTimestamp;
    }


  • 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

2、网关服务 过滤器校验token是否存在及有效性

/**
 * 请求认证过滤
 */
@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {

    /**
     * 基于LRU暂存token对应的用户信息和权限
     */
    public static final ConcurrentLinkedHashMap<String, TokenValueInMap> cacheMap =
            new ConcurrentLinkedHashMap.Builder<String, TokenValueInMap>()
                    .maximumWeightedCapacity(20000)
                    .weigher(Weighers.singleton())
                    .build();

    /**
     * 基于记录无效token,防止无效token多次,以提升系统性能
     */
    private static final ConcurrentLinkedHashMap<String, Long> cacheInValidToken =
            new ConcurrentLinkedHashMap.Builder<String, Long>()
                    .maximumWeightedCapacity(2000)
                    .weigher(Weighers.singleton())
                    .build();

    @Autowired
    RedisService<HashMap, String> redisService;

    @Autowired
    RedisProperties redisProperties;

    private static ThreadPoolExecutor threadPoolExecutorForSendToRedis;

    static {
        threadPoolExecutorForSendToRedis = new ThreadPoolExecutor(
                //线程池维护线程的最少数量
                100,
                //线程池维护线程的最大数量
                10000,
                //线程池维护线程所允许的空闲时间
                120, TimeUnit.SECONDS,
                //线程池所使用的缓冲队列
                new ArrayBlockingQueue<Runnable>(500),
                //加入失败,则在调用的主线程上执行
                new ThreadPoolExecutor.CallerRunsPolicy());
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        HttpResult httpResult = HttpStatus.Unauthorized;

        //备注:针对白名单请求,如果具有有效的token,则将有效的token(即:自身能够正常解析 + redis中未失效)也一并解析
        try {
            //从请求头部获取token
            String token = getTokenFromRequest(request);

            //是否是匿名请求,即:请求路径中是否含有unAuth
            boolean isHasUnAuth = false;

            //对于未携带token的请求,需要判断是否能够直接放行,通过PassConfig中的白名单进行控制
            AntPathMatcher antPathMatcher = new AntPathMatcher();
            long count = PassConfig.WITELIST.stream().filter(pattern -> antPathMatcher.match(pattern, request.getPath().value())).count();
            if (count > 0) {
                isHasUnAuth = true;
                if (StringUtils.isBlank(token)) {
                    //放行前校验请求中的签名
                    this.preChainFilterProcess(request, "");
                    //放行 未携带token的unAuth请求
                    return chain.filter(exchange);
                }
            }

            //访问需要授权的接口时未携带token
            if (!isHasUnAuth && StringUtils.isBlank(token)) {
                throw new Exception("请先登录系统");
            }

            String authorities = null;
            Map<String, Object> userMap = null;

            //获取token对应的用户信息
            //  tokenValueInMap为null,表示token无效,需要重新登录
            TokenValueInMap tokenValueInMap = getTokenValue(token);
            if (null != tokenValueInMap) {
                authorities = tokenValueInMap.tokenValueInRedis.getAuthority();
                userMap = tokenValueInMap.tokenValueInRedis.getClaims();
            }

            //非白名单内的请求,且携带自身解析有效的token,但是token在redis中不存在,直接抛出异常,需要用户重新登录
            if (!isHasUnAuth && null == tokenValueInMap) {
                throw new Exception("登录已超时, 请重新登录");
            }
            //白名单内的请求,且携带自身解析有效的token,但是token在redis中不存在,可以放行
            if (isHasUnAuth && null == tokenValueInMap) {
                this.preChainFilterProcess(request, "");
                return chain.filter(exchange);
            }

            //token自动续期
            final Map<String, Object> userMapTmp = userMap;
            threadPoolExecutorForSendToRedis.execute(() -> {
                this.resetTokenExpireTime(token, userMapTmp);
            });

            ServerHttpRequest httpRequest = request.mutate()
                    .header(User.CONTEXT_USER_ID, userMap.get(JwtTokenUtils.USERID).toString())
                    .header(User.CONTEXT_USER_NAME, userMap.get(JwtTokenUtils.USERNAME).toString())
                    .header(User.CONTEXT_USER_LOGIN_TYPE, userMap.get(JwtTokenUtils.USERLOGINTYPE).toString())
                    .header(User.CONTEXT_CLIENT_ID, userMap.get(JwtTokenUtils.CLIENTID).toString())

                    .header(Company.CONTEXT_COMPANY_ID, null == userMap.get(JwtTokenUtils.COMPANYID) ? ""
                            : userMap.get(JwtTokenUtils.COMPANYID).toString())
                    .header(Company.CONTEXT_COMPANY_NAME, null == userMap.get(JwtTokenUtils.COMPANYNAME) ? ""
                            : URLEncoder.encode(userMap.get(JwtTokenUtils.COMPANYNAME).toString(), "UTF-8"))

                    .header(Dept.CONTEXT_DEPT_ID, null == userMap.get(JwtTokenUtils.DEPTID) ? ""
                            : userMap.get(JwtTokenUtils.DEPTID).toString())
                    .header(Dept.CONTEXT_DEPT_NAME, null == userMap.get(JwtTokenUtils.DEPTNAME) ? ""
                            : URLEncoder.encode(userMap.get(JwtTokenUtils.DEPTNAME).toString(), "UTF-8"))

                    .header(User.CONTEXT_USER_AUTHORITIES, authorities)

                    .header(User.CONTEXT_INVITATION_CODE, null == userMap.get(JwtTokenUtils.INVITATIONCODE) ? ""
                            : userMap.get(JwtTokenUtils.INVITATIONCODE).toString()) //邀请码
                    .header(User.CONTEXT_USER_TOKEN, token).build();

            this.preChainFilterProcess(request, userMap.get(JwtTokenUtils.USERLOGINTYPE).toString());

            return chain.filter(exchange.mutate().request(httpRequest).build());
        } catch (BaseException baseException) {
            if (!BaseException.DEFAULT_CODE.equals(baseException.getCode())) {
                httpResult.setStatus(baseException.getCode());
            }
            return processException(httpResult, baseException, exchange);
        } catch (Exception ex) {
            return processException(httpResult, ex, exchange);
        }
    }

    /**
     * 认证时的异常处理
     *
     * @param httpResult
     * @param ex
     * @param response
     * @return
     */
    private Mono<Void> processException(HttpResult httpResult, Exception ex, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        httpResult.setMessage(ex.getMessage());
        String result = JSONObject.toJSON(httpResult).toString();
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        response.setStatusCode(org.springframework.http.HttpStatus.OK);
        return exchange.getResponse()
                .writeWith(Flux.just(exchange.getResponse().bufferFactory().wrap(result.getBytes())));
    }

    /**
     * 过滤器执行的优先级,值越小优先级越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return -5;
    }

    /**
     * 从请求总获取token
     *
     * @param request
     * @return
     */
    private String getTokenFromRequest(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(JwtTokenUtils.HEADER_AUTH);

        //由于websocket比较特殊,涉及http upgrade的过程
        //无法在header中携带token,只能在请求路径中携带token
        //如果请求以"/websocket-server/chat/"开头,就认为是websocket类型的请求,token放在请求的最后,通过"/"截取,以获取token
        if (request.getURI().getPath().startsWith("/websocket-server/chat/")) {
            token = request.getURI().getPath().substring(request.getURI().getPath().lastIndexOf("/") + 1);
        }
        return token;
    }

    /**
     * 自动续期
     * 重新设置token的过期时间
     *
     * @param token
     * @param userMap
     * @return
     */
    private void resetTokenExpireTime(String token, Map<String, Object> userMap) {
        String userId = userMap.get(JwtTokenUtils.USERID).toString();
        String userLoginType = userMap.get(JwtTokenUtils.USERLOGINTYPE).toString();

        //为了系统安全,通过员工超级密码生成的token不会自动续期,默认12小时后过期
        if (UserLoginTypeConstants.EmployeeLoginPCBySuperPassword.equals(userLoginType)) {
            return;
        }

        if (LoginDeviceTypeConstants.APP.equals(LoginRelatedHelper.getLoginDeviceType(userLoginType))) {
            //登录方式为app,自动续期12天
            boolean isOK = redisService.expire(token, redisProperties.getAppTokenExpireMilliSeconds());
            if (isOK) { //从性能上考虑,只有上一步执行成功,才会执行下一步
                redisService.expire(userLoginType + userId, redisProperties.getAppTokenExpireMilliSeconds());
            }
        } else if (LoginDeviceTypeConstants.PC.equals(LoginRelatedHelper.getLoginDeviceType(userLoginType))) {
            //登录方式为pc或第三方模拟登录,自动续期8小时ø
            boolean isOK = redisService.expire(token, redisProperties.getPcTokenExpireMilliSeconds());
            if (isOK) { //从性能上考虑,只有上一步执行成功,才会执行下一步
                redisService.expire(userLoginType + userId, redisProperties.getPcTokenExpireMilliSeconds());
            }
        } else {
            //针对虚拟用户的调用,由于token是不失效的,所以不用续期
        }
    }

    /**
     * 执行chain.filter之前进行的预处理
     *
     * @param request
     * @param userLoginType
     */
    private void preChainFilterProcess(ServerHttpRequest request, String userLoginType) {
        this.veritySign(request, userLoginType);
    }

    /**
     * 校验请求中的签名
     *
     * @param request
     * @param userLoginType
     */
    private void veritySign(ServerHttpRequest request, String userLoginType) {
        userLoginType = null == userLoginType ? "" : userLoginType;

        //暂时认定通过第三方系统 虚拟用户登录 的请求中必须要携带sign和signTimestamp
        //【待开发】等前端改造完后再开放对所有用户请求的签名校验
        if (userLoginType.startsWith(UserTypeConstants.VirtualUser)) {

            String sign = request.getQueryParams().getFirst("sign");
            String signTimestamp = request.getQueryParams().getFirst("signTimestamp");

            if (StringUtils.isBlank(sign)) {
                throw new BaseException(HttpStatus.SignError.getStatus(), "请求中的签名 sign 不能为空");
            }

            if (StringUtils.isBlank(signTimestamp)) {
                throw new BaseException(HttpStatus.SignError.getStatus(), "请求中签名的时间戳 signTimestamp 不能为空");
            }
        }
    }

    /**
     * 先从缓存中获取token对应的用户和权限,如果存在 且 没有过期,则直接返回,否则需要到redis中去获取,再写入map
     * 由于这里对token进行了缓存,所以在分布式场景中,客户退出登录后,token还在缓存中,这样就无法做到立即实现单点控制,但是考虑到服务器的tps,目前暂时采取这种折中的方式
     *
     * @param token
     * @return
     */
    private TokenValueInMap getTokenValue(String token) {
        // 先从缓存中获取token对应的权限,如果存在 且 没有过期,则直接返回,否则需要到redis中去获取,再写入map
        TokenValueInMap authorityInMapValue = cacheMap.get(token);
        // 60*60*8 * 1000 = 28800 * 1000 = 28800000 (map中的值8小时后过期)
        if (null == authorityInMapValue || (authorityInMapValue.timeMillis + 28800000) < System.currentTimeMillis()) {

            //如果判定是无效token,则直接返回
            if (cacheInValidToken.containsKey(token)) {
                Long timeMillis = cacheInValidToken.get(token);
                // 60*60*24 * 1000 = 86400 * 1000 = 86400000 (24小时)
                if (timeMillis + 86400000 > System.currentTimeMillis()) {
                    return null;
                }
            }

            String tokenValueInRedis = redisService.getValue(token);
            if (StringUtils.isNotBlank(tokenValueInRedis)) {
                TokenValueInRedis tokenValue = JSON.parseObject(tokenValueInRedis, new TypeReference<TokenValueInRedis>() {
                });
                authorityInMapValue = new TokenValueInMap(tokenValue, System.currentTimeMillis());
                cacheMap.put(token, authorityInMapValue);
            } else {
                cacheInValidToken.put(token, System.currentTimeMillis());
            }
        }
        return authorityInMapValue;
    }
}

  • 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
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292

3.authclient服务拦截器进行验签

/**
 * 校验签名拦截器
 * 放在所有拦截器的最前面
 */
@Slf4j
@Component
@Setter
public class VerifySignatureInterceptor extends HandlerInterceptorAdapter {

    /**
     * 由外部实例化VerifySignatureInterceptor对象后传入
     */
    private RedisService redisService;

    public static final String Authorization = "Authorization";
    public static final String AuthKey = "authKey";

    /**
     * 签名校验
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws IOException
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        try {
            //如果是访问内部资源文件,则直接放行
            if (!(handler instanceof HandlerMethod)) {
                return true;
            }

            if (!(request instanceof RequestWrapper)) {
                return true;
            }

            RequestWrapper requestWrapper = (RequestWrapper) request;

            //请求中包含签名参数 sign 和 signTimestamp 时,才会校验签名的正确性
            SortedMap<String, String> urlParams = HttpUtils.getUrlParams(requestWrapper);
            String sign = SignUtils.getSign(urlParams);
            String signTimestamp = SignUtils.getSignTimestamp(urlParams); 

            if (!StringUtils.isEmpty(sign) && !StringUtils.isEmpty(signTimestamp)) {
                //从请求中获取所有参数,并组装成验证签名所需要的map
                SortedMap<String, String> allParams = HttpUtils.getAllParams(requestWrapper);
                this.verifyRequestBySign(requestWrapper, allParams);
            }

            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
//            this.returnJson(response, ex);
            CommonExceptionHandler.sendErrorByResponse(response, ex);
            return false;
        }
    }

    /**
     * 根据签名校验请求的有效性,根据所有入参进行签名计算
     *
     * @param allParams
     */
    private void verifyRequestBySign(HttpServletRequest request, SortedMap<String, String> allParams) {
        String sign = SignUtils.getSign(allParams);
        String signTimestamp = SignUtils.getSignTimestamp(allParams);

        if (!StringUtils.isEmpty(sign) && !StringUtils.isEmpty(signTimestamp)) {
            // 写入token
            String token = request.getHeader(Authorization);
            allParams.put(Authorization.toLowerCase(), null == token ? "" : token);

            //从redis中获取签名用的key,并放入SortedMap中
            boolean isHasClientID = false;
            String clientId = request.getHeader(User.CONTEXT_CLIENT_ID);
            if (StringUtils.isEmpty(clientId)) {
                clientId = allParams.get("clientId");
            }
            if (!StringUtils.isEmpty(clientId)) {
                Object authClientObj = this.redisService.hashGet(AuthConstants.PREFIX_AUTH_CLIENT, clientId);
                if (!StringUtils.isEmpty(authClientObj)) {
                    JSONObject jsonObject = JSON.parseObject(authClientObj.toString());
                    allParams.put(AuthKey, jsonObject.getString(AuthKey));
                    isHasClientID = true;
                }
            }
            if (!isHasClientID) {
                throw new BaseException(HttpStatus.SignError.getStatus(), "请求参数中或token中缺少ClientID的值");
            }
            log.info("==allParams====allParams={}"+JSON.toJSONString(allParams));
            // 对参数进行签名验证
            boolean isSigned = SignUtils.verifySign(allParams, 30);
            if (!isSigned) {
                throw new BaseException(HttpStatus.SignError.getStatus(), "请求的签名有误,可能是由于请求过期或参数被篡改");
            }
        }
    }
}




/**
 * 签名工具类
 */
@Slf4j
public class SignUtils {
    /**
     * 根据入参校验
     *
     * @param params
     * @return
     */
    public static boolean verifySign(SortedMap<String, String> params, int timeoutSecond) {
        String urlSign = SignUtils.getSign(params);
        String signTimestamp = SignUtils.getSignTimestamp(params);

        //如果请求中没有签名和时间戳,则直接放行,用于开发内部调测,或各环境中服务间的RPC调用
        if (StringUtils.isBlank(urlSign) || StringUtils.isBlank(signTimestamp)) {
            return true;
        }
        log.info("请求中的签名 : {}", urlSign);

        // 把参数加密
        String paramsSign = getParamsSign(params);
        log.info("==paramsSign====paramsSign={}" + JSON.toJSONString(paramsSign));
        log.info("计算后的签名 : {}", paramsSign);
        return !StringUtils.isBlank(paramsSign)
                && urlSign.equals(paramsSign)
                && checkRequestUrlIsValid(params, timeoutSecond);
    }

    /**
     * 获取签名数据
     *
     * @param params
     * @return
     */
    public static String getSign(SortedMap<String, String> params) {
        return params.get("sign");
    }

    /**
     * 获取签名的时间戳
     *
     * @param params
     * @return
     */
    public static String getSignTimestamp(SortedMap<String, String> params) {
        return params.get("signTimestamp");
    }

    /**
     * 将请求的参数进行MD5加密
     *
     * @param params
     * @return
     */
    public static String getParamsSign(SortedMap<String, String> params) {
        // 要先去掉 Url 里的 Sign
        params.remove("sign");
        StringBuilder stringBuilder = new StringBuilder(150);

        for (Map.Entry<String, String> entry : params.entrySet()) {
            //value非空,才会参与签名计算
            if (!org.springframework.util.StringUtils.isEmpty(params.get(entry.getKey()))) {
                stringBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            }
        }

        //清除最后一位的符号:&
        String paramsStr = stringBuilder.toString();
        if (StringUtils.isNotBlank(paramsStr)) {
            paramsStr = paramsStr.substring(0, paramsStr.length() - 1);
        }

        return DigestUtils.md5DigestAsHex(paramsStr.getBytes()).toUpperCase();
    }

    /**
     * 校验请求中的时间戳是否过期
     *
     * @param params
     * @return
     */
    public static boolean checkRequestUrlIsValid(SortedMap<String, String> params, int timeoutSecond) {
        String signTimestamp = SignUtils.getSignTimestamp(params);
        if (StringUtils.isBlank(signTimestamp)) {
            return false;
        }
        Long signTimestampLong = 0L;
        try {
            signTimestampLong = Long.valueOf(signTimestamp);
        } catch (Exception ex) {
            return false;
        }

        //取得指定时区的时间(东八区)
        TimeZone zone = TimeZone.getTimeZone("GMT-8:00");
        Calendar cal = Calendar.getInstance(zone);
        long currentTimestamp = cal.getTime().getTime();

        long internalSecond = ((currentTimestamp - signTimestampLong) / 1000);
        log.info("internalSecond:" + internalSecond);

        //超出时间间隔,则认定请求过期
        if (internalSecond > timeoutSecond) {
            return false;
        }

        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
  • 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
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/732626
推荐阅读
相关标签
  

闽ICP备14008679号