Java 如何强制Spring安全性更新XSRF-TOKEN cookie?

Java 如何强制Spring安全性更新XSRF-TOKEN cookie?,java,spring,rest,spring-mvc,spring-security,Java,Spring,Rest,Spring Mvc,Spring Security,当用户进行身份验证时,Spring引导应用程序中的REST Spring安全/user服务无法立即更新XSRF-TOKENcookie。这会导致对/任何其他REST服务url的下一个请求返回无效CSRF证书错误,直到再次调用/user服务。如何解决此问题,以便REST/user服务在同一请求/响应事务中正确更新XSRF-TOKEN cookie,在该请求/响应事务中,REST首先对用户进行身份验证 后端REST/user服务被前端应用程序调用三次,但/user服务仅在第一次和第三次调用时返回匹配

当用户进行身份验证时,Spring引导应用程序中的REST Spring安全
/user
服务无法立即更新
XSRF-TOKEN
cookie。这会导致对
/任何其他REST服务url
的下一个请求返回
无效CSRF证书
错误,直到再次调用
/user
服务。如何解决此问题,以便REST
/user
服务在同一请求/响应事务中正确更新XSRF-TOKEN cookie,在该请求/响应事务中,REST首先对用户进行身份验证

后端REST
/user
服务被前端应用程序调用三次,但
/user
服务仅在第一次和第三次调用时返回匹配的
JSESSIONID/XSRF-TOKEN
cookie,而不是在第二次调用时返回

  • 在向服务器发出的第一个请求中,没有任何凭据(没有用户名或密码)发送到
    /
    url模式,我认为这会调用
    /user
    服务,服务器会使用与匿名用户关联的
    JSESSIONID
    XSRF-TOKEN
    进行响应。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令牌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
    。这会导致不匹配,导致对其他rest服务的后续请求中出现
    403
    错误(由于无效的csrf令牌),直到第三次调用
    /user
    服务解决此问题。我们是否有办法强制前面的
    200 get user
    也返回新的
    XSRF-TOKEN

  • 对后端REST
    /user
    服务的第三次调用使用的用户名和密码凭据与上面显示的第二次请求中使用的用户名和密码凭据非常相同,但是对
    /user
    的第三次调用会导致
    XSRF_令牌
    cookie得到正确更新,同时保留相同的正确
    JSESSIONID
    。以下是FireFox开发者工具的网络选项卡显示的内容:

    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令牌现在与jsessionid匹配,因此对其他后端rest服务的后续请求现在可以成功

    在登录表单第一次使用正确的用户名和密码调用
    /user
    服务时,可以对下面的代码进行哪些具体更改,以强制更新
    XSRF-TOKEN
    JSESSIONID
    cookies?我们是否在Spring中对后端
    /user
    方法的代码进行了特定更改?还是在安全配置类中进行了更改?我们可以试着解决这个问题吗

    后端
    /user
    服务的代码和安全配置位于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安全类的某些组合的配置,所有这些都在
    UiApplication.java
    中定义,如下所示:

  • 这个,

  • 这个,

  • 这个,

  • 和/或

  • /user
    服务返回的

  • 但是需要做哪些具体的改变来解决这个问题呢?

    更新的答案 您获得401的原因是,当用户注册时,在请求中可以找到基本身份验证头。这意味着Spring Security尝试验证凭据,但用户尚未出现,因此它会以401响应

    你应该

    • 使/register端点公开,并提供注册用户的控制器
    • 不要在授权标头中包含注册表的用户名/密码,因为这将导致Spring Security尝试验证凭据。而是将参数作为JSON或表单编码的参数包含在/register控制器处理的过程中
    原始答案 验证后,Spring Security使用
    CsrfAuthenticationStrategy
    使任何CsrfToken无效(以确保不可能发生会话固定攻击)。这就是触发要使用的新CsrfToken的原因

    但是,问题是在执行身份验证之前调用了
    csrfTokenRepository
    。这意味着当
    csrfTokenRepository
    检查令牌是否已更改时,如果结果为false(它尚未更改)

    要解决此问题,可以插入自定义的
    AuthenticationSuccessHandler
    。例如:

    公共类MyAuthenticat