I have a problem with configuring CORS in a microservices architecture application using Spring Boot. My application consists of the following components: • Frontend: Application written in Angular • Backend: Microservices running in Spring Boot • Nginx: Serves as a proxy – one Nginx for the frontend application (Angular) and another for the API • Gateway: An intermediary service (e.g. Spring Cloud Gateway) that forwards requests to the appropriate microservices. My problem occurs when I try to make a CORS request from the frontend application (Angular). Every time, although the OPTIONS request passes (with code 204), the browser returns a 403 Invalid CORS request error. I have tried to fix the problem, including improving the CORS configuration in both Nginx and Spring Security, but the error still persists. I understand that there may be various places where conflicts can occur, such as CORS configuration on the Nginx server level, API Gateway, and the microservices themselves. I want to make sure that CORS support is properly configured to ensure communication between the frontend and the backend. Has anyone had a similar problem and can share some tips or experience in this area?
Security config for target microservice.
public class SecurityConfig {
private final JwtService jwtService;
private final UserRepository userRepository;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**","/actuator/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/api/system/**").hasRole("SYSTEM")
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.userDetailsService(userDetailsService)
.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public UrlBasedCorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(List.of(";));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
@Bean
public JwtFilter jwtFilter() {
return new JwtFilter(jwtService, userDetailsService());
}
@Bean
@Primary
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService(userRepository, passwordEncoder());
}}
Config for gateway
uri: lb://user-service
predicates:
- Path=/api/auth/**, /api/admin/users/**, /api/system/users/**, /api/user/**
filters:
- AddResponseHeader=Access-Control-Allow-Origin,
- AddResponseHeader=Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS
- AddResponseHeader=Access-Control-Allow-Headers, Content-Type, Authorization
- AddResponseHeader=Access-Control-Allow-Credentials, true
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 5
redis-rate-limiter.burstCapacity: 10
redis-rate-limiter.requestedTokens: 1
key-resolver: "#{@ipKeyResolver}"
globalcors:
cors-configurations:
'[/**]':
allowedOrigins:
- ";
allowedMethods:
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
allowedHeaders:
- Content-Type
- Authorization
allowCredentials: true
maxAge: 3600
Config Nginx for api
server {
server_name api.domain.pl;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/api.domain.pl/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.domain.pl/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location /api/ {
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' '';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
return 204; # Brak treści, ale akceptacja CORS
}
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
access_log /var/log/nginx/api.access.log;
error_log /var/log/nginx/api.error.log;
}
server {
listen 80;
server_name api.domain.pl;
return 301 https://$host$request_uri;
}
Config Nginx for frontend
server {
listen 443 ssl;
server_name domain.pl www.domain.pl;
ssl_certificate /etc/letsencrypt/live/domain.pl/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain.pl/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/domain.pl/chain.pem;
location / {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Referer $http_referer;
proxy_cookie_path / "/; Secure; HttpOnly; SameSite=None";
}
access_log /var/log/nginx/frontend.access.log;
error_log /var/log/nginx/frontend.error.log;
}
I have a problem with configuring CORS in a microservices architecture application using Spring Boot. My application consists of the following components: • Frontend: Application written in Angular • Backend: Microservices running in Spring Boot • Nginx: Serves as a proxy – one Nginx for the frontend application (Angular) and another for the API • Gateway: An intermediary service (e.g. Spring Cloud Gateway) that forwards requests to the appropriate microservices. My problem occurs when I try to make a CORS request from the frontend application (Angular). Every time, although the OPTIONS request passes (with code 204), the browser returns a 403 Invalid CORS request error. I have tried to fix the problem, including improving the CORS configuration in both Nginx and Spring Security, but the error still persists. I understand that there may be various places where conflicts can occur, such as CORS configuration on the Nginx server level, API Gateway, and the microservices themselves. I want to make sure that CORS support is properly configured to ensure communication between the frontend and the backend. Has anyone had a similar problem and can share some tips or experience in this area?
Security config for target microservice.
public class SecurityConfig {
private final JwtService jwtService;
private final UserRepository userRepository;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**","/actuator/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/api/system/**").hasRole("SYSTEM")
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.userDetailsService(userDetailsService)
.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public UrlBasedCorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(List.of("https://domain.pl"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
@Bean
public JwtFilter jwtFilter() {
return new JwtFilter(jwtService, userDetailsService());
}
@Bean
@Primary
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService(userRepository, passwordEncoder());
}}
Config for gateway
uri: lb://user-service
predicates:
- Path=/api/auth/**, /api/admin/users/**, /api/system/users/**, /api/user/**
filters:
- AddResponseHeader=Access-Control-Allow-Origin, https://domain.pl
- AddResponseHeader=Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS
- AddResponseHeader=Access-Control-Allow-Headers, Content-Type, Authorization
- AddResponseHeader=Access-Control-Allow-Credentials, true
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 5
redis-rate-limiter.burstCapacity: 10
redis-rate-limiter.requestedTokens: 1
key-resolver: "#{@ipKeyResolver}"
globalcors:
cors-configurations:
'[/**]':
allowedOrigins:
- "https://domain.pl"
allowedMethods:
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
allowedHeaders:
- Content-Type
- Authorization
allowCredentials: true
maxAge: 3600
Config Nginx for api
server {
server_name api.domain.pl;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/api.domain.pl/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.domain.pl/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location /api/ {
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' 'https://domain.pl';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
return 204; # Brak treści, ale akceptacja CORS
}
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
access_log /var/log/nginx/api.access.log;
error_log /var/log/nginx/api.error.log;
}
server {
listen 80;
server_name api.domain.pl;
return 301 https://$host$request_uri;
}
Config Nginx for frontend
server {
listen 443 ssl;
server_name domain.pl www.domain.pl;
ssl_certificate /etc/letsencrypt/live/domain.pl/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain.pl/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/domain.pl/chain.pem;
location / {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Referer $http_referer;
proxy_cookie_path / "/; Secure; HttpOnly; SameSite=None";
}
access_log /var/log/nginx/frontend.access.log;
error_log /var/log/nginx/frontend.error.log;
}
Share
Improve this question
asked Mar 28 at 19:45
WierzbaWierzba
111 bronze badge
1
|
1 Answer
Reset to default 1In the Nginx API configuration, you're only adding CORS headers for OPTIONS but not for the rest GET/POST/PATCH/DELETE. This is why your preflight OPTIONS returns with 204, but the actual request fails with a 403 CORS error.
add the headers after the if statement and it should be fine.
location /api/ {
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' 'https://domain.pl';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
return 204; # No content, but accept CORS
}
add_header 'Access-Control-Allow-Origin' 'https://domain.pl' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
proxy_pass http://localhost:8000;
}
@DependsOn("corsConfigurationSource")
on top ofsecurityFilterChain
. The reason is that by the timesecurityFilterChain
is created it already configures CORS so it is too late to create a cors bean after – asgarov1 Commented Mar 29 at 5:40