I'm developing a web app where user can authenticate with OAuth2. In the web app there are a list of endpoints that are accessible only by sending requests (GET-POST-PUT-DELETE) including a valid token. Now I want to implement my web app by allowing users to authenticate with username and password. There is a form where user can Signup to the web app (they need to fill the form with the required data). So, once an user clicked on Signup, a POST request is sent to the URL localhost:8080/api/v1/accounts/register and their data are stored into the db. My problem is that the POST request works only if a valid token is included into the request. If I don't specify a valid token, I get an "error 401 unauthorized". If I send the request by including the token, I got 201. What I tried to do was to make the endpoint (/api/v1/accounts/register) accessible to everyone. But I still get the error 401.
Those are the classes involved into the authorization:
SecurityConfig.java:
package it.app.demoaimeetingroombe.configuration;
import it.app.demoaimeetingroombe.csrf.CsrfTokenCustom;
import it.app.demoaimeetingroombe.filter.AccountCheckFilterChain;
import it.app.demoaimeetingroombe.repository.AccountRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import java.util.Collections;
import java.util.List;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, AccountRepository accountRepository) throws Exception {
List<AntPathRequestMatcher> excludeFromCheck = List.of(
new AntPathRequestMatcher("/api/v1/accounts/authenticate"),
new AntPathRequestMatcher("/api/v1/rooms/*/bookings/daily/*"),
new AntPathRequestMatcher("/swagger-ui/**"),
new AntPathRequestMatcher("/swagger-ui.html"),
new AntPathRequestMatcher("/api/v1/accounts/register"),
new AntPathRequestMatcher("/v3/api-docs/**"),
new AntPathRequestMatcher("/api/v1/webauthn/**")
);
return http
.csrf().csrfTokenRepository(customCsrfTokenRepository())
.ignoringRequestMatchers(new AntPathRequestMatcher("/api/v1/webauthn/**"))
.and()
.authorizeHttpRequests(requests -> requests
.requestMatchers(
new AntPathRequestMatcher("/api/v1/rooms/*/bookings/daily/*"),
new AntPathRequestMatcher("/swagger-ui/**"),
new AntPathRequestMatcher("/swagger-ui.html"),
new AntPathRequestMatcher("/api/v1/accounts/register"),
new AntPathRequestMatcher("/v3/api-docs/**"),
new AntPathRequestMatcher("/api/v1/webauthn/**")).permitAll()
.anyRequest().authenticated()
)
.addFilterAfter(new AccountCheckFilterChain(accountRepository, excludeFromCheck), BearerTokenAuthenticationFilter.class)
.oauth2ResourceServer()
.jwt()
.and()
.and().cors().configurationSource(httpServletRequest -> {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("http://localhost:3000"));
corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
corsConfiguration.setExposedHeaders(Collections.singletonList("Authorization"));
corsConfiguration.setMaxAge(3600L);
return corsConfiguration;
})
.and().build();
}
private CsrfTokenRepository customCsrfTokenRepository() {
return new CsrfTokenCustom();
}
}
AccountCheckFilterChain.java:
package it.app.demoaimeetingroombe.filter;
import it.app.demoaimeetingroombe.repository.AccountRepository;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import java.io.IOException;
import java.util.List;
public class AccountCheckFilterChain implements Filter {
private final AccountRepository accountRepository;
private final List<AntPathRequestMatcher> excludeFromCheck;
public AccountCheckFilterChain(AccountRepository accountRepository, List<AntPathRequestMatcher> excludeFromCheck) {
this.accountRepository = accountRepository;
this.excludeFromCheck = excludeFromCheck;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpRequest = ((HttpServletRequest) servletRequest);
HttpServletResponse httpResponse = ((HttpServletResponse) servletResponse);
boolean shouldExclude = excludeFromCheck.stream().anyMatch(exclude -> exclude.matches(httpRequest));
if (!shouldExclude) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
setUnauthorized(httpResponse);
return;
}
String accountId = authentication.getName();
if (StringUtils.isBlank(accountId) || !accountRepository.existsByIdAndIsActiveTrue(accountId)) {
setUnauthorized(httpResponse);
return;
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
private static void setUnauthorized(HttpServletResponse httpResponse) {
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.setContentType("application/json");
httpResponse.setContentLength(0);
}
}
CsrfTokenCustom.java:
package it.app.demoaimeetingroombe.csrf;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.DefaultCsrfToken;
public class CsrfTokenCustom implements CsrfTokenRepository {
private final CookieCsrfTokenRepository delegate = CookieCsrfTokenRepository.withHttpOnlyFalse();
@Override
public CsrfToken generateToken(HttpServletRequest request) {
return delegate.generateToken(request);
}
@Override
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
delegate.saveToken(token, request, response);
}
@Override
public CsrfToken loadToken(HttpServletRequest request) {
CsrfToken token = delegate.loadToken(request);
if (isWebAuthnEndpoint(request)) {
String newToken = "ALLOW_ALL";
return new DefaultCsrfToken(token.getHeaderName(), token.getParameterName(), newToken);
}
return token;
}
private boolean isWebAuthnEndpoint(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return requestURI.startsWith("/api/v1/webauthn/");
}
}