RuoYi-Vue后端接口安全:签名验证与时间戳防篡改
在前后端分离架构中,接口安全是系统防护的第一道屏障。RuoYi-Vue作为成熟的权限管理系统,采用Spring Security和JWT(JSON Web Token)构建基础安全框架,但在实际生产环境中,还需通过签名验证和时间戳机制抵御重放攻击与数据篡改。本文将从原理到实践,详解如何为RuoYi-Vue接口添加双重防护。
安全框架基础架构
RuoYi-Vue的安全核心基于Spring Security实现,核心配置类ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java定义了完整的认证流程:
// 关键安全过滤器链配置
@Override
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf(csrf -> csrf.disable()) // 基于Token无需CSRF保护
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 无状态会话
.authorizeHttpRequests((requests) -> {
requests.antMatchers("/login", "/register").permitAll() // 公开接口
.anyRequest().authenticated(); // 其余接口需认证
})
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT过滤器
}
系统默认通过JWT过滤器JwtAuthenticationTokenFilter实现令牌校验,但缺乏针对请求参数的签名验证机制,这正是本文要解决的核心问题。
签名验证原理与实现
签名算法设计
签名验证通过对请求参数进行哈希计算生成签名值,服务端通过相同算法验证参数完整性。推荐实现步骤:
- 参数排序:对所有请求参数按ASCII码排序
- 拼接密钥:在排序后参数末尾追加应用密钥
- 哈希计算:使用SHA-256生成签名值
代码实现
在ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java中添加签名工具方法:
/**
* 生成请求签名
* @param params 请求参数
* @param secret 应用密钥
* @return 签名值
*/
public static String generateSignature(Map<String, String> params, String secret) {
// 1. 参数排序
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
// 2. 拼接参数
StringBuilder sb = new StringBuilder();
for (String key : keys) {
sb.append(key).append("=").append(params.get(key)).append("&");
}
sb.append("secret=").append(secret);
// 3. SHA-256哈希
return DigestUtils.sha256Hex(sb.toString());
}
/**
* 验证签名
*/
public static boolean verifySignature(Map<String, String> params, String signature, String secret) {
String generated = generateSignature(params, secret);
return generated.equals(signature);
}
时间戳防重放攻击
防篡改机制设计
重放攻击防护需结合时间戳(Timestamp)和随机数(Nonce)实现:
实现关键点
- 时间戳验证:要求客户端时间与服务端时间差不超过5分钟
- Nonce存储:使用Redis存储已使用随机数,设置5分钟过期
- 原子操作:通过Redis的SETNX命令实现Nonce去重
整合到安全过滤器链
自定义签名过滤器
创建ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/SignatureAuthenticationFilter.java:
@Component
public class SignatureAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private StringRedisTemplate redisTemplate;
private static final long TIMESTAMP_VALIDITY = 5 * 60 * 1000; // 5分钟
private static final String APP_SECRET = "your-app-secret-key"; // 实际应从配置文件读取
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 1. 获取请求参数
Map<String, String> params = getRequestParams(request);
String timestamp = params.get("timestamp");
String nonce = params.get("nonce");
String signature = params.get("signature");
// 2. 验证时间戳
long now = System.currentTimeMillis();
if (Math.abs(now - Long.parseLong(timestamp)) > TIMESTAMP_VALIDITY) {
throw new ServiceException("请求已过期", HttpStatus.BAD_REQUEST);
}
// 3. 验证Nonce
String nonceKey = "api:nonce:" + nonce;
Boolean isNew = redisTemplate.opsForValue().setIfAbsent(nonceKey, "1", TIMESTAMP_VALIDITY, TimeUnit.MILLISECONDS);
if (Boolean.FALSE.equals(isNew)) {
throw new ServiceException("重复请求", HttpStatus.BAD_REQUEST);
}
// 4. 验证签名
params.remove("signature"); // 移除签名参数
if (!SecurityUtils.verifySignature(params, signature, APP_SECRET)) {
throw new ServiceException("签名验证失败", HttpStatus.UNAUTHORIZED);
}
filterChain.doFilter(request, response);
}
}
注册过滤器
在SecurityConfig中添加新过滤器:
@Autowired
private SignatureAuthenticationFilter signatureFilter;
@Override
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
// ... 原有配置 ...
.addFilterBefore(signatureFilter, JwtAuthenticationTokenFilter.class) // 签名过滤器在JWT过滤器之前
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
客户端实现示例
前端请求需添加签名参数,以Vue组件为例:
// src/utils/request.js
import axios from 'axios';
import CryptoJS from 'crypto-js';
// 生成签名
function generateSignature(params, secret) {
const sortedKeys = Object.keys(params).sort();
let str = sortedKeys.map(key => `${key}=${params[key]}`).join('&') + `&secret=${secret}`;
return CryptoJS.SHA256(str).toString();
}
// 请求拦截器
axios.interceptors.request.use(config => {
const timestamp = Date.now().toString();
const nonce = Math.random().toString(36).substr(2, 10);
const params = { ...config.params, timestamp, nonce };
// 生成签名
params.signature = generateSignature(params, 'your-app-secret-key');
config.params = params;
return config;
});
系统安全加固建议
- 密钥管理:通过配置中心动态获取密钥,避免硬编码
- 敏感接口保护:对支付、用户信息等接口强制开启签名验证
- 日志审计:在ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java中记录签名验证日志
- 定期更换密钥:结合业务制定密钥轮换策略
通过以上实现,RuoYi-Vue系统将具备完善的接口防篡改能力,有效抵御重放攻击、参数篡改等常见安全威胁。实际部署时需根据业务场景调整时间戳有效期和密钥管理策略,确保安全性与可用性平衡。
完整实现可参考官方文档doc/若依环境使用手册.docx中的安全加固章节,或查看系统监控模块ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java的审计日志实现。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



