用于 Spring Security 和 AngularJS 的 CSRF/XSRF 保护
Posted
技术标签:
【中文标题】用于 Spring Security 和 AngularJS 的 CSRF/XSRF 保护【英文标题】:CSRF/XSRF protection for Spring Security and AngularJS 【发布时间】:2018-02-18 03:05:39 【问题描述】:我尝试向我的应用程序添加 CSRF/XSRF 保护,但遇到了奇怪的行为。所有获取请求都可以正常工作,但是在所有发布/放置/删除时,我都会收到 403 Unauthorized。最奇怪的是,当我尝试调试我的 CSRF 过滤器时,请求没有到达它,它们在更早的地方被拒绝了。他们甚至没有到达我的身份验证过滤器,所以我无法弄清楚问题可能是什么。
我的安全配置:
@Override
public void configure(HttpSecurity http) throws Exception
http
...
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf().csrfTokenRepository(csrfTokenRepository());
private CsrfTokenRepository csrfTokenRepository()
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
我没有添加过滤器,因为正如我所说,请求没有到达它们。但如果需要,我会完成我的问题。希望对您有所帮助,在此先感谢您!
【问题讨论】:
【参考方案1】:原则上,Spring 中的 CSRF 机制将 CSRF 令牌存储在仅 HTTP cookie 中。因为 javascript 无法访问仅 HTTP 的 cookie,您需要告诉 spring 仅禁用 HTTP:
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
然后,您可以从 Angular 读取 cookie,并将其添加到每个请求的 XSRF-TOKEN 标头中。
这是一般情况。我不确定这是否适合您的特殊情况。
【讨论】:
【参考方案2】:假设您的配置/过滤器的其余部分正常工作,您将因此面临这个问题:SessionCreationPolicy.STATELESS
。
您可以了解 Spring CsrfFilter
的幕后花絮。您会看到它需要记住会话中每个用户的每个 CSRF-token 的值,并且由于您没有使用会话,因此无法完成。
接下来要做什么 - 完全取决于您。有人说如果你的应用是无状态的,实际上就不需要 CSRF 保护。 Spring docs 说 CSRF 攻击仍然相关。我认为这真的取决于您的身份验证机制。
例如,您可能还想查看this nice article。
希望对你有帮助。
【讨论】:
出于这个原因,我实现了我的自定义 CSRF 过滤器,但我的问题是请求甚至没有到达它。这很奇怪。 @AnarSultanov,你能分享你过滤器的代码吗?不过,这并不重要,因为您在 Spring 的CsrfFilter
之后添加了过滤器。如果 Springs 过滤器无法匹配来自用户会话的令牌(因为没有会话而无法获取),它会调用accessDeniedHandler
并执行return
(破坏过滤器链),这意味着它没有调用filterChain.doFilter(request, response);
,所以你的过滤器实际上不能被调用。如果您正在实施自己的 CSRF 保护 - 那么,我想,您应该通过调用 .csrf().disable()
来禁用 Spring 的实施。
我发现了我的问题。我在 cookie 中有 XSRF-TOKEN,但是 Angular 不会将带有此令牌的标头添加到请求中。现在我正试图找出原因以及如何解决这个问题。【参考方案3】:
非常感谢您的回答,他们确实帮助我找到了解决方案。如果将来有人会遇到同样的问题,我想分享我的解决方案。
如答案中所述,我使用了 SessionCreationPolicy.STATELESS
并且没有会话,因此我不得不使用 CookieCsrfTokenRepository
和 withHttpOnlyFalse()
而不是 HttpSessionCsrfTokenRepository
以允许 AngularJS 读取 cookie。
结果,我有这样的配置:
@Override
public void configure(HttpSecurity http) throws Exception
http
...
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf().csrfTokenRepository(csrfTokenRepository());
如果有人对 CsrfHeaderFilter 的外观感兴趣:
public class CsrfHeaderFilter extends OncePerRequestFilter
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null)
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie==null || token!=null && !token.equals(cookie.getValue()))
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
filterChain.doFilter(request, response);
我的第二个问题是 CORS。 AngularJS 文档说:
“不会为跨域请求设置标头。”
为了解决这个问题,我不得不使用 HTTP 拦截器:
.factory('XsrfInterceptor', function ($cookies)
return
request: function (config)
var headerName = 'X-XSRF-TOKEN';
var cookieName = 'XSRF-TOKEN';
config.headers[headerName] = $cookies.get(cookieName);
return config;
;
);
.config(['$httpProvider', function($httpProvider)
$httpProvider.interceptors.push('XsrfInterceptor');
]);
希望我的回答对你有用。
【讨论】:
以上是关于用于 Spring Security 和 AngularJS 的 CSRF/XSRF 保护的主要内容,如果未能解决你的问题,请参考以下文章
Spring Security 自定义登录功能不适用于 Spring Boot 和 REST api
用于 Spring Security 和/或 Spring BlazeDS 集成的集中式会话管理(和终止)系统
用于非授权连接的 Spring Security REST Api
用于身份验证的 Grails Spring Security X509 和用于权限的 LDAP