Search code examples
spring-bootspring-cloud-gatewayhttp-status-code-401

CORS configuration not working in Spring Cloud Gateway


Accessing API from browser (a jQuery script) is getting blocked. Here is the error message I am getting:

Access to XMLHttpRequest at 'http://localhost:8765/conversion/convert' from origin 'null' 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.

My jQuery script in a HTML file:

$.ajax({
    type: 'POST',
    url: 'http://localhost:8765/conversion/convert',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer MY_JWT_TOKEN'
    },
    data: JSON.stringify({
        id: 1,
        from: 'usd',
        to: 'bdt',
        quantity: 100
    }),
    success: function(data) {
        console.log(data);
    },
    error: function(error) {
        console.error(error);
    }
});

Maven Dependencies and Versions:

  1. spring-cloud-gateway: 2022.0.4
  2. spring-cloud-starter-gateway: 3.1.6
  3. spring-boot-starter-oauth2-resource-server: 3.1.6
  4. spring-cloud-starter-netflix-eureka-client

I have configured OAuth2 (Keycloak) with my API gateway and used CORS (globalcors) config in my Gateway service config file (yml) as directed on official Spring Cloud page. I also tried all the different approaches from internet, nothing seems working.

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-ui: http://localhost:9000/realms/Microservices-demo-realm
          jws-algorithm: RS256
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods: "*"
            allowedHeaders: "*"
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin RETAIN_UNIQUE
        - name: TokenRelay
      routes:
        - id: conversion-service
          uri: lb://currency-conversion
          predicates:
            - Path=/conversion/**

Here is the Google Chrome Network Console:

enter image description here

I have also tried from a tiny ReactJS app and CORS are being block from there as well. Here is a screenshot:

enter image description here

Please help!


Solution

  • A simple way to avoid CORS errors without the need for CORS configuration is to have same origin (serve your website through the gateway too, not only the API).

    gateway-uri: http://localhost:7080
    ui-uri: http://localhost:4200
    rest-api-service-uri: http://localhost:6080
    
    
    spring:
      cloud:
        gateway:
          default-filters:
          - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
          routes:
          # Redirection from / to /ui/
          - id: home
            uri: ${gateway-uri}
            predicates:
            - Path=/
            filters:
            - RedirectTo=301,${gateway-uri}/ui/
          # Serve a SPA through the gateway (requires it to have a baseHref set as /ui)
          - id: ui
            uri: ${ui-uri}
            predicates:
            - Path=/ui/**
          # Access the API with BFF pattern
          - id: bff
            uri: ${rest-api-service-uri}
            predicates:
            - Path=/bff/v1/**
            filters:
            - TokenRelay=
            - SaveSession
            - StripPrefix=2
    

    With

    • this routes definitions
    • gateway running on port 7080
    • REST API running on 6080
    • SPA hosted on let's say 4200

    I can:

    • access the SPA assets from http://localhost:7080/ui/* (request are routed to http://localhost:4200/ui/*)
    • send REST requests to http://localhost:7080/bff/v1/* (request are routed to http://localhost:6080/*, the /bff/v1 prefix being stripped)

    So with this setup, from the browser perspective, all network exchanges are made with http://localhost:7080 => no cross origin

    P.S.

    This is not the question, but configuring a gateway as a resource server is a bad idea, specially when it is queried by some Javascript code running in browsers.

    It would better be configured as a client with oauth2Login() and the TokenRelay= filter. The REST APIs behind the gateway would then be candidates for oauth2ResourceServer() configuration. I posted a tutorial for that on Baeldung.