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
相关产品推荐
相关产品推荐

