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