spring-cloud-gateway icon indicating copy to clipboard operation
spring-cloud-gateway copied to clipboard

Cors Pre Flight Request

Open nguyentriloi opened this issue 4 years ago • 26 comments

Describe the bug i'm using spring cloud Hoxton.SR3 and spring boot 2.2.6.RELEASE. i have a problem when using angular 2+ call to api-gateway : "Access to XMLHttpRequest at 'http://localhost:8080/auth/oauth/token' from origin 'http://localhost:4200' 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." config api-gateway : spring: cloud: gateway: default-filters: - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin routes: - id: auth-service uri: lb://auth-service predicates: - Path=/auth/** - id: product-service uri: lb://product-service predicates: - Path=/product/** - id: order-service uri: lb://order-service predicates: - Path=/order/** - id: payment-service uri: lb://payment-service predicates: - Path=/payment/** globalcors: corsConfigurations: '[/**]': allowedOrigins: "" allowedHeader: "" allowCredentials: true allowedMethods: "*" add-to-simple-url-handler-mapping: true i have tried add @CrossOrigin in gateway main class or add method Options in predicates in route[i] but it not work. Please help me . I think the problem lies in the version of the cloud

nguyentriloi avatar Apr 09 '20 12:04 nguyentriloi

See here: https://cloud.spring.io/spring-cloud-gateway/reference/html/#cors-configuration Have you set the property spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true?

tony-clarke-amdocs avatar Apr 09 '20 12:04 tony-clarke-amdocs

See here: https://cloud.spring.io/spring-cloud-gateway/reference/html/#cors-configuration Have you set the property spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true?

I tried removing it earlier, but it still doesn't work

nguyentriloi avatar Apr 09 '20 13:04 nguyentriloi

It is something you would add, not remove.

tony-clarke-amdocs avatar Apr 09 '20 13:04 tony-clarke-amdocs

It is something you would add, not remove.

I added it from the beginning but it not work.

nguyentriloi avatar Apr 09 '20 13:04 nguyentriloi

It is something you would add, not remove.

I added it from the beginning but it not work.

Maybe you should set ''http.cors()'' in SecurityWebFilterChain

Been24 avatar Apr 10 '20 07:04 Been24

It is something you would add, not remove.

I added it from the beginning but it not work.

Maybe you should set ''http.cors()'' in SecurityWebFilterChain

I have config it in the auth service, but is it needed in the gateway service?

nguyentriloi avatar Apr 10 '20 12:04 nguyentriloi

Can you provide a complete, minimal, verifiable sample that reproduces the problem? It should be available as a GitHub (or similar) project or attached to this issue as a zip file.

TYsewyn avatar Apr 23 '20 16:04 TYsewyn

Can you provide a complete, minimal, verifiable sample that reproduces the problem? It should be available as a GitHub (or similar) project or attached to this issue as a zip file.

This is my project. pls change config url in centralize-config to local before run. https://github.com/nguyentriloi/spring-cloud

nguyentriloi avatar Apr 24 '20 02:04 nguyentriloi

As of June 2, 2020 I confirm this issue still exists. Prior to the problem, I was using Spring Boot 2.2.2 and Cloud Hoxton.SR1 and it worked fine at the time so I rolled back to that version.

It currently failed in our dev environments using Spring Boot 2.3.0 and Hoxton.SR5

ch4dwick avatar Jun 04 '20 11:06 ch4dwick

July 07, 2021, i have the same problem the spring boot version i use is 2.4.8 and cloud version 2020.0.3, my application.yaml is the following:

##Spring Configuration
base-path: /services/api/v1
gateway-path: ${base-path}/gateway
server:
  port: 8888
spring:
  application:
    name: ms_gateway
  devtools:
    add-properties: false
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
        - AddResponseHeader=Access-Control-Allow-Origin, *
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins: '*'
            allowed-methods:
              - "GET"
              - "POST"
              - "PUT"
              - "DELETE"
              - "HEAD"
              - "OPTIONS"
            allowed-headers:
              - "Origin"
              - "Content-Type"
              - "Accept"
              - "Authorization"
              - "User-Key"
              - "Request-Tracker"
              - "Session-Tracker"
              - "X-XSRF-TOKEN"
              - "X-IBM-CLIENT-ID"
              - "Message-ID"
              - "X-IBM-CLIENT-SECRET"
            allow-credentials: true
        add-to-simple-url-handler-mapping: true
  webflux:
    base-path: /
  sleuth:
    reactor:
      instrumentation-type: manual
management:
  endpoints:
    enabled-by-default: true
    web:
      exposure:
        include: health, mappings
      base-path: ${gateway-path}
    jmx:
      exposure:
        include: health

---
spring:
  profiles:
    group:
      <netw>: dev, qa, pdn
  cloud:
    gateway:
      routes:
        - id: ms_company
          uri: ${company-internal-endpoint}
          predicates:
            - Path= ${gateway-path}/company/**
          filters:
            - RewritePath=${gateway-path}/company(?<segment>/?.*), ${base-path}/company/$\{segment}
            - HeaderCustomizer 

and this is my configuration class:


@Configuration
@EnableWebFluxSecurity
@Slf4j
@EnableConfigurationProperties(value = {GlobalCorsProperties.class})
public class SecurityAutoConfiguration {

    @Bean
    public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http,
                                                       @Value("${jwt.secret}") String secret) {
        log.info("Configuring SecurityWebFilterChain");
        return http.securityMatcher(new NegatedServerWebExchangeMatcher(
                ServerWebExchangeMatchers.pathMatchers("/services/api/v1/gateway/health"))
        )
                .formLogin(ServerHttpSecurity.FormLoginSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .logout(logout -> logout
                        .requiresLogout(new PathPatternParserServerWebExchangeMatcher("**/logout")))
                .csrf(csrf -> csrf.csrfTokenRepository(new CookieServerCsrfTokenRepository()))
                .addFilterBefore(JwtFilter.of(secret), SecurityWebFiltersOrder.AUTHORIZATION)
                .cors()
                .and()
                .build();
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    @RefreshScope
    public CorsWebFilter corsWebFilter(GlobalCorsProperties globalCorsProperties) {
        var source = new UrlBasedCorsConfigurationSource();
        globalCorsProperties.getCorsConfigurations().forEach(source::registerCorsConfiguration);
        return new CorsWebFilter(source);
    }

    @PostConstruct
    public void postConstruct() {
        log.info("Starting SecurityAutoConfiguration");
    }
}

and still got the problem

image

while postman and non browser request are done nicely.

EDIT: i also tried all the suggestions in #840 and it didn't work 😢

dgallego58 avatar Jul 14 '21 15:07 dgallego58

2021 年 7 月 7 日,我遇到了同样的问题,我使用的 Spring Boot 版本是 2.4.8 和云版本 2020.0.3,我application.yaml的问题如下:

## Spring 配置
base-path : /services/api/v1 
gateway-path : ${base-path}/gateway 
server :
   port : 8888 
spring :
   application :
     name : ms_gateway 
  devtools :
     add-properties : false 
  cloud :
     gateway :
       default -filters :
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE 
        - AddResponseHeader=Access-Control-Allow-Origin, *
      globalcors :
         cors-configurations :
           ' [/**] ' :
             allowed-origins : ' * '
             allowed-methods : 
              - “ GET ” 
              - “ POST ” 
              - “ PUT ” 
              - “ DELETE ” 
              - “ HEAD ” 
              - “ OPTIONS ”
            允许- 标题:
              —— ”来源" 
              - " Content-Type " 
              - "接受" 
              - "授权" 
              - " User-Key " 
              - " Request-Tracker " 
              - " Session-Tracker " 
              - " X-XSRF-TOKEN " 
              - " X-IBM-CLIENT- ID " 
              - "消息 ID "
              - " X-IBM-客户-秘密“
            允许凭据:真
        添加到简单网址处理程序映射:真
  webflux:
    基本路径: /
  侦探:
    反应器:
      仪器类型:手动
管理:
  端点:
    默认启用:真
    网络:
      曝光:
        包括:健康,映射
      基本路径: ${gateway-path} 
    jmx:
      曝光:
        包括:健康

---春天:
  型材:
    组:
       <NETW> :开发,QA,PDN云:
    网关:
      路线:
        - ID:ms_company URI:$ {公司内部端点}断言:
            - PATH = $ {网关路径} /company/**过滤器:
            - RewritePath=${gateway-path}/company(?<segment>/?.*), ${base-path}/company/$\{segment} 
            - HeaderCustomizer

  
          
          
          

这是我的配置类:

@Configuration
@EnableWebFluxSecurity
@Slf4j
@EnableConfigurationProperties(value = {GlobalCorsProperties.class})
public class SecurityAutoConfiguration {

    @Bean
    public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http,
                                                       @Value("${jwt.secret}") String secret) {
        log.info("Configuring SecurityWebFilterChain");
        return http.securityMatcher(new NegatedServerWebExchangeMatcher(
                ServerWebExchangeMatchers.pathMatchers("/services/api/v1/gateway/health"))
        )
                .formLogin(ServerHttpSecurity.FormLoginSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .logout(logout -> logout
                        .requiresLogout(new PathPatternParserServerWebExchangeMatcher("**/logout")))
                .csrf(csrf -> csrf.csrfTokenRepository(new CookieServerCsrfTokenRepository()))
                .addFilterBefore(JwtFilter.of(secret), SecurityWebFiltersOrder.AUTHORIZATION)
                .cors()
                .and()
                .build();
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    @RefreshScope
    public CorsWebFilter  corsWebFilter(GlobalCorsProperties  globalCorsProperties){
         VAR源= 新 UrlBasedCorsConfigurationSource(); 
        globalCorsProperties 。getCorsConfigurations() 。forEach(source :: registerCorsConfiguration);
        返回 新的 CorsWebFilter(来源);
    } @PostConstruct public void postConstruct () {
        日志。info( “启动SecurityAutoConfiguration ” ); 
    }

    
      
}

仍然有问题

图像

而邮递员和非浏览器请求做得很好。

编辑:我也尝试了#840 中的所有建议,但没有奏效😢

July 07, 2021, i have the same problem the spring boot version i use is 2.4.8 and cloud version 2020.0.3, my application.yaml is the following:

##Spring Configuration
base-path: /services/api/v1
gateway-path: ${base-path}/gateway
server:
  port: 8888
spring:
  application:
    name: ms_gateway
  devtools:
    add-properties: false
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
        - AddResponseHeader=Access-Control-Allow-Origin, *
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins: '*'
            allowed-methods:
              - "GET"
              - "POST"
              - "PUT"
              - "DELETE"
              - "HEAD"
              - "OPTIONS"
            allowed-headers:
              - "Origin"
              - "Content-Type"
              - "Accept"
              - "Authorization"
              - "User-Key"
              - "Request-Tracker"
              - "Session-Tracker"
              - "X-XSRF-TOKEN"
              - "X-IBM-CLIENT-ID"
              - "Message-ID"
              - "X-IBM-CLIENT-SECRET"
            allow-credentials: true
        add-to-simple-url-handler-mapping: true
  webflux:
    base-path: /
  sleuth:
    reactor:
      instrumentation-type: manual
management:
  endpoints:
    enabled-by-default: true
    web:
      exposure:
        include: health, mappings
      base-path: ${gateway-path}
    jmx:
      exposure:
        include: health

---
spring:
  profiles:
    group:
      <netw>: dev, qa, pdn
  cloud:
    gateway:
      routes:
        - id: ms_company
          uri: ${company-internal-endpoint}
          predicates:
            - Path= ${gateway-path}/company/**
          filters:
            - RewritePath=${gateway-path}/company(?<segment>/?.*), ${base-path}/company/$\{segment}
            - HeaderCustomizer 

and this is my configuration class:

@Configuration
@EnableWebFluxSecurity
@Slf4j
@EnableConfigurationProperties(value = {GlobalCorsProperties.class})
public class SecurityAutoConfiguration {

    @Bean
    public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http,
                                                       @Value("${jwt.secret}") String secret) {
        log.info("Configuring SecurityWebFilterChain");
        return http.securityMatcher(new NegatedServerWebExchangeMatcher(
                ServerWebExchangeMatchers.pathMatchers("/services/api/v1/gateway/health"))
        )
                .formLogin(ServerHttpSecurity.FormLoginSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .logout(logout -> logout
                        .requiresLogout(new PathPatternParserServerWebExchangeMatcher("**/logout")))
                .csrf(csrf -> csrf.csrfTokenRepository(new CookieServerCsrfTokenRepository()))
                .addFilterBefore(JwtFilter.of(secret), SecurityWebFiltersOrder.AUTHORIZATION)
                .cors()
                .and()
                .build();
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    @RefreshScope
    public CorsWebFilter corsWebFilter(GlobalCorsProperties globalCorsProperties) {
        var source = new UrlBasedCorsConfigurationSource();
        globalCorsProperties.getCorsConfigurations().forEach(source::registerCorsConfiguration);
        return new CorsWebFilter(source);
    }

    @PostConstruct
    public void postConstruct() {
        log.info("Starting SecurityAutoConfiguration");
    }
}

and still got the problem

image

while postman and non browser request are done nicely.

EDIT: i also tried all the suggestions in #840 and it didn't work 😢

https://docs.spring.io/spring-security/site/docs/current/reference/html5/#cors when spring-webflux in security,you should replace gateway globalCors with explicit declare CorsConfigurationSource.CorsWebFilter is added due to http.cors(withDefaults()).

Been24 avatar Jul 15 '21 01:07 Been24

does it work with last recommendation ? if yes can u please share a sample config for spring cloud gateway with security enabled ?

Romeh avatar Aug 06 '21 14:08 Romeh

does it work with last recommendation ? if yes can u please share a sample config for spring cloud gateway with security enabled ?

Yes, actually it worked with the following:

import co.com.retrival.security.filters.JwtFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.config.GlobalCorsProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

import javax.annotation.PostConstruct;

@Configuration
@EnableWebFluxSecurity
@Slf4j
@EnableConfigurationProperties(value = {GlobalCorsProperties.class})
public class ApiGatewaySecurityAutoConfiguration {

    @Bean
    public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http,
                                                       @Value("${jwt.secret}") String secret) {
        log.info("Configuring SecurityWebFilterChain");
        return http.securityMatcher(new NegatedServerWebExchangeMatcher(
                ServerWebExchangeMatchers.pathMatchers(
                        "/excluded/paths/**")))
                .formLogin(ServerHttpSecurity.FormLoginSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .logout(logout -> logout
                        .requiresLogout(new PathPatternParserServerWebExchangeMatcher("**/logout")))
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .addFilterAt(JwtFilter.of(secret), SecurityWebFiltersOrder.AUTHORIZATION)
                .cors()
                .and()
                .build();
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    @RefreshScope
    public CorsWebFilter corsWebFilter(CorsConfigurationSource corsConfigurationSource) {
        return new CorsWebFilter(corsConfigurationSource);
    }

    @Bean 
    public CorsConfigurationSource corsConfigurationSource(GlobalCorsProperties globalCorsProperties) {
        var source = new UrlBasedCorsConfigurationSource();
        globalCorsProperties.getCorsConfigurations().forEach(source::registerCorsConfiguration);
        return source;
    }

    @PostConstruct
    public void postConstruct() {
        log.info("Starting ApiGatewaySecurityAutoConfiguration");
    }
}

Both CorsWebFilter and CorsConfigurationSource beans were crucial for me, like said @Been24. it was a bit confusing that part from the doc, but after that config everything worked just fine

Also here is my application.yaml

Also note that im working now with Spring Boot 2.4.9

##Spring Configuration
base-path: /retrival/api/v1
gateway-path: ${base-path}/gateway
server:
  port: 8888
spring:
  application:
    name: ms_gateway
  devtools:
    add-properties: false
  h2:
    console:
      enabled: true
      path: /h2
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_FIRST
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins: '*'
            allowed-methods:
              - "GET"
              - "POST"
              - "PUT"
              - "DELETE"
              - "HEAD"
              - "OPTIONS"
            allowed-headers:
              - "Origin"
              - "Content-Type"
              - "Accept"
              - "Authorization"
              - "User-Key"
              - "Request-Tracker"
              - "Session-Tracker"
              - "X-XSRF-TOKEN"
              - "X-IBM-CLIENT-ID"
              - "Message-ID"
              - "X-IBM-CLIENT-SECRET"
        add-to-simple-url-handler-mapping: true
  webflux:
    base-path: /
  sleuth:
    reactor:
      instrumentation-type: manual
management:
  endpoints:
    enabled-by-default: true
    web:
      exposure:
        include: health, mappings
      base-path: ${gateway-path}
    jmx:
      exposure:
        include: health
aws:
  region: us-east-1
jwt:
  secret: ${secret_token_var}

---
spring:
  profiles:
    group:
      <netw>: dev, qa, pdn
  cloud:
    gateway:
      routes:
        - id: ms_company
          uri: ${kube-retrival-internal-endpoint}
          predicates:
            - Path= ${gateway-path}/retrival/**
          filters:
            - RewritePath=${gateway-path}/retrival(?<segment>/?.*), ${base-path}/enterprie/$\{segment}
            - HeaderCustomizer

dgallego58 avatar Aug 06 '21 16:08 dgallego58

@dgallego58 thanks for your reply , it works for me using the global config only in spring cloud gateway with security enabled , i am using spring boot version 2.5.3 and spring cloud 2020.0.3 :

     globalcors:
        add-to-simple-url-handler-mapping: true
        corsConfigurations:
          '[/**]':
            allowedOrigins: "http://localhost:4200"
            allowedOriginPatterns:
              - "*.testDomain.com"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
      default-filters:
        - TokenRelay
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_FIRST

Romeh avatar Aug 07 '21 09:08 Romeh

does it work with last recommendation ? if yes can u please share a sample config for spring cloud gateway with security enabled ?

Yes, actually it worked with the following:

import co.com.retrival.security.filters.JwtFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.config.GlobalCorsProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

import javax.annotation.PostConstruct;

@Configuration
@EnableWebFluxSecurity
@Slf4j
@EnableConfigurationProperties(value = {GlobalCorsProperties.class})
public class ApiGatewaySecurityAutoConfiguration {

    @Bean
    public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http,
                                                       @Value("${jwt.secret}") String secret) {
        log.info("Configuring SecurityWebFilterChain");
        return http.securityMatcher(new NegatedServerWebExchangeMatcher(
                ServerWebExchangeMatchers.pathMatchers(
                        "/excluded/paths/**")))
                .formLogin(ServerHttpSecurity.FormLoginSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .logout(logout -> logout
                        .requiresLogout(new PathPatternParserServerWebExchangeMatcher("**/logout")))
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .addFilterAt(JwtFilter.of(secret), SecurityWebFiltersOrder.AUTHORIZATION)
                .cors()
                .and()
                .build();
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    @RefreshScope
    public CorsWebFilter corsWebFilter(CorsConfigurationSource corsConfigurationSource) {
        return new CorsWebFilter(corsConfigurationSource);
    }

    @Bean 
    public CorsConfigurationSource corsConfigurationSource(GlobalCorsProperties globalCorsProperties) {
        var source = new UrlBasedCorsConfigurationSource();
        globalCorsProperties.getCorsConfigurations().forEach(source::registerCorsConfiguration);
        return source;
    }

    @PostConstruct
    public void postConstruct() {
        log.info("Starting ApiGatewaySecurityAutoConfiguration");
    }
}

Both CorsWebFilter and CorsConfigurationSource beans were crucial for me, like said @Been24. it was a bit confusing that part from the doc, but after that config everything worked just fine

Also here is my application.yaml

Also note that im working now with Spring Boot 2.4.9

##Spring Configuration
base-path: /retrival/api/v1
gateway-path: ${base-path}/gateway
server:
  port: 8888
spring:
  application:
    name: ms_gateway
  devtools:
    add-properties: false
  h2:
    console:
      enabled: true
      path: /h2
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_FIRST
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins: '*'
            allowed-methods:
              - "GET"
              - "POST"
              - "PUT"
              - "DELETE"
              - "HEAD"
              - "OPTIONS"
            allowed-headers:
              - "Origin"
              - "Content-Type"
              - "Accept"
              - "Authorization"
              - "User-Key"
              - "Request-Tracker"
              - "Session-Tracker"
              - "X-XSRF-TOKEN"
              - "X-IBM-CLIENT-ID"
              - "Message-ID"
              - "X-IBM-CLIENT-SECRET"
        add-to-simple-url-handler-mapping: true
  webflux:
    base-path: /
  sleuth:
    reactor:
      instrumentation-type: manual
management:
  endpoints:
    enabled-by-default: true
    web:
      exposure:
        include: health, mappings
      base-path: ${gateway-path}
    jmx:
      exposure:
        include: health
aws:
  region: us-east-1
jwt:
  secret: ${secret_token_var}

---
spring:
  profiles:
    group:
      <netw>: dev, qa, pdn
  cloud:
    gateway:
      routes:
        - id: ms_company
          uri: ${kube-retrival-internal-endpoint}
          predicates:
            - Path= ${gateway-path}/retrival/**
          filters:
            - RewritePath=${gateway-path}/retrival(?<segment>/?.*), ${base-path}/enterprie/$\{segment}
            - HeaderCustomizer

Thanks, worked for me.

minhtk1 avatar Jul 23 '22 16:07 minhtk1

Describe the bug i'm using spring cloud Hoxton.SR3 and spring boot 2.2.6.RELEASE. i have a problem when using angular 2+ call to api-gateway : "Access to XMLHttpRequest at 'http://localhost:8080/auth/oauth/token' from origin 'http://localhost:4200' 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." config api-gateway : spring: cloud: gateway: default-filters: - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin routes: - id: auth-service uri: lb://auth-service predicates: - Path=/auth/** - id: product-service uri: lb://product-service predicates: - Path=/product/** - id: order-service uri: lb://order-service predicates: - Path=/order/** - id: payment-service uri: lb://payment-service predicates: - Path=/payment/** globalcors: corsConfigurations: '[/**]': allowedOrigins: "" allowedHeader: "" allowCredentials: true allowedMethods: "*" add-to-simple-url-handler-mapping: true i have tried add @crossorigin in gateway main class or add method Options in predicates in route[i] but it not work. Please help me . I think the problem lies in the version of the cloud

Maybe you can refer to this: https://github.com/spring-cloud/spring-cloud-gateway/issues/2472#issuecomment-1233659197

lgscofield avatar Sep 01 '22 02:09 lgscofield

If set to "allowCredentials: true", the correct domain must be specified in "allowedOrigins". Or set to "allowCredentials: false". However, if set to "allowCredentials: false", the client's authorization information is not sent to the server. Therefore, the correct domain must be specified for "allowedOrigins".

hajubal avatar May 17 '23 09:05 hajubal

If set to "allowCredentials: true", the correct domain must be specified in "allowedOrigins". Or set to "allowCredentials: false". However, if set to "allowCredentials: false", the client's authorization information is not sent to the server. Therefore, the correct domain must be specified for "allowedOrigins".

What's the format for the domain? I just spent a few days trying to configure a web product that would not work if I added a trailing slash after the domain url. Better yet, give me an example.

ch4dwick avatar Aug 15 '23 11:08 ch4dwick

This is driving me crazy, no matter how I configure the oAuth on the Spring Cloud Gateway the OPTIONS calls are all protected by security (returning a 401 every time a preflight request comes in).

How do I disable the security on just the OPTIONS calls?

Standard setup for my gateway:

spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true
        cors-configurations:
          "[/**]":
            allowedOrigins: "http://localhost:4200"
            allowedHeaders: "*"
            allowedMethods: "*"
            allowCredentials: true
      routes:
        - id: product_api
          uri: http://localhost:8090
          predicates:
            - Path=/product-api/{segment}
          filters:
            - RewritePath=/product-api/?(?<segment>.*), /product/v1/$\{segment}
        - id: customer_api
          uri: http://localhost:8070
          predicates:
            - Path=/customer-api
          filters:
            - RewritePath=/customer-api/?(?<segment>.*), /customer/v1/$\{segment}
        - id: order-api
          uri: http://localhost:8060
          predicates:
            - Path=/order-api/{segment}
          filters:
            - RewritePath=/order-api/?(?<segment>.*), /order/v1/$\{segment}
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE
  security:
    oauth2:
      resourceserver:
        opaquetoken:
          client-id: portal-global
          client-secret: <<secret>>
          introspection-uri: http://localhost:8080/realms/my-realm/protocol/openid-connect/token/introspect

Every call to the OPTIONS preflight ends up with a 401:

❯ http --print=BbHh OPTIONS http://localhost:8010/product-api/67909821 "Origin: http://localhost:4200" Access-Control-Request-Method:GET
OPTIONS /product-api/67909821 HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: GET
Connection: keep-alive
Host: localhost:8010
Origin: http://localhost:4200
User-Agent: HTTPie/3.2.2



HTTP/1.1 401 Unauthorized
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Expires: 0
Pragma: no-cache
Referrer-Policy: no-referrer
WWW-Authenticate: Bearer
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
content-length: 0

anthonyikeda avatar Sep 19 '23 01:09 anthonyikeda

@anthonyikeda did you declare the beans of type CorsWebFilter and CorsConfigurationSource in your security config? those were the ones that helped me.

dgallego58 avatar Sep 21 '23 14:09 dgallego58

@anthonyikeda did you declare the beans of type CorsWebFilter and CorsConfigurationSource in your security config? those were the ones that helped me.

@dgallego58 I've tried those options, however, it seems that the Spring Security oAuth does a blanket protection of all methods and, while I've seen people overriding those options in code, it hasn't proven successful for me as yet (not sure if this a spring security version or webflux version problem).

Don't get me wrong, if a Bearer token is supplied with the OPTIONS calls, CORS works, though I'm not sure what the contract is here: if OPTIONS should be secured (which the Angular Keycloak client says it shouldn't) or if it should be secured (as per Spring Boot).

There don't seem to be any specs that outline this as a rule, nor do the different frameworks have an agreed upon approach.

❯ http --print=BbHh OPTIONS http://localhost:8010/product-api/67909821 "Origin: http://localhost:4200" Access-Control-Request-Method:GET "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIg..."

OPTIONS /product-api/67909821 HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: GET
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIg...
Connection: keep-alive
Host: localhost:8010
Origin: http://localhost:4200
User-Agent: HTTPie/3.2.2

Access-Control-Allow-Methods: GET
Access-Control-Allow-Origin: *
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Expires: 0
Pragma: no-cache
Referrer-Policy: no-referrer
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
content-length: 0


anthonyikeda avatar Sep 21 '23 16:09 anthonyikeda

Let me ask you this question: if it’s a secured endpoint, why would you allow unauthenticated access to the endpoint with an OPTIONS call?

The OPTIONS request is typically triggered by a browser because of cross-origin resource sharing (CORS). If I remember correctly the browser passes the same headers from the original call, thus also passing the Authorization header.

TYsewyn avatar Sep 21 '23 17:09 TYsewyn

Yeah I don't know whats going on, it's working again today. I'm going to keep monitoring the application.

As for browser sending the auth header on an options call, from the web browser console, from what's being logged, calls don't use the Authorization header when making pre-flight OPTIONS call - at least not that is being logged. Which is also being made through the framework making the pre-flight call.

There is some config options that I discovered for Angular Keycloak that determines if the Authorization header is added (documentation positions it as omitting the Authorization header - https://github.com/mauriciovigolo/keycloak-angular#httpclient-interceptor) With or without the Angular configuration change I'm still not seeing the Authorization header sent on the pre-flight call.

Name Value
Accept: /
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: authorization
Access-Control-Request-Method: GET
Connection: keep-alive
Host: localhost:8010
Origin: http://localhost:4200
Referer: http://localhost:4200/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.35

anthonyikeda avatar Sep 21 '23 17:09 anthonyikeda

Okay so it started working again because I'd taken the gateway resource server security config out (no longer authenticating at the gateway)

I did come across this though:

https://stackoverflow.com/questions/68143581/how-to-force-authentication-in-preflight-request

There’s no way to force authentication in a preflight request. The preflight is controlled totally by the browser, and nothing about it is exposed in any way that you can manipulate from frontend JavaScript code. And the requirements for the CORS protocol explicitly prohibit browsers from including any credentials in preflight requests.

For a detailed explanation, see the answer at https://stackoverflow.com/a/45406085/.

https://stackoverflow.com/questions/45405983/http-status-code-401-even-though-i-m-sending-credentials-in-the-request/45406085#45406085

You need to configure the server to not require authorization for the OPTIONS requests...

  1. Your browser sends the OPTIONS reuquest without the Authorization header, because the whole purpose of the OPTIONS check is to see if it's okay to include that header.

So the question is, @TYsewyn, how do I remove the authentication on the OPTIONS requests?

anthonyikeda avatar Sep 21 '23 23:09 anthonyikeda

This finally fixed it, it wasn't just adding a CorsWebFilter Bean, but also setting the @Order(Ordered.HIGHEST_PRECEDENCE):

	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
    CorsWebFilter corsWebFilter(GlobalCorsProperties properties) {
        CorsConfiguration corsConfig = properties.getCorsConfigurations().get("/**");

        UrlBasedCorsConfigurationSource source =
                new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig);

        return new CorsWebFilter(source);
    }

~~Leaves me to wonder how to re-prioritize the globalcors configuration.~~

Okay so I'm assuming the CorsWebFilter is not automatically created when you declare globalcors in the application.yaml file. By injecting the GlobalCorsProperties, however, can ensure the order and configuration gets applied.

I have no idea why there is no CorsFilter by default or if the add-to-simple-url-handler-mapping actually does anything, but I'm guessing there is something missing in the Spring Cloud Gateway code to wire this all up.

A search for uses of GlobalCorsProperties yields zero (0) uses in the local classpath - is there a specific dependency that it is tied to?

anthonyikeda avatar Sep 21 '23 23:09 anthonyikeda

If set to "allowCredentials: true", the correct domain must be specified in "allowedOrigins". Or set to "allowCredentials: false". However, if set to "allowCredentials: false", the client's authorization information is not sent to the server. Therefore, the correct domain must be specified for "allowedOrigins".

This was the solution that worked for me. I was putting * in allowedOrigins and always had the same "Access-Control-Allow-Origin" error.

I think this requirement should be in the documentation.

JoePortilla avatar Feb 19 '24 14:02 JoePortilla

IIRC the Access-Control-Allow-Origin header wasn't being added to each subsequent request, e.g. GET /endpoint/path and I somewhat had to hardcode it.

anthonyikeda avatar Feb 23 '24 20:02 anthonyikeda