校验时间戳和签名

该博客主要介绍了使用Java进行URL链接中时间戳和签名的获取与校验。先从URL中获取时间戳和签名,接着对时间戳的有效性和签名的正确性进行校验,还给出了验证工具类,包含验证签名和生成签名的方法。

1.获取url链接里面的时间戳和签名 

// 时间戳
            String timestamp = qpMap.get("timestamp");
            // 签名
            String sign = qpMap.get("sign");
2.进行校验

        // 校验签名前后过期时间
            checkSignTime(timestamp);

            // 校验签名
            checkSign(streamStr, timestamp, sign);
private void checkSignTime(String timestamp) {
        if (StringUtils.isBlank(timestamp)) {
            //logger.error("{}-鉴权时间戳缺失", MsgCode.SIGN_PARAM_ERROR_CODE);
            throw new BusinessException(MsgCode.SIGN_PARAM_ERROR_MSG, MsgCode.SIGN_PARAM_ERROR_CODE);
        }
        long currentTime = System.currentTimeMillis() / 1000;// 统一都传毫秒
        long requestTime = 0;
        try {
            requestTime = Long.parseLong(timestamp);
        } catch (NumberFormatException e) {
            //logger.error("{}-鉴权时间戳错误 timestamp:{}", MsgCode.SIGN_PARAM_ERROR_CODE, timestamp);
            throw new BusinessException(MsgCode.SIGN_PARAM_ERROR_MSG, MsgCode.SIGN_PARAM_ERROR_CODE);
        }
        if (Math.abs(currentTime - requestTime) > APPIConstants.Filter.OUT_OF_DATE_TIME_LONG) {
            logger.error("{}-签名已过期,服务器当前时间:{}", MsgCode.OUT_OF_DATE_SIGN_CODE, currentTime);
            throw new BusinessException(String.format(MsgCode.OUT_OF_DATE_SIGN_MSG, currentTime), MsgCode.OUT_OF_DATE_SIGN_CODE);
        }
    }

        if (StringUtils.isBlank(sign)) {
            //logger.error("{}-签名为空", MsgCode.SIGN_PARAM_ERROR_CODE);
            throw new BusinessException(MsgCode.SIGN_PARAM_ERROR_MSG, MsgCode.SIGN_PARAM_ERROR_CODE);
        }

        // 验证签名
         streamStr = Base64.encode(streamStr);
         if (!SignUtil.checkSign(streamStr, APPIConstants.Filter.key, timestamp, sign)) {
            //logger.error("{}-签名错误", MsgCode.SIGN_ERR_CODE);
            throw new BusinessException(MsgCode.SIGN_ERR_MSG, MsgCode.SIGN_ERR_CODE);
        }
    }
验证工具类: 

public class SignUtil {
private final static Logger logger = LoggerFactory.getLogger(SignUtil.class);

/**
* @Description 验证签名
* @param content 验签内容
* @param key 签名key值
* @param additionContent 附加的签名信息
* @param sign 待验证的签名
*/
public static boolean checkSign(String content, String key, String additionContent, String sign) {
if (StringUtils.isBlank(sign)) {
return false;
}
String _sign = createSign(content, key, additionContent);
if (sign.equalsIgnoreCase(_sign)) {
return true;
}
logger.error("参数sign:{},验签sign:{}", sign, _sign);
return false;
}

/**
* @Description 生成签名
* @param content 签名内容
* @param key 签名key值
*/
public static String createSign(String content, String key, String additionContent) {
if(StringUtils.isEmpty(additionContent)){
return MD5Util.getMd5(content + key);
}else{
return MD5Util.getMd5(content + key + additionContent);
}
}
}
 
---------------------

原文:https://blog.youkuaiyun.com/riju4713/article/details/81457479

转载于:https://www.cnblogs.com/dayandday/p/10948081.html

### 签名、验签与时间戳功能整合的实现方法 在接口安全设计中,签名(Sign)、验签时间戳(Timestamp)是确保请求完整性时效性的关键机制。通过整合这些功能,可以有效防止请求伪造、参数篡改重放攻击。以下是实现方法的详细说明。 #### 签名的生成与验证 签名是基于请求参数生成的加密字符串,用于验证请求的来源完整性。通常使用 `HMAC-SHA256` 或 `MD5` 等算法进行签名生成。具体步骤如下: 1. **参数排序**:将所有请求参数按照参数名进行排序,确保生成签名的顺序一致。 2. **拼接字符串**:将排序后的参数以 `key=value` 的形式拼接成一个字符串。 3. **添加密钥**:在拼接的字符串末尾加上密钥(Secret Key),用于增强签名的安全性。 4. **生成签名**:使用加密算法(如 `HMAC-SHA256`)对拼接后的字符串进行加密,生成签名值。 5. **传递签名**:将生成的签名作为请求参数之一传递给服务端。 服务端在接收到请求后,重复上述步骤,生成签名并与请求中的签名进行比对。如果一致,则验签成功;否则,拒绝请求。 #### 时间戳校验 时间戳用于防止请求的重放攻击。通过校验时间戳,可以确保请求的时效性。具体实现如下: 1. **生成时间戳**:客户端在发起请求时,生成当前时间的时间戳(通常以秒或毫秒为单位)。 2. **传递时间戳**:将时间戳作为请求参数之一传递给服务端。 3. **校验时间戳**:服务端接收到请求后,获取时间戳参数,并与当前时间进行比较。如果时间差超过预设的阈值(如 5 分钟),则拒绝请求。 #### 防止请求重放 为了进一步防止请求重放,可以引入 `nonce`(一次性随机值)机制。`nonce` 是一个仅能使用一次的随机值,确保每个请求的唯一性。具体步骤如下: 1. **生成 nonce**:客户端在发起请求时,生成一个唯一的随机值作为 `nonce`。 2. **传递 nonce**:将 `nonce` 作为请求参数之一传递给服务端。 3. **校验 nonce**:服务端接收到请求后,检查 `nonce` 是否已使用过。如果已使用,则拒绝请求;否则,记录 `nonce` 并继续处理。 #### 整合签名、验签与时间戳功能 在实际开发中,可以结合拦截器或过滤器实现签名、验签时间戳校验。以下是一个基于 Spring Boot 的实现示例: ```java @Component public class SignInterceptor implements HandlerInterceptor { private static final String SECRET_KEY = "your_secret_key"; private static final long TIMESTAMP_EXPIRE = 5 * 60 * 1000; // 5分钟 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取请求参数 Map<String, String[]> parameterMap = request.getParameterMap(); Map<String, String> params = new HashMap<>(); for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { params.put(entry.getKey(), entry.getValue()[0]); } // 获取签名时间戳 nonce String sign = params.get("sign"); String timestamp = params.get("timestamp"); String nonce = params.get("nonce"); // 校验时间戳 long currentTime = System.currentTimeMillis(); long requestTime = Long.parseLong(timestamp); if (Math.abs(currentTime - requestTime) > TIMESTAMP_EXPIRE) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Timestamp expired"); return false; } // 校验 nonce if (isNonceUsed(nonce)) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Nonce already used"); return false; } // 生成签名校验 String generatedSign = generateSign(params, SECRET_KEY); if (!generatedSign.equals(sign)) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid sign"); return false; } // 标记 nonce 已使用 markNonceAsUsed(nonce); return true; } private String generateSign(Map<String, String> params, String secretKey) { // 参数排序 List<String> keys = new ArrayList<>(params.keySet()); Collections.sort(keys); // 拼接字符串 StringBuilder sb = new StringBuilder(); for (String key : keys) { if (!key.equals("sign")) { sb.append(key).append("=").append(params.get(key)).append("&"); } } sb.append("key=").append(secretKey); // 生成签名 return DigestUtils.md5DigestAsHex(sb.toString().getBytes(StandardCharsets.UTF_8)); } private boolean isNonceUsed(String nonce) { // 实现 nonce 校验逻辑,例如使用 Redis 缓存 return false; } private void markNonceAsUsed(String nonce) { // 实现 nonce 记录逻辑,例如使用 Redis 缓存 } } ``` #### 请求体重复读取问题的解决方案 在 Spring Boot 中,`HttpServletRequest.getInputStream()` 默认只能读取一次。如果在拦截器中读取了请求体内容用于签名校验,后续的 Controller 将无法再次读取,导致报错。解决方案是使用 `CachedBodyHttpServletRequest` 包装请求,实现请求体的缓存重复读取。具体实现如下: ```java public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { private byte[] cachedBody; public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException { super(request); InputStream requestInputStream = request.getInputStream(); this.cachedBody = StreamUtils.readStream(requestInputStream); } @Override public ServletInputStream getInputStream() { return new CachedBodyServletInputStream(this.cachedBody); } @Override public BufferedReader getReader() { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody); return new BufferedReader(new InputStreamReader(byteArrayInputStream)); } } public class CachedBodyServletInputStream extends ServletInputStream { private final ByteArrayInputStream byteArrayInputStream; public CachedBodyServletInputStream(byte[] cachedBody) { this.byteArrayInputStream = new ByteArrayInputStream(cachedBody); } @Override public int read() { return this.byteArrayInputStream.read(); } @Override public boolean isFinished() { return this.byteArrayInputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { throw new UnsupportedOperationException(); } } ``` 通过上述方法,可以有效整合签名、验签时间戳功能,提升接口的安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值