Reputation: 2430
I am currently migrating from a Delphi Proxy to a Java Spring Webflux proxy. My requests against the migrated proxy work fine when done via Postman, but in Swagger, the POST requests fail against the new Server (they work against the old one), with the following error:
Failed to fetch.
Possible Reasons:
CORS
Network Failure
URL scheme must be "http" or "https" for CORS request.
I have debugged this and confirmed that the last request that reaches the server is an OPTIONS request made against the endpoint, and that this request delivers a 200 status code along with a bunch of headers.
Using Postman, I compared what this OPTIONS request returns when made against the old Delphi Server (where the Swagger POST requests work), and the new Java Spring Webflux Server (where they fail). The result was that the new server returns all of the headers that the old server returns (including those that according to other posts are essential for Swagger to accept the preflight), plus some additional ones. Here's the comparison:
Key | Old Proxy (working) | New Proxy (not working) |
---|---|---|
Access-Control-Allow-Headers | Content-Type, Authorization, token_auth | Content-Type, Authorization, token_auth |
Access-Control-Allow-Methods | GET, POST, DELETE, PUT, PATCH, OPTIONS | GET, POST, DELETE, PUT, PATCH, OPTIONS |
Access-Control-Allow-Origin | * | * |
Cache-Control | no-cache, no-store, max-age=0, must-revalidate | |
Content-Length | 39 | 39 |
Content-Type | text/html; charset=utf-8 | text/html; charset=utf-8 |
Date | Fri, 12 Jul 2024 10:59:43 GMT | Fri, 12 Jul 2024 11:04:15 GMT |
Expires | 0 | |
Pragma | no-cache | |
Referrer-Policy | no-referrer | |
Strict-Transport-Security | max-age=16070400; includeSubDomains | max-age=16070400; includeSubDomains |
X-Content-Type-Options | nosniff | |
X-Frame-Options | DENY | |
X-XSS-Protection | 0 |
My assumption is that these extra headers are causing the issue, but really, I'm not sure. I also have no idea how I can influence the plethora of headers being sent by Webflux, which brings me to my question:
Are those extra headers the cause of the Swagger POST requests not working? And if yes, how can I modify them in Spring Webflux to return the same as on our old Proxy?
UPDATE:
I now analyzed the outgoing OPTIONS request by Swagger, and found that it contains a header Origin: https://editor-next.swagger.io
. That OPTIONS request returns a 403 FORBIDDEN response, which causes Swagger to display "Failed to Fetch".
I was able to reproduce this in Postman by copying the request there. There, I was also able to confirm that if I omit the Origin
header, then the request returns through with a 200 OK response.
I managed to get it to work in Postman by adding the following CorsConfig to the Proxy server:
@Configuration
@EnableWebFlux
public class CorsConfig implements WebFluxConfigurer {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.addAllowedOrigin("*");
corsConfig.addAllowedMethod("GET");
corsConfig.addAllowedMethod("POST");
corsConfig.addAllowedMethod("DELETE");
corsConfig.addAllowedMethod("PUT");
corsConfig.addAllowedMethod("PATCH");
corsConfig.addAllowedMethod("OPTIONS");
corsConfig.addAllowedHeader("Content-Type");
corsConfig.addAllowedHeader("Authorization");
corsConfig.addAllowedHeader("token_auth");
corsConfig.addAllowedHeader("Origin");
corsConfig.setAllowCredentials(false);
corsConfig.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return new CorsWebFilter(source);
}
}
With this, the request passes in Postman, but Swagger still fails, albeit with a different error message:
Access to fetch at 'https://localhost:8080/endpoint' from origin 'https://editor-next.swagger.io' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Oddly, when I send the same request via Postman, the 'Access-Control-Allow-Origin' header contains only "*". Also, although Swagger displays a "failed to fetch", I can actually confirm that the request went through and made the intended changes on the system.
I have tried to remove the corsConfig.addAllowedOrigin("*");
from my CorsConfig, but that just causes the request to fail saying "No 'Access-Control-Allow-Origin' header is present on the requested resource".
Does anyone have any idea what causes these '*, *' in the access control header that appear only on Swagger and not Postman, and what to do about them?
Upvotes: 0
Views: 121
Reputation: 2430
Okay, so after a lot of trying around and puzzling together clues from different sources, I found what seems to be the solution to this problem.
The issue was double-layered, which complicated things. For starters, I needed a correct CorsConfig in my Proxy Service. Now, I don't know if this is the best possible config, but at least it worked for me:
import org.springframework.context.annotation.*;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.reactive.config.*;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
@EnableWebFlux
public class CorsConfig implements WebFluxConfigurer {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.addAllowedOrigin("*");
corsConfig.addAllowedMethod("GET");
corsConfig.addAllowedMethod("POST");
corsConfig.addAllowedMethod("DELETE");
corsConfig.addAllowedMethod("PUT");
corsConfig.addAllowedMethod("PATCH");
corsConfig.addAllowedMethod("OPTIONS");
corsConfig.addAllowedHeader("Content-Type");
corsConfig.addAllowedHeader("Authorization");
corsConfig.addAllowedHeader("token_auth");
corsConfig.addAllowedHeader("Origin");
corsConfig.setAllowCredentials(false);
corsConfig.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return new CorsWebFilter(source);
}
}
One important thing in this config was the corsConfig.addAllowedHeader("Origin");
line, because the lack of this is what caused my earlier attempts to fail.
After that, the issue was that the reply to the subsequent POST request contained two Access-Control-Allow-Origin
headers. Here the solution was that one of those actually got returned from the actual final endpoint that my Proxy service redirected to. The response from that was simply appended with the Access-Control-Allow-Origin
header from the Proxy even though it already contained one, creating the duplicate. Here, I fixed that by adjusting my final endpoint to not return an Access-Control-Allow-Origin
header.
This works for me for now. However, if anyone knows of a better way to do that, please let me know.
Upvotes: 1