[英]Spring Security not sending CSRF token in REST Application
I'm new to Spring Security and I'm trying to understand the CSRF mechanism.我是 Spring Security 的新手,我正在尝试了解 CSRF 机制。 I have a Spring based application with Angular.我有一个基于 Spring 的 Angular 应用程序。 As far as I know, Spring will send a CSRF Token in a cookie on the first GET Request it recieves (called XSRF-TOKEN) and then on every subsequent request it will look for that token in another cookie called (X-XSRF-TOKEN).据我所知,Spring 会在它收到的第一个 GET 请求(称为 XSRF-TOKEN)上在 cookie 中发送一个 CSRF 令牌,然后在每个后续请求中它会在另一个名为(X-XSRF-TOKEN)的 cookie 中查找该令牌). My problem is that it doesn't generate a token and I can't figure out why.我的问题是它不生成令牌,我不知道为什么。 I'm using the latest Spring and Angular versions.我正在使用最新的 Spring 和 Angular 版本。
Link to the github repo链接到 github 仓库
I created a dummy application, in which I have a /home endpoint, which accepts GET request and then returns a dummy string.我创建了一个虚拟应用程序,其中有一个 /home 端点,它接受 GET 请求,然后返回一个虚拟字符串。 I've set the withCredentials: true on the Angular side and I've configured CookieCsrfTokenRepository.我在 Angular 端设置了 withCredentials: true 并且配置了 CookieCsrfTokenRepository。 I don't see the Set-cookie header containing the token.我没有看到包含令牌的 Set-cookie 标头。 What am I expected to do and what am I doing wrong?我应该做什么,我做错了什么?
Spring Security Configuration弹簧安全配置
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations())
.permitAll()
.requestMatchers("/home")
.permitAll()
.anyRequest()
.authenticated();
return http.build();
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setAllowedOrigins(Collections.singletonList("http://localhost:4200"));
corsConfiguration.setAllowedHeaders(Arrays.asList("Origin", "Access-Control-Allow-Origin", "Content-Type",
"Accept", "Authorization", "Origin, Accept", "X-Request-With", "Access-Control-Request-Method",
"Access-Control-Request-Headers", "XSRF-TOKEN", "X-XSRF-TOKEN"));
corsConfiguration.setExposedHeaders(Arrays.asList("Origin", "Content-Type", "Accept", "Authorization",
"Access-Control-Allow-Origin", "Access-Control-Allow-Credentials", "XSRF-TOKEN", "X-XSRF-TOKEN"));
corsConfiguration.setAllowedMethods(Arrays.asList(
HttpMethod.GET.name(), HttpMethod.POST.name(),
HttpMethod.PUT.name(), HttpMethod.PATCH.name(),
HttpMethod.DELETE.name(), HttpMethod.OPTIONS.name()
));
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new org.springframework.web.filter.CorsFilter(urlBasedCorsConfigurationSource);
}
}
Home Controller家庭控制器
@RestController
public class HomeResource {
@GetMapping("/home")
public String home() {
return "Home is working";
}
}
Angular有角的
home.service.ts家庭服务.ts
@Injectable({
providedIn: 'root'
})
export class HomeService {
apiHost: string = 'http://www.localhost:8000';
constructor(private http: HttpClient) {
}
public home(): Observable<string> {
return this.http.get(`${this.apiHost}/home`, {withCredentials: true, responseType: 'text'});
}
}
home.component.html主页.component.html
<p>{{home$ | async}}</p>
home.component.ts主页.component.ts
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
home$!: Observable<string>;
constructor(private homeService: HomeService) {
}
ngOnInit(): void {
this.home$ = this.homeService.home();
}
}
Please refer to this Migration documentation , and read it completely.请参考此迁移文档,并完整阅读。
In summary, Spring Security 6.0 defer the loading of the CsrfToken
, this means that it will not include the token automatically in the response.总之,Spring Security 6.0 推迟了CsrfToken
的加载,这意味着它不会在响应中自动包含令牌。
You have a few ways to solve this:你有几种方法可以解决这个问题:
@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
// set the name of the attribute the CsrfToken will be populated on
requestHandler.setCsrfRequestAttributeName(null);
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRequestHandler(requestHandler)
);
return http.build();
}
CsrfToken
from the request:创建一个从请求中检索CsrfToken
的控制器端点:@RestController
@RequestMapping("/csrf")
public class CsrfController {
@GetMapping
public void getCsrfToken(HttpServletRequest request) {
// https://github.com/spring-projects/spring-security/issues/12094#issuecomment-1294150717
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
csrfToken.getToken();
}
}
You might consider http.requestMatchers("/csrf").permitAll()
.您可能会考虑http.requestMatchers("/csrf").permitAll()
。
In all cases, you have simply to access the CsrfToken from request attributes.在所有情况下,您只需从请求属性访问 CsrfToken。
And, on the Angular side, you can create a APP_INITIALIZER
provider that will get the CSRF token when the application initializes:而且,在 Angular 方面,您可以创建一个APP_INITIALIZER
提供程序,它将在应用程序初始化时获取 CSRF 令牌:
function getCsrfToken(httpClient: HttpClient): () => Observable<any> {
return () => httpClient.get('/csrf').pipe(catchError((err) => of(null)));
}
@NgModule({
...
providers: [
{
provide: APP_INITIALIZER,
useFactory: getCsrfToken,
deps: [HttpClient],
multi: true,
},
],
...
})
export class AppModule {}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.