I have a Spring Boot project with working REST endpoints that are secured with a JWT filter. A GET request for example would require a bearer token and then return the users data. All the REST endpoints work as intended. Now we wanted to implement GraphQL queries for future data fetching.
This is where the problem comes in. If the /graphql endpoint is not secured in the Security config
.authorizeHttpRequests(
authorizeRequests ->
authorizeRequests
.requestMatchers("/graphql/**")
.permitAll() // Whitelist
.anyRequest().authenticated() // Everything else should be authenticated
)
the query goes through and i get my wanted data. But if i now take the GraphQL endpoint out of this "Whitelist" every request, with or without a valid access token, is coming back as a 403. There also is no error message in Postman or the IntelliJ console where i could trace back where this code comes from.
For further resources, this is my full security config:
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final AuthenticationProvider authenticationProvider;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(
authorizeRequests ->
authorizeRequests
.requestMatchers("/graphql/**")
.permitAll()
.anyRequest().authenticated()
)
.sessionManagement(
sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
and this my JWT filter:
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull FilterChain filterChain)
throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String userEmail;
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
jwt = authHeader.substring(7);// beginIndex is 7 bc "Bearer " is 7
userEmail = jwtService.extractUserEmail(jwt);
if (userEmail != null
&& SecurityContextHolder.getContext().getAuthentication() == null) { // check f if user is already authenticated
UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
It seems to be a problem caused by Spring security 6 (More details: https://github.com/spring-projects/spring-graphql/issues/594)
You can change your code like this:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(
authorizeRequests ->
authorizeRequests
.requestMatchers("/graphql/**")
.permitAll()
.anyRequest().authenticated()
)
.sessionManagement(
sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthenticationFilter,UsernamePasswordAuthenticationFilter.class)
.securityContext((securityContext) -> securityContext.requireExplicitSave(false))
.build();
}
Just need to add : .securityContext((securityContext) -> securityContext.requireExplicitSave(false))