使用 Spring Security 和 AngularJS 预防 CSRF
Posted
技术标签:
【中文标题】使用 Spring Security 和 AngularJS 预防 CSRF【英文标题】:CSRF Prevention with Spring Security and AngularJS 【发布时间】:2020-03-13 15:57:12 【问题描述】:我使用的是 Spring 4.3.12.RELEASE 版本,AngularJS 1.4.8。我正在尝试阻止对应用程序的 CSRF 攻击。
@Configuration
@Order(2)
public static class SecurityConfig extends WebSecurityConfigurerAdapter
String[] pathsToRemoveAuthorizaton =
"/mobile/**",
"/logout",
"/j_spring_security_logout",
"/login",
;
private final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
@Override
public void configure(HttpSecurity http) throws Exception
logger.info("http configure");
http.antMatcher("/**").authorizeRequests().antMatchers(pathsToRemoveAuthorizaton).permitAll()
.antMatchers("/**").authenticated()
.and().formLogin().loginPage("/login")
.usernameParameter("employeeId").passwordParameter("password")
.successForwardUrl("/dashboard").defaultSuccessUrl("/dashboard", true)
.successHandler(customAuthenticationSuccessHandler()).loginProcessingUrl("/j_spring_security_check")
.and().logout().logoutSuccessUrl("/logout").logoutUrl("/j_spring_security_logout")
.logoutSuccessHandler(customLogoutSuccessHandler()).permitAll().invalidateHttpSession(true)
.deleteCookies("JSESSIONID").and().sessionManagement().sessionFixation().newSession()
.maximumSessions(1).maxSessionsPreventsLogin(true).and()
.sessionCreationPolicy(SessionCreationPolicy.NEVER).invalidSessionUrl("/logout").and()
.exceptionHandling().accessDeniedPage("/logout");
// http.csrf().csrfTokenRepository(csrfTokenRepository()).and()
// .addFilterAfter(new StatelessCSRFFilter(), CsrfFilter.class);
http.csrf().csrfTokenRepository(csrfTokenRepository());
// http.csrf().disable();
// http.csrf().ignoringAntMatchers("/mobile/**");
http.authorizeRequests().anyRequest().authenticated();
private CsrfTokenRepository csrfTokenRepository()
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
@Bean
public AuthenticationSuccessHandler customAuthenticationSuccessHandler()
return new CustomAuthenticationSuccessHandler();
@Bean
public LogoutSuccessHandler customLogoutSuccessHandler()
return new CustomLogoutSuccessHandler();
以下是我的角度服务代码
govtPMS.service('Interceptor', function($q, $location, $rootScope, pinesNotifications, Auth)
return
request: function(config)
config.headers.Authorization = 'Bearer '+$rootScope.authToken;
// document.cookie = 'CSRF-TOKEN=' + $rootScope.generateKey;
return config;
,
requestError: function (rejection)
return $q.reject(rejection);
,
response: function(res)
if(res.status === 200 || res.status === 201)
if(res.data.response !== undefined)
if(res.data.status === 1 || res.data.status === 3 || res.data.status === 2)
pinesNotifications.notify(
'title': 'Success',
'text': res.data.message,
'type': 'success',
'delay': 5000
);
else if(res.data.status === 5)
pinesNotifications.notify(
'title': 'Warning',
'text': res.data.message,
'type': 'warning',
'delay': 5000
);
return res || $q.when(res);
,
responseError: function(error)
return $q.reject(error);
;
).config(['$httpProvider', function($httpProvider)
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';
$httpProvider.defaults.xsrfCookieName = 'CSRF-TOKEN';
$httpProvider.interceptors.push('Interceptor');
])
我仍然无法看到 CSRF 令牌标头以及请求。
在这个应用程序中,我们使用了 3 个 jsp 页面 - login.jsp、logout.jsp 和 dashboard.jsp 角度范围在dashboard.jsp 中定义,因此登录和注销超出了AngularJS 的范围。 我还尝试了this 和this 示例中的无状态方式,其中 angular 生成 UUID 并附加 cookie 和请求标头,下面的过滤器做得很好。 直到注销攻击。在此攻击中,攻击者试图成功注销用户,因为要从应用程序中注销,我们只是使用了 href。
<li><a href="j_spring_security_logout" ><i class="fa fa-sign-out"></i><span>Logout</span></a></li>
现在,由于它的注销没有角度,所以 angularjs 拦截器无法在此处附加 UUID。 自上周以来,我一直在为此苦苦挣扎,我们将不胜感激。
StatelessCSRFFilter.java
package com.leadwinner.sms.config.filters;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.web.filter.OncePerRequestFilter;
public class StatelessCSRFFilter extends OncePerRequestFilter
private static final String CSRF_TOKEN = "CSRF-TOKEN";
private static final String X_CSRF_TOKEN = "X-CSRF-TOKEN";
private final AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException
List<String> excludedUrls = new ArrayList<>();
excludedUrls.add("/resources");
excludedUrls.add("/j_spring_security_check");
excludedUrls.add("/j_spring_security_logout");
excludedUrls.add("/login");
excludedUrls.add("/logout");
excludedUrls.add("/mobile");
excludedUrls.add("/migrate");
excludedUrls.add("/dashboard");
String path = request.getServletPath();
System.out.println(path);
AtomicBoolean ignoreUrl = new AtomicBoolean(false);
excludedUrls.forEach(url ->
if (request.getServletPath().startsWith(url.toLowerCase()) || request.getServletPath().equals("/"))
ignoreUrl.set(true);
);
if (!ignoreUrl.get())
final String csrfTokenValue = request.getHeader(X_CSRF_TOKEN);
final Cookie[] cookies = request.getCookies();
System.out.println("**************************************************");
System.out.println("--------------------------------------------------");
String csrfCookieValue = null;
if (cookies != null)
for (Cookie cookie : cookies)
if (cookie.getName().equals(CSRF_TOKEN))
csrfCookieValue = cookie.getValue();
System.out.println("csrfTokenValue = "+csrfTokenValue);
System.out.println("csrfCookieValue = "+csrfCookieValue);
System.out.println("--------------------------------------------------");
System.out.println("**************************************************");
if (csrfTokenValue == null || !csrfTokenValue.equals(csrfCookieValue))
accessDeniedHandler.handle(request, response, new AccessDeniedException(
"Missing or non-matching CSRF-token"));
return;
filterChain.doFilter(request, response);
【问题讨论】:
我有点困惑,因为您似乎正在使用 Spring Security 的 CSRF 保护(连接 CsrfTokenRepository),同时还禁用了保护(csrf().disable())。此外,您正在编写自己的 CSRF 过滤器。如果您尝试编写自己的 CSRF 保护,请您澄清一下吗?如果是的话,为什么Spring Security提供的还不够呢? 对不起。已被评论。我只需要使用 Spring 的 CSRF 保护方式,并为某些移动 API 禁用 CSRF。由于我不能这样做,我尝试了另一种通过角度拦截器在 Header 和 Cookie 中附加 CSRF 令牌的替代方法,除了注销页面之外,它工作正常,因为对于注销,href 正在使用。在 Spring 中启用 CSRF 保护的任何帮助,我可以禁用移动 API 的 CSRF 检查就足够了。 【参考方案1】:如果可以通过浏览器发出请求并且自动提交凭据(会话 cookie、基本身份验证凭据),则 CSRF 保护是必要的,即使使用移动 API。
鉴于您将移动 API 作为应用程序的一部分,问题是浏览器能否成功处理这些 API?
我建议您创建两个单独的过滤器链,就像这样,一个用于网络应用程序,一个用于移动 API:
@Configuration
@Order(100)
public class WebAppConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http)
http
.requestMatchers()
.antMatchers("/app/**")
.and()
.authorizeRequests()
// ...
.anyRequest().authenticated()
.and()
.formLogin();
@Configuration
@Order(101)
public class MobileApiConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http)
http
.requestMatchers()
.antMatchers("/api/**")
.and()
.authorizeRequests()
// ...
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
这样做的目的是将两种配置分开。与 Web 应用程序相关的端点使用一种设置,而与移动 API 相关的端点使用另一种设置。
现在,有几个关于移动 API 的问题。我假设您正在使用 OAuth 2.0 Bearer 令牌进行身份验证,这就是上面的配置使用 Spring Security 5.1+ 中的 oauth2ResourceServer()
的原因。这样做是有选择地为包含 Authorization: Bearer xyz
标头的请求禁用 CSRF。
但是,由于您使用的是 Spring Security 4.3,因此您可能需要执行以下操作(除非您可以升级):
@Order(101)
public class MobileApiConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http)
http
.requestMatchers()
.antMatchers("/api/**")
.and()
.authorizeRequests()
// ...
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(NEVER)
.and()
.addFilterBefore(new MyMobileAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable();
不过,您需要确保的是,您的自定义身份验证过滤器不使用浏览器自动从任何来源(会话 cookie,Authorization: Basic
)发送的身份验证机制。
【讨论】:
1.移动 API,我们不包括在此配置中,有一个 MobileSessionAspector 正在拦截我们正在检查 JWT 令牌授权的所有移动相关 API。 2. 我已经在使用 MultiHttpSecurity Config。 ***.com/questions/58231064/… 3.我需要做的就是在 Spring 和 AngularJS 中启用 CSRF,以便保护所有请求(API)和注销功能免受 CSRF 攻击,并且我可以选择指定可以排除的模式列表CSRF 保护。【参考方案2】:`嗨湿婆,
您在 SecurityConfig 的 configure 方法中的代码应该如下所示:
http
.authorizeRequests()
.antMatchers(patterns)
.permitAll()
.antMatchers("/hello/**")
.hasRole("USER")
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.requireCsrfProtectionMatcher(csrfProtectionMatcher(patterns))
.and()
.httpBasic()
.and()
.addFilterAfter(csrfFilter(patterns), FilterSecurityInterceptor.class)
.addFilterAfter(new StatelessCSRFFilter(), CsrfFilter.class);
在StatelessCSRFFilter中,使用如下代码:
@Override
public void init(FilterConfig filterConfig) throws ServletException
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
CsrfToken csrf = (CsrfToken) servletRequest.getAttribute(CsrfToken.class.getName());
String token = csrf.getToken();
if (token != null && isAuthenticating(servletRequest))
HttpServletResponse response = (HttpServletResponse) servletResponse;
Cookie cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
filterChain.doFilter(servletRequest, servletResponse);
private boolean isAuthenticating(ServletRequest servletRequest)
HttpServletRequest request = (HttpServletRequest) servletRequest;
return request.getRequestURI().equals("/login");
`
【讨论】:
你也可以定义 csrfFilter(patterns) 方法吗?【参考方案3】:<pre>
Add this code for the csrfTokenRepository method
private CsrfTokenRepository csrfTokenRepository()
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
Add this for the csrfFilter method
private Filter csrfFilter(String[] patterns)
CsrfFilter csrfFilter = new CsrfFilter(csrfTokenRepository());
csrfFilter.setRequireCsrfProtectionMatcher(csrfProtectionMatcher(patterns));
return csrfFilter;
Add this for the csrfProtectionMatcher method
private NoAntPathRequestMatcher csrfProtectionMatcher(String[] patterns)
return new NoAntPathRequestMatcher(patterns);
Also remove these lines in configure method
.csrfTokenRepository(csrfTokenRepository())
.requireCsrfProtectionMatcher(csrfProtectionMatcher(patterns))
Move these lines below .csrf() in configure method:
.and()
.addFilterAfter(csrfFilter(patterns), FilterSecurityInterceptor.class)
</pre>
【讨论】:
以上是关于使用 Spring Security 和 AngularJS 预防 CSRF的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Spring Security 模拟身份验证和授权
如何使用 Spring-Security 3 和 Hibernate 4 将 spring security xml 配置 hibernate 转换为 java config
Spring Framework,Spring Security - 可以在没有 Spring Framework 的情况下使用 Spring Security?
使用 Spring 和 Spring Security 正确注入 SessionFactory
如何使用spring-security通过用户名和密码登录?
Spring 框架 4.0 和 Spring security 3.2.4 上的 Spring Security SAML 扩展