I have upgraded Springboot from 2.7.17 to 3.1.5. I have updated the security config classes by following the guide here. Some issues arose which I was able to fix. However, Now, I'm stuck with 403 forbidden issue for apis with http methods PUT or POST.
Apis which are failing:
POST /app/order/comment
PUT /app/order
Apis which are successful :
All apis with GET.
My Security Config
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableConfigurationProperties(AppConfigProperties.class)
@EnableMethodSecurity(prePostEnabled = true)
public class WebappSecurityConfiguration {
private static final String CUSTOM_CSRF_COOKIE_NAME = "CUSTOM-XSRF-TOKEN";
@Bean
@Order(1)
SecurityFilterChain internalAPISecurityConfig(HttpSecurity http, AppConfigProperties configProps) throws Exception {
String camundaWebAppUrlPattern = "/camunda/**";
CookieCsrfTokenRepository csrfRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
csrfRepository.setCookieName(CUSTOM_CSRF_COOKIE_NAME);
http
.csrf(csrf -> csrf
.ignoringRequestMatchers(AntPathRequestMatcher.antMatcher(camundaWebAppUrlPattern))
.csrfTokenRepository(csrfRepository)
)
.authorizeHttpRequests(authorize -> {
authorize.requestMatchers( AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/favicon.ico")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher(camundaWebAppUrlPattern)).hasRole("ADMIN")
.requestMatchers(AntPathRequestMatcher.antMatcher("/app/**")).hasAnyRole("ADMIN", "USER")
.requestMatchers(AntPathRequestMatcher.antMatcher( "/engine-rest/**")).hasAnyRole("ADMIN", "USER")
.requestMatchers(AntPathRequestMatcher.antMatcher("/error")).authenticated()
.anyRequest().denyAll();
}
)
.logout(logout -> logout
.logoutUrl("/app/logout")
// Redirect URL from config
.logoutSuccessUrl(configProps.getLogoutUrl())
)
;
http.apply(new RequestHeaderAuthenticationConfigurer());
return http.build();
}
My Controller :
@RestController
@RequestMapping("/app")
public class OrderController {
@Autowired
private ProductOrderService productOrderService;
@Autowired
private AuditService auditService;
@PutMapping("/activities")
@PreAuthorize("!hasRole('ROLE_VISITOR')")
public CustomResponse activityUpdate(@RequestBody ActivitiesUpdateDto activitiesUpdateDto)
throws JsonProcessingException {
return productOrderService.activityUpdate(activitiesUpdateDto);
}
@GetMapping("/orders/products")
public List<OrderDto> getOrders(@RequestParam("search") Optional<String> searchTerm) {
return productOrderService.fetchOrders(searchTerm.isPresent() ? searchTerm.get() : null);
}
@PutMapping("/order")
@PreAuthorize("!hasRole('ROLE_VISITOR')")
public CustomResponse updateOrder(@RequestBody OrderUpdateDto orderUpdateDto) {
return productOrderService.updateOrder(orderUpdateDto);
}
@PostMapping("/order/comment")
public CustomerResponse addComment(@RequestBody CommentDto commenteDto) {
return productOrderService.addComment(commenteDto);
}
}
For all api calls, a request header with header name "Cookie" is being sent.
Cookie: Idea-56ec4e87=6d30aeb7-9c46-4d74-be5b-18c896652a0c; CUSTOM-XSRF-TOKEN=527851d7-ac94-424a-b55f-bf7b6a1c1d6c; JSESSIONID=9A9C968680E61F50CD936B6C7199F631
Security config before the springboot upgrade:
SecurityFilterChain internalAPISecurityConfig(HttpSecurity http, AppConfigProperties configProps)
throws Exception {
String camundaWebAppUrlPattern = "/camunda/**";
String[] staticRessourceUrlPatterns = new String[] { "/favicon.ico" };
CookieCsrfTokenRepository csrfRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
csrfRepository.setCookieName(CUSTOM_CSRF_COOKIE_NAME);
http
.csrf(csrf -> csrf
.ignoringAntMatchers(camundaWebAppUrlPattern)
.csrfTokenRepository(csrfRepository)
)
.authorizeHttpRequests(authorize -> authorize
.antMatchers(HttpMethod.GET, staticRessourceUrlPatterns).permitAll()
.antMatchers(camundaWebAppUrlPattern).hasRole("ADMIN")
.antMatchers("/app/**", "/engine-rest/**").hasAnyRole("ADMIN", "USER")
.antMatchers("/error").authenticated()
.anyRequest().denyAll()
)
.logout(logout -> logout
.logoutUrl("/app/logout")
.logoutSuccessUrl(ConfigProps.getLogoutUrl())
);
http.apply(new RequestHeaderAuthenticationConfigurer());
return http.build();
}
I would be thankful for any help to resolve this issue.
What have I tried so far
I have disabled the csrf using csrf.disable(). Then all apis are accessible. However, it does not meet client's requirement. So, I need to keep the csrf enabled.
Removed this annotation @PreAuthorize("!hasRole('ROLE_VISITOR')") for a POST method addComment(@RequestBody CommentDto commenteDto). Still this was failing with 403
Changed the annotation @PreAuthorize("!hasRole('ROLE_VISITOR')") to @PreAuthorize("hasRole('USER') or hasRole('ADMIN')"). Still the same issue. Even tried by removing ROLE_ prefix. Same result.
Based on Migration Guide.
In Spring Security 5, the default behavior is that the CsrfToken will be loaded on every request. This means that in a typical setup, the HttpSession must be read for every request even if it is unnecessary.
In Spring Security 6, the default is that the lookup of the CsrfToken will be deferred until it is needed.
As a fix you have to change a little bit internalAPISecurityConfig
, exactly next one change:
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
// set the name of the attribute the CsrfToken will be populated on
requestHandler.setCsrfRequestAttributeName("_csrf");
And then in configuration
part also set csrfTokenRequestHandler
http
.csrf(csrf -> csrf
.ignoringAntMatchers(camundaWebAppUrlPattern)
.csrfTokenRepository(csrfRepository)
.csrfTokenRequestHandler(requestHandler)
)
Also more discussion about this topic you can find csrf-protection-not-working-with-spring-security-6