First of all my setup. I am using Spring-boot 3.1 with spring security. The api is the spring boot application while Angular is the client application both running on different hosts. Spring boot runs on host http://api.example.com while angular runs on http://client.example.com.
This is my configuration for CORS at the moment.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://client.example.com"));
// tried this too but without any luck.
// configuration.setAllowedOrigins(Arrays.asList("localhost:4200"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return httpSecurity.build();
}
}
AS from the spring documentation CORS has to be configured first.
I have two kinds of requests one that fetches resources from the database(this one passes cors with the above configuration). And one that uses WebClient to make a request to keycloak to perform the login operation(this one is failing due to CORS).
By now i understand or i think i understand that WebFlux uses Webclient. That means that i have to configure security for webflux???
And this is where i am loosing it. I tried the bellow configuration but without any luck.
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://client.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true);
}
}
console error Access to XMLHttpRequest at 'http://api.example.com/login' from origin 'http://client.example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Any suggestions?
I finally manage to get through this.
My first assumption that it was a problem coming from the internal request to keycloak was wrong. I understood that when i created another dummy post request. I had the same issue.
Digging through tons of answers on the web and reading again and again the documentation there was one option i did not follow. The CorsFilter.
After implementing the CorsFilter thought i had the same issue....then just by pure luck i found an issue with my nginx configuration. When i fixed that CorsFilter work like a charm form me. So this is the full implementation hope that will help someone in the future.
One more time my set up:
Spring-boot 3.1 with spring-security enabled this is all that maters at the time.
SecurityConfig.java
// other security configuration
....
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return httpSecurity.build();
}
CorsFilter.java
public class CorsFilter implements Filter {
private final Logger log = LoggerFactory.getLogger(CorsFilter.class);
public CorsFilter() {
log.info("CorsFilter Init");
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "http://www.your-domain.me");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(HttpServletResponse.SC_ACCEPTED);
return;
}
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void destroy() {}
}
This is the configuration that worked for me. Enjoy!