API安全:从Token到签名
随着企业业务规模的扩大,API接口安全成为开发者必须面对的核心问题。无论是对接第三方服务(如支付、地图API),还是对外提供开放接口,安全设计都至关重要。本文将系统介绍两种主流解决方案:Token机制和接口签名验证,并通过代码示例展示最佳实践。
Token机制详解
Token方案是Web开发中最基础的鉴权方式,其核心流程包括:
用户登录成功后,服务端生成唯一凭证Token 客户端在后续请求头中携带Token 服务端验证Token有效性后处理业务
生产环境中,Token通常存储在Redis中并设置2小时有效期。虽然Token方案能有效防止数据爬取,但在高并发场景下存在明显缺陷——当Token过期瞬间,刷新Token的过程会导致大量请求失败。某大型互联网公司的实际案例显示,这种机制在流量高峰期可能引发服务抖动。
接口签名方案
相比Token,接口签名更适合高安全性要求的场景,其核心参数包括:
appid/appsecret:应用标识和密钥,用于加密签名 timestamp:请求时间戳(有效期为5分钟) nonce:随机数,防止重放攻击 signature:最终签名值
签名算法采用双重MD5加密:
//第一次加密
String step1 = md5(method + url + body);
//第二次加密
String signature = md5(appsecret + timestamp + nonce + step1);
这种方案在需要与第三方进行复杂对接时尤为可靠,但计算复杂度较高,不建议用于前后端交互场景。
JWT实现示例
以下是基于JWT的Token工具类实现:
public class JwtTokenUtil {
private static final String KEY = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";
private static final long EXPIRATION = 7200000; //2小时
public static String createToken(String content) {
return "Bearer " + JWT.create()
.withSubject(content)
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION))
.sign(Algorithm.HMAC512(KEY));
}
public static String verifyToken(String token) throws Exception {
return JWT.require(Algorithm.HMAC512(KEY))
.build()
.verify(token.replace("Bearer ", ""))
.getSubject();
}
}
使用注意事项:
可将基础用户信息存入Token,减少数据库查询 严禁存储敏感数据,Token内容可被解析
签名验证拦截器
完整的签名验证流程应包含:
public class SignInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//1. 验证appid
String appId = request.getHeader("appid");
String appSecret = getAppSecret(appId);
//2. 验证时间戳(5分钟有效期)
long timestamp = Long.parseLong(request.getHeader("timestamp"));
if(Math.abs(System.currentTimeMillis() - timestamp) > 300000) {
throw new CommonException("timestamp已过期");
}
//3. 防重放验证
String nonce = request.getHeader("nonce");
if(redisUtil.exist(appId + "_" + timestamp + "_" + nonce)) {
throw new CommonException("请勿重复提交");
}
//4. 签名校验
String signature = request.getHeader("signature");
String computed = SignUtil.getSignature(
request.getMethod(),
request.getRequestURI(),
StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8),
timestamp, nonce, appSecret
);
if(!signature.equals(computed)) {
throw new CommonException("签名验证失败");
}
//5. 记录请求标记
redisUtil.save(appId + "_" + timestamp + "_" + nonce, computed, 300);
return true;
}
}
最佳实践建议:
对内接口使用JWT + Token机制 对外合作优先选择签名方案 关键操作必须实现防重放攻击 密钥管理采用分级隔离策略
这两种方案可以结合使用:内部系统采用Token验证效率更高,而开放接口则使用签名确保安全性。开发者应根据实际业务场景选择合适的方案,在安全性和性能之间取得平衡。