最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Invalid CORS request in Spring boot microservices - Stack Overflow

programmeradmin0浏览0评论

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
  • try addings @DependsOn("corsConfigurationSource") on top of securityFilterChain. The reason is that by the time securityFilterChain is created it already configures CORS so it is too late to create a cors bean after – asgarov1 Commented Mar 29 at 5:40
Add a comment  | 

1 Answer 1

Reset to default 1

In 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;
}
发布评论

评论列表(0)

  1. 暂无评论