【发布时间】:2016-04-28 05:16:11
【问题描述】:
当用户进行身份验证时,Spring Boot 应用程序中的 REST Spring Security /user 服务无法立即更新 XSRF-TOKEN cookie。这导致/any-other-REST-service-url 的下一个请求返回Invalid CSRF certificate 错误,直到再次调用/user 服务。如何解决此问题,以便 REST /user 服务在首先验证用户身份的同一请求/响应事务中正确更新 XSRF-TOKEN cookie?
后端 REST /user 服务被前端应用程序调用了 3 次,但 /user 服务仅在第一次和第三次调用时返回匹配的 JSESSIONID/XSRF-TOKEN cookie,而不是在第二次调用时返回。
-
在对服务器的第一个请求中,没有凭据(没有用户名或密码)被发送到
/url 模式,我认为这称为/user服务,服务器以JSESSIONID和@ 响应987654338@ 与匿名用户相关联。 FireFox 开发人员工具的网络选项卡将这些 cookie 显示为:Response cookies: JSESSIONID:"D89FF3AD2ACA7007D927872C11007BCF" path:"/" httpOnly:true XSRF-TOKEN:"67acdc7f-5127-4ea2-9a7b-831e95957789" path:"/"然后,用户对可公开访问的资源发出各种请求而不会出错,FireFox 开发人员工具的“网络”选项卡会显示这些相同的 cookie 值。
-
对
/user服务的第二个请求是通过一个登录表单完成的,该表单发送一个有效的用户名和密码,/user服务使用它来验证用户身份。但是/user服务只返回一个更新的jsessionid cookie,并没有在这一步更新xsrf-token cookie。以下是 FireFox 开发者工具的网络选项卡中显示的 cookie:200 GET user在 FireFox 的“网络”选项卡中包含以下 cookie:Response cookies: JSESSIONID:"5D3B51A03B9AE218586591E67C53FB89" path:"/" httpOnly:true AUTH1:"yes" Request cookies: JSESSIONID:"D89FF3AD2ACA7007D927872C11007BCF" XSRF-TOKEN:"67acdc7f-5127-4ea2-9a7b-831e95957789"请注意,响应包含新的
JSESSIONID,但不包含新的XSRF-TOKEN。这会导致在对其他休息服务的后续请求中导致403错误(由于无效的csrf 令牌)的不匹配,直到通过对/user服务的第三次调用来解决此问题。有没有办法可以强制前面的200 get user也返回新的XSRF-TOKEN? -
对后端 REST
/user服务的第三次调用使用与上面显示的第二个请求中使用的完全相同的用户名和密码凭据,但是对/user的第三次调用导致XSRF_TOKENcookie 被正确更新,而相同的正确JSESSIONID被保留。以下是 FireFox 开发者工具的 Network 选项卡此时显示的内容:200 GET user表明不匹配的请求会强制更新响应中的XSRF-TOKEN:Response cookies: XSRF-TOKEN:"ca6e869c-6be2-42df-b7f3-c1dcfbdb0ac7" path:"/" AUTH1:"yes" Request cookies: JSESSIONID:"5D3B51A03B9AE218586591E67C53FB89" XSRF-TOKEN:"67acdc7f-5127-4ea2-9a7b-831e95957789"
更新后的 xsrf-token 现在与 jsessionid 匹配,因此对其他后端休息服务的后续请求现在可以成功。
可以对下面的代码进行哪些具体更改,以在登录表单第一次使用正确的用户名和密码调用/user 服务时强制更新XSRF-TOKEN 和JSESSIONID cookie?我们是否在 Spring 中对后端 /user 方法的代码进行了特定更改?还是在安全配置类中进行了更改?我们可以尝试什么来解决这个问题?
后端/user服务和Security Config的代码在Spring Boot后端应用的主应用类中,在UiApplication.java中如下:
@SpringBootApplication
@Controller
@EnableJpaRepositories(basePackages = "demo", considerNestedRepositories = true)
public class UiApplication extends WebMvcConfigurerAdapter {
@Autowired
private Users users;
@RequestMapping(value = "/{[path:[^\\.]*}")
public String redirect() {
// Forward to home page so that route is preserved.
return "forward:/";
}
@RequestMapping("/user")
@ResponseBody
public Principal user(HttpServletResponse response, HttpSession session, Principal user) {
response.addCookie(new Cookie("AUTH1", "yes"));
return user;
}
public static void main(String[] args) {
SpringApplication.run(UiApplication.class, args);
}
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.US);
return slr;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang");
return lci;
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
protected static class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter {
@Autowired
private Users users;
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(users);
}
}
@SuppressWarnings("deprecation")
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests()
.antMatchers("/registration-form").permitAll()
.antMatchers("/confirm-email**").permitAll()
.antMatchers("/submit-phone").permitAll()
.antMatchers("/check-pin").permitAll()
.antMatchers("/send-pin").permitAll()
.antMatchers("/index.html", "/", "/login", "/message", "/home", "/public*", "/confirm*", "/register*")
.permitAll().anyRequest().authenticated().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
private Filter csrfHeaderFilter() {
return new 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);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
}
显示CSRF错误的服务器日志的相关部分是:
2016-01-20 02:02:06.811 DEBUG 3995 --- [nio-9000-exec-5] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@70b8c8bb
2016-01-20 02:02:06.813 DEBUG 3995 --- [nio-9000-exec-5] o.s.security.web.FilterChainProxy : /send-pin at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
2016-01-20 02:02:06.813 DEBUG 3995 --- [nio-9000-exec-5] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost:9000/send-pin
我需要对上述代码进行哪些具体更改才能解决此CSRF 错误?
当后端/user 服务更改用户状态(登录、注销等)时,如何强制立即更新XSRF cookie?
注意:我猜测(基于我的研究)这个问题的解决方案将涉及更改以下 Spring Security 类的某些组合的配置,所有这些都定义在如下所示的UiApplication.java 中:
Principal 由
/user服务返回。
但是需要做出哪些具体的改变来解决这个问题呢?
【问题讨论】:
标签: java spring rest spring-mvc spring-security