spring-cloud / spring-cloud-gateway

An API Gateway built on Spring Framework and Spring Boot providing routing and more.
http://cloud.spring.io
Apache License 2.0
4.55k stars 3.33k forks source link

Cors Pre Flight Request #1665

Open nguyentriloi opened 4 years ago

nguyentriloi commented 4 years ago

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:

tony-clarke-amdocs commented 4 years ago

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?

nguyentriloi commented 4 years ago

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

tony-clarke-amdocs commented 4 years ago

It is something you would add, not remove.

nguyentriloi commented 4 years ago

It is something you would add, not remove.

I added it from the beginning but it not work.

Been24 commented 4 years ago

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

nguyentriloi commented 4 years ago

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?

TYsewyn commented 4 years ago

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.

nguyentriloi commented 4 years ago

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

ch4dwick commented 4 years ago

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

dgallego58 commented 3 years ago

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 😢

Been24 commented 3 years ago

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()).

Romeh commented 3 years ago

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

dgallego58 commented 3 years ago

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
Romeh commented 3 years ago

@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
minhtk1 commented 2 years ago

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.

lgscofield commented 2 years ago

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

hajubal commented 1 year ago

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".

ch4dwick commented 1 year ago

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.

anthonyikeda commented 1 year ago

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
dgallego58 commented 1 year ago

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

anthonyikeda commented 1 year ago

@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
TYsewyn commented 1 year ago

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.

anthonyikeda commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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?

JoePortilla commented 9 months ago

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.

anthonyikeda commented 9 months ago

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.