@Configuration
public class SecurityWebConfig {
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("admin").password("{noop}111").roles("SUPER_ADMIN").build());
return manager;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.mvcMatchers("/index").permitAll()
.mvcMatchers("/hello").authenticated()
.and().formLogin();
http.rememberMe();
// session 管理
http.sessionManagement() // 开启会话管理
.maximumSessions(1) // 允许同一个用户只允许创建一个会话
.expiredSessionStrategy(new SessionExpiredHandler()) // session 失效处理类
.sessionRegistry(sessionRegistry()) // session 存储策略
.maxSessionsPreventsLogin(false);// 登录之后禁止再次登录
// 将生成 csrf 放入到cookie 中
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// 跨域处理方案
http.cors().configurationSource(configurationSource());
// 异常处理,认证异常和授权异常
return http.build();
}
/**
* 跨域资源配置
* @return
*/
public CorsConfigurationSource configurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList("*"));
corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
corsConfiguration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
}
前后端分离
首先随便发起一次请求获取 XSRF-TOKEN
发送请求携带令牌即可
- 请求参数中携带令牌
key: _csrf
value:"xxx"
- 请求头中携带令牌
X-XSRF-TOKEN:value
源码解析
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
boolean missingToken = (csrfToken == null);
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not protect against CSRF since request did not match "
+ this.requireCsrfProtectionMatcher);
}
filterChain.doFilter(request, response);
return;
}
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
this.logger.debug(
LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken)
: new MissingCsrfTokenException(actualToken);
this.accessDeniedHandler.handle(request, response, exception);
return;
}
filterChain.doFilter(request, response);
}
请求参数中携带令牌
request.getParameter()获取不到数据的问题_zhanghuiyu01的博客-CSDN博客
由于请求参数为 JSON,所以 request.getParameter(csrfToken.getParameterName()) 获取不到 请求参数中的 _csrf,此次请求将会被拒绝。但是如果是 GET 请求就不会有问题。
总结:POST 请求必须将令牌写道 Header 中,GET 请求写在请求头或者请求参数中都是可以的