当前位置: 首页 > 工具软件 > Spring4Me > 使用案例 >

Spring security开启remember me功能,配置session超时,导致csrf验证失败

袁赞
2023-12-01

概述

当开启remember me功能,且配置session超时,当session超时后,会清空session,刷新页面可以正常实现记住我的功能,但是存放到session中的csrf token已被清空,会提示csrf校验异常,跳转到登录页面,导致remember me功能无法实现。

配置remember me + session超时

后端

public class HttpSecurityConfigurer {
    @Override
    public void configure(HttpSecurity http) throws Exception {
          http
              //...
              .anyRequest().authenticated()
              .and().rememberMe().tokenValiditySeconds(86400)
              //...
    }
}
# resources/config/application-dev.yml
server:
    port: 8080
    servlet:
        session:
            timeout: 80s # 超时时间80s

5.2.2前端

登录的URL中增加 remember-me请求参数

&remember-me=${encodeURIComponent(data['remember-me'])}`;

解决方案

移动端无法读取cookie中的csrf的token增加到请求的header中,可以使用本解决方案。

只有在用户登录成功后,才生成csrf的token增加到cookie中,并且将csrf的token增加到用户响应信息中。前端发起请求的时候,从用户信息中获取csrf的token,增加到请求的header中。

5.4.2.后端



import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import com.fasterxml.jackson.databind.ObjectMapper;



/**
 * 登录成功后保持token
 *
 * 
 */
public class AjaxAuthenticationSuccessCustomizerHandler extends AjaxAuthenticationSuccessHandler {

    private ObjectMapper mapper;

    private CsrfTokenRepository csrfTokenRepository;

    private OnAuthenticationSuccessResponseProvider successResponseProvider;

    public AjaxAuthenticationSuccessCustomizerHandler(ObjectMapper mapper,
                                                      OnAuthenticationSuccessResponseProvider successResponseProvider) {
        super(mapper, successResponseProvider);
    }

    public AjaxAuthenticationSuccessCustomizerHandler(ObjectMapper mapper,
                                                      OnAuthenticationSuccessResponseProvider successResponseProvider,
                                                      CsrfTokenRepository csrfTokenRepository) {
        super(mapper, successResponseProvider);
        this.mapper = mapper;
        this.successResponseProvider = successResponseProvider;
        this.csrfTokenRepository = csrfTokenRepository;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication)
        throws IOException, ServletException {

        response.setStatus(HttpServletResponse.SC_CREATED);
       
        // 用户响应信息设置TOKEN

        clearAuthenticationAttributes(request);
    }

    private String generateToken(HttpServletRequest request, HttpServletResponse response) {
        CsrfTokenCustomizer newToken = (CsrfTokenCustomizer) csrfTokenRepository.generateToken(request);
        newToken.setCanSave(true);

        csrfTokenRepository.saveToken(newToken, request, response);

        request.setAttribute(CsrfToken.class.getName(), newToken);
        request.setAttribute(newToken.getParameterName(), newToken);

        return newToken.getToken();
    }
}


import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.csrf.CsrfTokenRepository;



/**
 * 删除csrfToken
 *
 */
public class AjaxLogoutSuccessCustomizerHandler extends AjaxLogoutSuccessHandler {

    private CsrfTokenRepository csrfTokenRepository;

    public AjaxLogoutSuccessCustomizerHandler(CsrfTokenRepository csrfTokenRepository) {
        this.csrfTokenRepository = csrfTokenRepository;
    }

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                Authentication authentication)
        throws IOException, ServletException {
        if (csrfTokenRepository instanceof CookieCsrfTokenCustomizerRepository) {
            CookieCsrfTokenCustomizerRepository repositoryCustomizer = (CookieCsrfTokenCustomizerRepository) csrfTokenRepository;
            repositoryCustomizer.clearToken(null, request, response);
        }

        super.onLogoutSuccess(request, response, authentication);
    }
}


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;

/**
 * 根据Token中标识判断是否可以保持
 *
 * 
 */
public class CookieCsrfTokenCustomizerRepository implements CsrfTokenRepository {

    private CsrfTokenRepository cookieCsrfTokenRepository;

    public CookieCsrfTokenCustomizerRepository(CookieCsrfTokenRepository cookieCsrfTokenRepository) {
        this.cookieCsrfTokenRepository = cookieCsrfTokenRepository;
    }

    @Override
    public CsrfToken generateToken(HttpServletRequest request) {
        CsrfToken csrfToken = cookieCsrfTokenRepository.generateToken(request);
        return new CsrfTokenCustomizer(csrfToken);
    }

    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
        if (token == null) {
            return;
        }

        if (token instanceof CsrfTokenCustomizer) {
            CsrfTokenCustomizer myCsrfToken = (CsrfTokenCustomizer) token;
            if (!myCsrfToken.isCanSave()) {
                return;
            }
        }

        cookieCsrfTokenRepository.saveToken(token, request, response);
    }

    public void clearToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
        cookieCsrfTokenRepository.saveToken(token, request, response);
    }

    @Override
    public CsrfToken loadToken(HttpServletRequest request) {
        return cookieCsrfTokenRepository.loadToken(request);
    }
}


import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import com.fasterxml.jackson.databind.ObjectMapper;


/**
 * csrf的配置
 *
 */
@Configuration
public class CsrfCustomizerConfiguration {


    @Bean
    public CsrfTokenRepository csrfTokenRepository() {
        CookieCsrfTokenRepository cookieCsrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
        cookieCsrfTokenRepository.setHeaderName("X-CSRF-TOKEN");
        cookieCsrfTokenRepository.setCookiePath("/");
        return new CookieCsrfTokenCustomizerRepository(cookieCsrfTokenRepository);
    }
}
import org.springframework.security.web.csrf.CsrfToken;

/**
 * 增加能否保持的标志
 *
 * 
 */
public class CsrfTokenCustomizer implements CsrfToken {

    private final CsrfToken csrfToken;

    private boolean canSave = false;

    public CsrfTokenCustomizer(CsrfToken csrfToken) {
        this.csrfToken = csrfToken;
    }

    @Override
    public String getHeaderName() {
        return this.csrfToken.getHeaderName();
    }

    @Override
    public String getParameterName() {
        return this.csrfToken.getParameterName();
    }

    @Override
    public String getToken() {
        return this.csrfToken.getToken();
    }

    public boolean isCanSave() {
        return canSave;
    }

    public void setCanSave(boolean canSave) {
        this.canSave = canSave;
    }
}

public class HttpSecurityConfigurer {
     //...
     private final CsrfTokenRepository csrfTokenRepository;

     public HttpSecurityConfigurer(//...
                                  CsrfTokenRepository csrfTokenRepository) {
        //...
        this.csrfTokenRepository = csrfTokenRepository;
    }

     @Override
    public void configure(HttpSecurity http) throws Exception {

        http
           .csrf()
       //  ...
           .csrfTokenRepository(csrfTokenRepository)
       //  ...
    }
}
 类似资料: