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

微服务架构下Spring Boot 2.7.3+Angular的CSRF实现问题咨询

问题分析与解决方案

首次请求CSRF Token的正常表现

你提到调用/csrf-token后Angular和Cookie中都存在Token是完全正常的:

  • Cookie中的Token是Spring Security通过CookieCsrfTokenRepository生成并写入的,浏览器会自动携带到同域/同父域的请求中
  • Angular的HttpClientXsrfModule会自动读取指定Cookie的Token值,将其放入请求头发送,所以两边同时存在是预期行为,并非异常

你的Kafka方案的核心问题

这个思路确实存在难以规避的缺陷:

  • 同步风险:Angular发送请求的速度可能快于Kafka消息的消费速度,导致资源服务还未收到Token映射就收到请求,验证失败
  • 维护成本:每个微服务都要维护独立的Token映射,增加了系统复杂度和出错概率
  • 侵入性强:需要修改Spring Security原生的Token生成逻辑,定制成本高

可行的跨服务CSRF验证方案(无需数据库、无需频繁调用认证服务)

基于Spring Boot 2.7.3(Spring Security 5.7.x),推荐以下两种方案:

1. 自包含式CSRF Token(JWT方案)

让认证服务生成带签名的JWT格式CSRF Token,资源服务直接验证签名即可确认Token有效性:

  • 实现逻辑:
    1. 自定义CsrfTokenRepository,替换默认的CookieCsrfTokenRepository,生成包含用户标识、过期时间的JWT Token,并用只有认证服务和资源服务知晓的密钥签名
    2. 保持现有Cookie配置,将JWT Token写入Cookie
    3. 资源服务引入JWT验证依赖(如jjwt),配置相同的签名密钥,自定义CSRF验证逻辑,通过验证JWT签名和过期时间来确认Token合法性

示例代码(认证服务自定义Repository):

public class JwtCsrfTokenRepository implements CsrfTokenRepository {
    private final String sharedSecret = "your-cross-service-secret";
    private final CookieCsrfTokenRepository delegate = CookieCsrfTokenRepository.withHttpOnlyFalse();

    @Override
    public CsrfToken generateToken(HttpServletRequest request) {
        CsrfToken baseToken = delegate.generateToken(request);
        // 生成带签名的JWT
        String jwtToken = Jwts.builder()
                .setSubject(baseToken.getToken())
                .claim("userId", request.getUserPrincipal().getName())
                .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1小时过期
                .signWith(SignatureAlgorithm.HS256, sharedSecret.getBytes(StandardCharsets.UTF_8))
                .compact();
        return new DefaultCsrfToken(baseToken.getHeaderName(), baseToken.getParameterName(), jwtToken);
    }

    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
        delegate.saveToken(token, request, response);
    }

    @Override
    public CsrfToken loadToken(HttpServletRequest request) {
        return delegate.loadToken(request);
    }
}

资源服务验证逻辑:

@Bean
public CsrfTokenValidator csrfTokenValidator() {
    return (csrfToken, request) -> {
        String requestToken = request.getHeader(csrfToken.getHeaderName());
        try {
            // 验证JWT签名和有效性
            Jwts.parser()
                    .setSigningKey("your-cross-service-secret".getBytes(StandardCharsets.UTF_8))
                    .parseClaimsJws(requestToken);
        } catch (JwtException e) {
            throw new InvalidCsrfTokenException(csrfToken, requestToken);
        }
    };
}

2. 分布式缓存共享Token(Redis方案)

用Redis作为跨服务的CSRF Token存储,替代Kafka的异步方案:

  • 实现逻辑:
    1. 认证服务生成CSRF Token后,将Token与用户标识、过期时间存入Redis
    2. 资源服务验证Token时,直接从Redis查询Token是否存在、是否属于当前用户、是否过期
    3. 配置Redis自动过期策略,避免无效Token占用内存

这个方案的优势是同步读取,不会出现消息延迟导致的验证失败,且实现复杂度低于JWT方案。

关键遗漏点

  • Spring Security 5.x默认的CSRF验证仅检查请求头与Cookie的Token是否一致,不验证Token的合法性(如是否属于当前用户、是否过期),你需要自定义验证逻辑补充这一步
  • 跨服务CSRF验证的核心是Token可信性的传递:要么让Token自带验证信息(JWT),要么让服务共享可信的存储(Redis),异步消息方案(如Kafka)无法满足实时验证的需求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 02:24:54