RuoYi-Vue后端接口安全:签名验证与时间戳防篡改

RuoYi-Vue后端接口安全:签名验证与时间戳防篡改

【免费下载链接】RuoYi-Vue :tada: (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue & Element 的前后端分离权限管理系统,同时提供了 Vue3 的版本 【免费下载链接】RuoYi-Vue 项目地址: https://gitcode.com/GitHub_Trending/ru/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实现令牌校验,但缺乏针对请求参数的签名验证机制,这正是本文要解决的核心问题。

签名验证原理与实现

签名算法设计

签名验证通过对请求参数进行哈希计算生成签名值,服务端通过相同算法验证参数完整性。推荐实现步骤:

  1. 参数排序:对所有请求参数按ASCII码排序
  2. 拼接密钥:在排序后参数末尾追加应用密钥
  3. 哈希计算:使用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)实现:

mermaid

实现关键点

  1. 时间戳验证:要求客户端时间与服务端时间差不超过5分钟
  2. Nonce存储:使用Redis存储已使用随机数,设置5分钟过期
  3. 原子操作:通过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;
});

系统安全加固建议

  1. 密钥管理:通过配置中心动态获取密钥,避免硬编码
  2. 敏感接口保护:对支付、用户信息等接口强制开启签名验证
  3. 日志审计:在ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java中记录签名验证日志
  4. 定期更换密钥:结合业务制定密钥轮换策略

通过以上实现,RuoYi-Vue系统将具备完善的接口防篡改能力,有效抵御重放攻击、参数篡改等常见安全威胁。实际部署时需根据业务场景调整时间戳有效期和密钥管理策略,确保安全性与可用性平衡。

完整实现可参考官方文档doc/若依环境使用手册.docx中的安全加固章节,或查看系统监控模块ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java的审计日志实现。

【免费下载链接】RuoYi-Vue :tada: (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue & Element 的前后端分离权限管理系统,同时提供了 Vue3 的版本 【免费下载链接】RuoYi-Vue 项目地址: https://gitcode.com/GitHub_Trending/ru/RuoYi-Vue

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值