浅析Spring Security如何防止CSRF攻击

一、CSRF攻击原理与危害

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web安全威胁,攻击者诱骗已认证用户在不知情的情况下执行非预期的操作。

图片[1]_浅析Spring Security如何防止CSRF攻击_知途无界

典型攻击流程

  1. 用户登录受信任网站A,本地保存了认证Cookie
  2. 用户未登出网站A的情况下访问恶意网站B
  3. 网站B的页面中包含伪造请求(如自动提交的表单)
  4. 浏览器自动携带网站A的Cookie发送请求
  5. 网站A服务器认为这是用户的合法操作

潜在危害

  • 用户数据篡改(修改个人信息)
  • 资金转移(银行转账)
  • 权限提升(修改账户权限)
  • 内容发布(社交媒体发帖)

二、Spring Security的CSRF防护机制

Spring Security提供了开箱即用的CSRF防护方案,主要通过以下技术实现:

1. 同步令牌模式(Synchronizer Token Pattern)

核心原理

  • 服务器生成随机令牌(CSRF Token)
  • 令牌存储在服务器会话中
  • 每次提交表单必须携带该令牌
  • 服务器验证令牌有效性

Spring Security实现细节

public class CsrfFilter extends OncePerRequestFilter {
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain) {
        // 生成或获取现有令牌
        CsrfToken csrfToken = this.tokenRepository.loadToken(request);
        if (csrfToken == null) {
            csrfToken = this.tokenRepository.generateToken(request);
            this.tokenRepository.saveToken(csrfToken, request, response);
        }

        // 验证请求(非安全方法需要验证)
        if (!this.requireCsrfProtectionMatcher.matches(request)) {
            filterChain.doFilter(request, response);
            return;
        }

        // 实际验证逻辑
        String actualToken = request.getHeader(csrfToken.getHeaderName());
        if (actualToken == null) {
            actualToken = request.getParameter(csrfToken.getParameterName());
        }
        if (!csrfToken.getToken().equals(actualToken)) {
            accessDeniedHandler.handle(request, response, 
                new AccessDeniedException("Invalid CSRF token"));
            return;
        }

        filterChain.doFilter(request, response);
    }
}

2. 默认启用与配置

Spring Security 4.x+ 默认启用CSRF保护,如需自定义配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .ignoringAntMatchers("/api/public/**")
                .and()
            // 其他配置...
    }
}

关键配置项

  • csrfTokenRepository:指定令牌存储策略
  • HttpSessionCsrfTokenRepository(默认,会话存储)
  • CookieCsrfTokenRepository(Cookie存储)
  • ignoringAntMatchers:排除不需要防护的路径

3. 令牌生成与验证流程

令牌生命周期

  1. 首次请求:生成令牌并存入Session/Cookie
  2. 页面渲染:将令牌值注入表单隐藏域或HTTP头
  3. 表单提交:携带令牌发送请求
  4. 服务器验证:比对提交令牌与存储令牌
  5. 验证通过:处理请求;失败:拒绝请求

三、前端集成方案

1. 传统表单集成

Spring Security自动支持Thymeleaf、JSP等模板引擎:

<form method="post">
    <input type="hidden" 
           name="${_csrf.parameterName}" 
           value="${_csrf.token}"/>
    <!-- 其他表单字段 -->
</form>

2. 单页应用(SPA)集成

Cookie方案(推荐):

http.csrf()
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());

前端从Cookie读取令牌并设置HTTP头:

// 从Cookie获取令牌
function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

// 设置XSRF-TOKEN头
fetch('/api/data', {
    method: 'POST',
    headers: {
        'X-XSRF-TOKEN': getCookie('XSRF-TOKEN'),
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({/* data */})
});

3. 自动令牌注入

使用Spring Security标签库或Meta标签:

<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>

四、高级防护策略

1. 双重Cookie验证

http.csrf()
    .csrfTokenRepository(new CookieCsrfTokenRepository())
    .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler());

2. 同站点Cookie属性

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.ignoring().requestMatchers(
        new CookieCsrfTokenRepository()::setCookieCustomizer);
}

// 自定义Cookie配置
CookieCsrfTokenRepository cookieRepo = CookieCsrfTokenRepository.withHttpOnlyFalse();
cookieRepo.setCookieCustomizer(c -> c.sameSite("Strict"));

3. 自定义令牌仓库

public class RedisCsrfTokenRepository implements CsrfTokenRepository {
    private final RedisTemplate<String, String> redisTemplate;

    @Override
    public CsrfToken generateToken(HttpServletRequest request) {
        return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", 
            UUID.randomUUID().toString());
    }

    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request,
            HttpServletResponse response) {
        // 存储到Redis并设置过期时间
    }

    @Override
    public CsrfToken loadToken(HttpServletRequest request) {
        // 从Redis获取令牌
    }
}

五、测试与验证

1. 测试CSRF防护

@SpringBootTest
@AutoConfigureMockMvc
public class CsrfTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldRejectRequestWithoutCsrfToken() throws Exception {
        mockMvc.perform(post("/transfer")
               .param("amount", "1000")
               .param("account", "attacker"))
               .andExpect(status().isForbidden());
    }

    @Test
    public void shouldAcceptRequestWithValidCsrfToken() throws Exception {
        mockMvc.perform(get("/form").with(csrf()))
               .andDo(print())
               .andExpect(status().isOk());

        mockMvc.perform(post("/transfer")
               .with(csrf()) // 自动添加有效令牌
               .param("amount", "1000")
               .param("account", "family"))
               .andExpect(status().isOk());
    }
}

2. 安全头检测

@Test
public void checkSecurityHeaders() throws Exception {
    mockMvc.perform(get("/"))
        .andExpect(header().exists("X-Content-Type-Options"))
        .andExpect(header().exists("X-Frame-Options"))
        .andExpect(header().exists("X-XSS-Protection"));
}

六、最佳实践与注意事项

  1. 防护范围
  • 保护所有状态修改请求(POST/PUT/DELETE/PATCH)
  • 排除纯数据获取接口(GET/HEAD/OPTIONS)
  1. 性能考量
  • 分布式环境下使用共享令牌存储(如Redis)
  • 避免频繁的令牌生成/验证操作
  1. 安全增强
   http.headers()
       .contentSecurityPolicy("script-src 'self'")
       .frameOptions().deny()
       .xssProtection().block(true);
  1. 常见误区
  • 仅依赖CSRF防护而忽视XSS防护(XSS可窃取令牌)
  • 在REST API中错误启用CSRF(应使用OAuth2等机制)
  • 未正确处理令牌过期场景
  1. 现代替代方案
  • 使用SameSite Cookie属性(需浏览器支持)
  • 实现基于加密的”双提交”模式
  • 在API网关层统一处理CSRF防护

总结

Spring Security通过成熟的同步令牌模式为Web应用提供了可靠的CSRF防护,开发者应当:

  1. 理解默认防护机制的工作原理
  2. 根据应用架构选择合适的前端集成方案
  3. 在分布式环境中实现可靠的令牌存储
  4. 结合其他安全措施构建纵深防御体系

随着Web技术的发展,CSRF防护也需要与时俱进,结合SameSite Cookie、CORS策略等现代浏览器安全特性,构建更加完善的防护体系。

© 版权声明
THE END
喜欢就点个赞,支持一下吧!
点赞84 分享
评论 抢沙发
头像
欢迎您留下评论!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容