You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security自定义CSRF实现:签名验证逻辑放置位置咨询

自定义CSRF Token签名验证的正确位置与实现

你的猜测完全正确——把签名验证逻辑放在loadToken()方法里是非常合适的选择。原因很直接:Spring Security在执行CSRF校验流程时,会首先调用loadToken()从请求中加载Token,在这里完成签名验证,能直接拦截掉无效或被篡改的Token,避免后续流程处理非法请求。

不过有个前提需要先调整:你当前的createNewToken()只返回了签名后的字符串,没有保留原始的UUID值,而RSA签名验证需要原始内容+签名值才能完成校验。所以我们需要先修改Token的生成逻辑,把原始Token和签名拼接后存入Cookie。


步骤1:调整Token生成逻辑

修改createNewToken(),将原始UUID和对应的RSA签名用分隔符(比如.)拼接,这样后续可以拆分验证:

private String createNewToken() {
    String unsignedToken = UUID.randomUUID().toString();
    String signature = RSAUtil.signMessage(unsignedToken, privateKey);
    // 拼接原始Token和签名,用分隔符区分(注意分隔符要避免出现在UUID或签名中)
    return unsignedToken + "." + signature;
}

步骤2:在loadToken()中实现签名验证

在加载Cookie中的Token后,先拆分原始内容和签名,然后用公钥验证签名的合法性。如果验证失败,直接返回null,Spring Security会自动判定为CSRF校验失败:

// 注意:需要通过构造方法或配置注入公钥,这里示例用构造方法注入
private final PublicKey publicKey;

public CustomCookieCsrfTokenRepository(PublicKey publicKey) {
    this.publicKey = publicKey;
}

@Override
public CsrfToken loadToken(HttpServletRequest request) {
    Cookie cookie = WebUtils.getCookie(request, this.DEFAULT_CSRF_COOKIE_NAME);
    if (cookie == null) {
        return null;
    }
    String tokenValue = cookie.getValue();
    if (!StringUtils.hasLength(tokenValue)) {
        return null;
    }

    // 拆分原始Token和签名,限制拆分次数为2,避免分隔符出现在签名中的情况
    String[] tokenParts = tokenValue.split("\\.", 2);
    if (tokenParts.length != 2) {
        // Token格式错误,返回null触发CSRF校验失败
        return null;
    }
    String unsignedToken = tokenParts[0];
    String signature = tokenParts[1];

    // 验证签名合法性
    try {
        boolean isValid = RSAUtil.verifyMessage(unsignedToken, signature, publicKey);
        if (!isValid) {
            // 签名无效,返回null
            return null;
        }
    } catch (Exception e) {
        // 验证过程中出现异常(比如签名格式错误、公钥不匹配),视为无效Token
        // 这里可以添加日志记录,方便排查问题
        log.warn("Failed to verify CSRF token signature", e);
        return null;
    }

    // 验证通过,返回合法的CsrfToken(注意这里返回的是原始UUID,而非拼接后的字符串)
    return new DefaultCsrfToken(this.DEFAULT_CSRF_HEADER_NAME, this.DEFAULT_CSRF_PARAMETER_NAME, unsignedToken);
}

额外优化建议

  • 异常与日志处理:在签名验证失败时添加详细日志,便于后续排查篡改或配置问题。
  • Cookie安全性强化:调整Cookie的属性,提升安全性的同时保证前端能正常读取:
@Override
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
    String tokenValue = token == null ? "" : token.getToken();
    Cookie cookie = new Cookie(this.DEFAULT_CSRF_COOKIE_NAME, tokenValue);
    cookie.setSecure(request.isSecure()); // 仅在HTTPS下传输
    cookie.setHttpOnly(false); // 前端需要读取Cookie并在请求头携带,必须设为false
    cookie.setSameSite("Lax"); // 根据业务需求调整为Strict或None(None需配合Secure)
    cookie.setPath("/"); // 确保整个应用域都能访问该Cookie
    cookie.setMaxAge(3600); // 设置Cookie有效期,避免永久有效
    response.addCookie(cookie);
}

内容的提问来源于stack exchange,提问作者Killer Beast

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:07:21