Reputation: 11
I'm getting a 403 on a PUT request even though the CSRF token and header look to be set properly
Spring Boot logs:
2023-04-14T10:19:06.134+10:00 DEBUG 19528 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Securing PUT /api/incidents/1
2023-04-14T10:19:06.134+10:00 TRACE 19528 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking DisableEncodeUrlFilter (1/12)
2023-04-14T10:19:06.134+10:00 TRACE 19528 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (2/12)
2023-04-14T10:19:06.134+10:00 TRACE 19528 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderFilter (3/12)
2023-04-14T10:19:06.134+10:00 TRACE 19528 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (4/12)
2023-04-14T10:19:06.134+10:00 TRACE 19528 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking CsrfFilter (5/12)
2023-04-14T10:19:06.135+10:00 DEBUG 19528 --- [nio-8080-exec-2] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost:8080/api/incidents/1
2023-04-14T10:19:06.135+10:00 DEBUG 19528 --- [nio-8080-exec-2] o.s.s.w.access.AccessDeniedHandlerImpl : Responding with 403 status code
This is the JS code issuing the PUT request:
updateIncident(incident: Incident): Observable<any> {
const url = `${this.incidentsUrl}/${incident.number}`;
return this.http.put(url, incident).pipe(
tap(_ => this.log(`updated incident number=${incident.number}`)),
catchError(this.handleError<any>('updateIncident'))
);
}
And this is the WebSecurityConfig code:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
/****************************************************************************/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/", "/home", "/contact").permitAll()
.requestMatchers("/css/**", "/scripts/**", "/images/**").permitAll()
.anyRequest().authenticated()
).formLogin((form) -> form.loginPage("/login").permitAll()
).logout((logout) -> logout.permitAll().logoutSuccessUrl("/login"));
return http.build();
}
What's in the Chrome Network tab:
**Headers:**
**General:**
Request URL: http://localhost:8080/api/incidents/1
Request Method: PUT
Status Code: 403
Remote Address: [::1]:8080
Referrer Policy: strict-origin-when-cross-origin
**Response Headers:**
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: keep-alive
Content-Type: application/json
Date: Fri, 14 Apr 2023 00:28:56 GMT
Expires: 0
Keep-Alive: timeout=60
Pragma: no-cache
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
**Request Headers:**
t/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 400
Content-Type: application/json
Cookie: JSESSIONID=0D60F7FD506D0E4A6826CB64FEF7099D; XSRF-TOKEN=8f5a726a-e3a3-4c47-9ff2-bb41499c2e2a
Host: localhost:8080
Origin: http://localhost:8080
Referer: http://localhost:8080/incident-details/1
sec-ch-ua: "Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36
X-XSRF-TOKEN: 8f5a726a-e3a3-4c47-9ff2-bb41499c2e2a
**Cookies:**
JSESSIONID 0D60F7FD506D0E4A6826CB64FEF7099D localhost / Session 42 ✓ Medium
XSRF-TOKEN 8f5a726a-e3a3-4c47-9ff2-bb41499c2e2a localhost / Session 46 Medium
I know this has been asked a million times but none of the answers I've read have helped except for csrf().disable().
I'm new to this but I've read the docs and as far as I can tell it the request looks good. What am I missing? Thanks :)
Upvotes: 0
Views: 908
Reputation: 11
This worked for me: https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#servlet-defer-loading-csrf-token-opt-out
Here's my code:
/****************************************************************************/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
// set the name of the attribute the CsrfToken will be populated on
requestHandler.setCsrfRequestAttributeName("_csrf");
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/", "/home", "/contact").permitAll()
.requestMatchers("/css/**", "/scripts/**", "/images/**").permitAll()
.anyRequest().authenticated())
.formLogin((form) -> form.loginPage("/login").permitAll())
.logout((logout) -> logout.permitAll().logoutSuccessUrl("/"))
.csrf((csrf) -> csrf
.csrfTokenRepository(tokenRepository)
.csrfTokenRequestHandler(requestHandler)
)
.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
return http.build();
}
/****************************************************************************/
private static final class CsrfCookieFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
csrfToken.getToken();
filterChain.doFilter(request, response);
}
}
Upvotes: 1