The project uses Spring-Security OAUTH 2. An Angular application is used as a web client. And as a Keycloak authorization server. The Angular application sends requests to the Spring application via the Spring GateWay.
When I try to send a Get request, I get an error
has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
file Pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.amrut.prabhu</groupId>
<artifactId>spring-cloud-gateway-keycloak-oauth2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring Cloud Gateway Oauth2 With Keycloak</name>
<description>spring cloud gateway with keycloak oauth2</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.1</spring-cloud.version>
<lombok.version>1.18.22</lombok.version>
<logback-access-spring-boot-starter.version>3.1.2</logback-access-spring-boot-starter.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
SecurityConfig
@Configuration
public class SecurityConfig {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ServerLogoutSuccessHandler handler) {
http
.cors()
.and()
.csrf().disable()
.authorizeExchange()
.pathMatchers("/actuator/**", "/","/logout.html")
.permitAll()
.and()
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.oauth2Login() // to redirect to oauth2 login page.
.and()
.logout()
.logoutSuccessHandler(handler)
;
return http.build();
}
@Bean
public ServerLogoutSuccessHandler keycloakLogoutSuccessHandler(ReactiveClientRegistrationRepository repository) {
OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedServerLogoutSuccessHandler(repository);
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}/logout.html");
return oidcLogoutSuccessHandler;
}
}
I tried to register in the Spring Gateway properties file, from this post cors
spring:
cloud:
gateway:
default-filters:
- TokenRelay
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"
I also tried to determine the global configuration of CORS, according to this article
@Configuration
@EnableWebFlux
public class CorsGlobalConfiguration implements WebFluxConfigurer {
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods("PUT")
.allowedMethods("GET")
.allowedHeaders("Baeldung-Allowed", "Baledung-Another-Allowed")
.exposedHeaders("Baeldung-Allowed", "Baeldung-Exposed")
.maxAge(3600);
}
}
I still get an error What am I doing wrong? I add all the necessary headers. What else does he need? And the @CrossOrigin annotation on the method always worked before.
The following two options also did not help solve the problem:
@Configuration
public class CorsWebFilterConfig {
@Bean
CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
corsConfig.setMaxAge(8000L);
corsConfig.addAllowedMethod("PUT");
corsConfig.addAllowedMethod("GET");
// corsConfig.addAllowedHeader("Baeldung-Allowed");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", corsConfig);
return new CorsWebFilter(source);
}
}
OR
@Configuration
public class CorsWebFilterConfig implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
ServerHttpRequest request = serverWebExchange.getRequest();
ServerHttpResponse response = serverWebExchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://localhost:4200");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "18000L");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();//HERE
}
return webFilterChain.filter(serverWebExchange);
}
}
The annotation on the method does not work either
@CrossOrigin(origins = "http://localhost:4200")
Here are the contents of the network tab, the first request is OPTIONS, the second is GET
Request URL: http://10.151.68.8:8484/auth/realms/demo/protocol/openid-connect/auth?response_type=code&client_id=spring-gateway-client&scope=message.write&state=dnAY-OwNUBGKuvZE5-3AH3L6v9W8OA5V67bD-U2YgiA%3D&redirect_uri=http://localhost:9090/login/oauth2/code/keycloak
Request Method: OPTIONS
Status Code: 200 OK
Remote Address: 10.151.68.8:8484
Referrer Policy: no-referrer
Response
Connection: keep-alive
Content-Length: 25
Content-Type: application/json
Date: Wed, 16 Nov 2022 07:34:59 GMT
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Request
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
Access-Control-Request-Headers: authorization
Access-Control-Request-Method: GET
Connection: keep-alive
Host: 10.151.68.8:8484
Origin: null
Sec-Fetch-Mode: cors
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
For some reason, the Origin = null field in the request Perhaps this is the reason for cors? It turns out that Gateway sends a verification request to the authorization server with Origin = null and receives a cors error.
The problem turned out to be that as a result of double forwarding of requests, the origin field became null. And when Keycloak received such a request, it threw a cors error. I found a temporary solution to this problem (thanks to the user @ch4mp): I disable oauth2 on the gateway
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency> -->
in the properties file I write
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-methods: "*"
allowed-headers: "*"
exposed-headers: "*"
and I add two beans
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder, Function<GatewayFilterSpec, UriSpec> brutalCorsFilters) {
return builder
.routes()
.route(p -> p.path("/users/**").filters(brutalCorsFilters).uri("https://localhost:9443"))
.route(p -> p.path("/greet/**").filters(brutalCorsFilters).uri("https://localhost:9445"))
.build();
}
@Bean
Function<GatewayFilterSpec, UriSpec> brutalCorsFilters() {
return f -> f
.setResponseHeader("Access-Control-Allow-Origin", "*")
.setResponseHeader("Access-Control-Allow-Methods", "*")
.setResponseHeader("Access-Control-Expose-Headers", "*");
}
The solution is temporary, but I haven't found another one. And what offers baeldung.com it doesn't work.