Springfox Swagger UI behind reverse proxy - swagger-ui

I have configured a Spring Boot application with Swagger API documentation and configured Swagger UI.
I also run my backend application behind a reverse proxy that maps all requests from host:port/api to backend_host:port/, when running locally on localhost I map localhost:8082/api. In production a similar mapping is applied.
When I open the Swagger UI from localhost:8082/api/swagger-ui.html it shows the following lines below the title:
[ Base URL: localhost:8080 ]
http://localhost:8082/api/v2/api-docs
When I invoke any rest operation swagger always tries to perform it against localhost:8080 which then fails due to the same origin policy.
I am aware of using pathProvider but it only affects the base URL's path part, not the domain and port. So I can only use it to change the base URL to localhost:8080/api but I would need it to change to localhost:8082/api. Is there a way to set the host dynamically to the current host and port that is active in the browser?
.pathProvider (new RelativePathProvider (servletContext) {
#Override
public String getApplicationBasePath() {
return "/api";
}
})

In my case with a spring-boot:2.2.6 application with springdoc-openapi-ui:1.3.0 (that also has embedded the swagger UI), I solved the proxy problem setting the server URL in this way:
#Configuration
public class OpenApiConfig {
#Value("${server}")
private String url;
#Bean
#Profile("prod")
public OpenAPI customConfiguration() {
return new OpenAPI()
.servers(Collections
.singletonList(new Server().url(url))) //real public URL
.components(new Components())
.info(new Info().title("Dummy API Docs")
.description("Dummy REST API documentation"));
}
}
This change is reflected in the contract (https://real-domain.com/api-docs):
And in the Swagger UI (https://real-domain.com/swagger-ui/index.html?configUrl=/api-docs/swagger-config)

I think in your case you need to configure your proxy to set HTTP Header
(which will be forwarded to your target backend)
to "notify" Swagger endpoints to return custom URL in /apidocs endpoint.
Please configure proxy to set header X-Forwarded-Host to value from Host request header
Explanation:
In your browser when you will visit a url eg. https://my.domain.com/api/swagger-ui.html
the proxy should create and forward header X-Forwarded-Host: my.endpoint.com
to your backend localhost:8082/api/swagger-ui.html
-> so the Swagger /apidocs enpoint could take this header into consideration in response JSON.
My own case - in Microsoft IIS:
I needed to configure Microsoft IIS to serve Swagger IU from Apache Tomcat on 8080 port on HTTPS domain,
so I needed to have following configuration:
<serverVariables>
<set name="HTTP_X_FORWARDED_HOST" value=“{HTTP_HOST}” />
<set name="HTTP_X_FORWARDED_PROTO" value="https" />
</serverVariables>

JuanMorenos answer helped me, however, if anyone is using Springboot and annotations with OpenAPI you can define the URL in your main class
#SpringBootApplication
#OpenAPIDefinition(info = #Info(
version = "2.0",
title = "Swagger - My application",
description = "A description of the application"),
servers = #Server(
url = "http://yourhost:yourport",
description = "A description of the Server "
))

Related

How can I force the .NET ASMX WSDL generation to change a soap:address location from http to https?

Using VB.NET asmx project, which is hosted behind SSL offload, I need to change the generated WSDL to show https for the soap:address.
from: <soap:address location="http://example.com/example.asmx"/>
to: <soap:address location="https://example.com/example.asmx"/>
preferably outside of code so we can influence in the build process.
It depends what system are you using for generating the wsdl.
You shared that you are using VB.NET but it does not narrow down enough to answer your question a 100%. If you can show some code then we could help hopefully. Also as far as I remember, the location in the WSDL file is the same as the client is accessing it (the URL where it reaches). Meaning that as the offloading happens elsewhere the location could always be http.
Without further information I see three options for you:
Configure the TLS offloader to redirect the queries from http to httpS. (This is also a recommended setting from a security point of view.)
Where the offloading is happening use a solution to replace the content of the response. (This has the advantage of being specific to the environment.)
Use self singed certificate on the internal application as well, and therefore the address will be generated correctly. (This could be a bit tougher nut to crack, but has the benefit of not being dependent on other configuration and having to modify that configuration for every environment from development to live.)
In c# it could be done in code https://learn.microsoft.com/en-us/archive/blogs/kaevans/modify-a-web-services-wsdl-using-a-soapextensionreflector and is qite complicated. If you have a developer machine, then you need to use TLS as well... but here you go:
using System;
using System.Web.Services.Description;
namespace Msdn.Web.Services.Samples
{
public class HttpsReflector : SoapExtensionReflector
{
public override void ReflectMethod()
{
//no-op
}
public override void ReflectDescription()
{
ServiceDescription description = ReflectionContext.ServiceDescription;
foreach (Service service in description.Services)
{
foreach (Port port in service.Ports)
{
foreach (ServiceDescriptionFormatExtension extension in port.Extensions)
{
SoapAddressBinding binding = extension as SoapAddressBinding;
if (null != binding)
{
binding.Location = binding.Location.Replace("https://", "https://");
}
}
}
}
}
}
}

Swagger Base URL is wrong for my application deployed on AWS

I have a Spring Boot application deployed and configured as AWS Route 53 > AWS Load Balance -> 2 EC2 instances which hosted the Spring Boot application.
The URL for the Swagger is
https://applicationXYZ.company.net/release/swagger-ui.html
I'm able to see the page without any issue. But we can't use the 'Tryout' feature because the Base URL is wrong.
On top of the page I do see information as
[ Base URL: service/release]
I have no idea where 'service' became my base URL. I also hit api-docs and also see 'server' in 'host' field.
Could you please help on this?
Note: I'm using Spring Boot Starter 2.0.8.RELEASE and Swagger 2.9.2 (without any Spring Security)
Thanks,
Did you ever try to make a redirect,
//Do redirection inside controller
#RequestMapping("/swagger")
public String greeting() {
return "redirect:/swagger-ui.html";
}
you can try to add bean too, inside main method,
#Bean
RouterFunction<ServerResponse> routerFunction() {
return route(GET("/swagger"), req ->
ServerResponse.temporaryRedirect(URI.create("swagger-ui.html")).build());
}
refer: How to change Swagger-ui URL prefix?

Spring OAuth redirect_uri not using https

I have a Spring Boot 1.3.0 application with Spring Security OAuth included as a sort of SSO integration.
The problem is that the application is running in a non-SSL environment with a non-standard port behind a load balancer (F5) that forces SSL and the OAuth provider requires all redirect URLs be registered as https, but the Spring OAuth client (auto-configured with #EnableOAuthSso) will only redirect to the OAuth provider with the following URL...
https://[provider_host]/oauth/authorize?client_id=[redact]&redirect_uri=http://[application_host]/login&response_type=code&scope=[redact]&state=IpMYTe
Note that the return redirect_uri is generated as http. Even though the F5 will force it to https on the way back, our OAuth provider will not allow a non-SSL redirect URI. How can I configure this?
With the exception of my Spring Data JPA controllers, this is the entirety of the app...
AppConfig.java
#SpringBootApplication(exclude = { HibernateJpaAutoConfiguration.class })
#EnableJpaRepositories
public class AppConfig extends SpringBootServletInitializer {
public static void main(final String... args) {
SpringApplication.run(AppConfig.class, args);
}
#Autowired
public DataSource dataSource;
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean getEntityManagerFactoryInfo() {
final LocalContainerEntityManagerFactoryBean fac = new LocalContainerEntityManagerFactoryBean();
fac.setDataSource(dataSource);
fac.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
fac.setPackagesToScan("[redact]");
final Properties props = new Properties();
props.put("hibernate.dialect", "org.hibernate.dialect.SQLServerDialect");
props.put("hibernate.show_sql", "true");
props.put("hibernate.format_sql", "true");
fac.setJpaProperties(props);
return fac;
}
#Bean(name = "transactionManager")
public PlatformTransactionManager getTransactionManager() {
final JpaTransactionManager transactMngr = new JpaTransactionManager();
transactMngr.setEntityManagerFactory(getEntityManagerFactoryInfo().getObject());
return transactMngr;
}
}
SecurityConfig.java
#Configuration
#EnableOAuth2Sso
public class SecurityConfig {
}
application.properties
server.port=9916
server.contextPath=
server.use-forward-headers=true
security.oauth2.client.clientId=[redact]
security.oauth2.client.clientSecret=[redact]
security.oauth2.client.scope=[redact]
security.oauth2.client.accessTokenUri=https://[provider_host]/oauth/token
security.oauth2.client.userAuthorizationUri=https://[provider_host]/oauth/authorize
security.oauth2.resource.userInfoUri=https://[provider_host]/oauth/me
security.oauth2.resource.preferTokenInfo=false
logging.level.org.springframework=TRACE
After digging manually through the configuration classes I was able to find and add the following, which did the trick...
security.oauth2.client.pre-established-redirect-uri=https://[application_host]/login
security.oauth2.client.registered-redirect-uri=https://[application_host]/login
security.oauth2.client.use-current-uri=false
I'm not convinced there isn't a better way to solve the problem of forcing a HTTPS redirect URL, but this fix worked for me.
You may need to ensure that your application understands x-forwarded headers from your load balancer.
Putting this in my application.yml fixed my very similar problem with an application behind an AWS ELB:
server:
tomcat:
remote-ip-header: x-forwarded-for
protocol-header: x-forwarded-proto
Edit: This can be simplified with the more generic configuration:
server:
use-forward-headers: true
For Apache Tomcat use RemoteIpValve in server.xml (above AccessLogValve):
<Valve className="org.apache.catalina.valves.RemoteIpValve"
protocolHeader="X-Forwarded-Proto" />
See also: https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html.
My answer is for people using latest spring version, as the answers suggested above didnt work for me. I am using Spring Boot 2.3.5.RELEASE.
I had a the same issue, I am using Azure AD for oauth2 authentication. My application runs behind the reverse proxy and redirect uri formed was taking http rather than https.
After reading the document https://docs.spring.io/spring-security/site/docs/5.2.x/reference/html/oauth2.html#oauth2Client-auth-code-redirect-uri
, I added below line in the application.properties files and it worked for me
spring.security.oauth2.client.registration.azure.redirect-uri=https://{baseHost}{basePort}{basePath}/login/oauth2/code/azure
Since you have mentioned the use of oauth I think this will help someone to understand the flow of operation. This answer only applies if you are using a reverse proxy such as NGINX.
Cause of the problem,
Your spring boot application is running on the server with a address simlar to http://localhost:8080 . That's what all the spring boot app know about its host. You can inspect this behavior if you check the redirect url in facebook(or other oauth client) error page. It will look something like https://graph.facebook.com/v3.0/me?fields=id,first_name,middle_name,last_name,name,email,verified,is_verified,picture.width(250).height(250),link&redirect_url=http%3A%2F%2Flocalhost%2Flogin%2Ffacebook
See the redirect_url is wrong.
So we need to somehow tell the application that it is hosted under this address.
Quick fix
If you are only looking to fix Facebook OAuth ( Or other oAuth provider), Adding following lines to client will fix.
facebook:
client:
preEstablishedRedirectUri: https://yourdomain.com/
useCurrentUri: false
But, this will only fix the issue at hand ( Also not flexible). But if you need a more concrete solution which is portable, you need to solve this at the reverse proxy.
Open your nginx configuration for the app and change it reflecting as follows.
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Will add the user's ip to the request, some apps need this
proxy_set_header X-Forwarded-Proto $scheme; # will forward the protocole i.e. http, https
proxy_set_header X-Forwarded-Port $server_port; # Will forward the port
proxy_set_header Host $host; # !Important will forward the host address
proxy_pass http://localhost:8080/;
}
Okay so now, nginx is sending the information which were previously hidden to the spring boot app. But yet, spring app is not using this information. To tell it to use these information add the following line to the application.yml.
server.use-forward-headers = true
If you have your reverse proxy in a different node of the same network, you may want to configure the ip of the reverse proxy server with the following. ( replace with your IP)
server.tomcat.internal-proxies=192\.65\.210\.55
I had the same problem.
I add theses two parameters to force HTTPS in redirect_uri :
preEstablishedRedirectUri: https://...
useCurrentUri: false
It works : "redirect_uri" is now using HTTPS
you may need to use spring.oauth2.client.access-token-uri
configuration parameter changed after 1.3.0.M1
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.3.0-M1-Configuration-Changelog

Using an MVC web api, can I find out the domain a cors request

So this is insecure and isn't my favorite option, but I just want to know how possible it is. Is there a way I could find the host in a cors request so that i could send back a Access-Control-Allow-Origin: "domain.com" where domain is any domain that uses the api. The reason being is I would like to be able to "use credentials" with what is essentially a wildcard.
I could have my end user send me their host name, but if possible i would rather just pick it up from the request itself.
Its possible, assuming you are using the Microsoft.AspNet.WebApi.Cors to enable CORS on your API, you need to define your own Cors Policy Provider:
public class MyApiCorsPolicy : Attribute, ICorsPolicyProvider
{
private System.Web.Cors.CorsPolicy _policy;
public MyApiCorsPolicy ()
{
// Create a CORS policy.
_policy = new System.Web.Cors.CorsPolicy
{
AllowAnyMethod = true,
AllowAnyHeader = true,
SupportsCredentials = true
};
}
public Task<System.Web.Cors.CorsPolicy> GetCorsPolicyAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
// Add the Request origin to the response.
_policy.Origins.Add(request.GetCorsRequestContext().Origin);
return Task.FromResult<System.Web.Cors.CorsPolicy>(_policy);
}
}
Next, in your ASP.NET Web Api config code you need to pass this policy when enabling Cors:
config.EnableCors(new MyApiCorsPolicy());

Web Service client generated by wsdl not working with Deployed web service

I have generated a WSDL from a java class using axis2 java2wsdl utility as follows;
java2wsdl -o C:\temp -cn com.temenos.webservices.customer.CustomerServiceWS
Then I have deployed the same web service within an Application Server (say jBoss) in axis2 and I can browse the wsdl on http:// 127.0.0.1:8080/axis2/services/CustomerServiceWS?wsdl and call the methods on this service via standard client like SoapUI etc.
The problem is now that when I generated a client using standard java tooling 'wsimport' by providing a WSDL location as C:\temp (Generated WSDL from java2wsdl utility), my client is unable to communicate with the Deployed Web Service. I am using following code to access the web service;
// Initialise WS
CustomerServiceWS service = null;
CustomerServiceWSPortType servicePort = null;
try {
URL wsdlLocation = new URL("http://127.0.0.1:8080/axis2/services/CustomerServiceWS?wsdl");
QName serviceName = new QName("http://customer.webservices.temenos.com", "CustomerServiceWS");
service = new CustomerServiceWS(wsdlLocation, serviceName);
servicePort = service.getCustomerServiceWSHttpSoap12Endpoint();
} catch (MalformedURLException murle) {
murle.printStackTrace();
return;
}
But while creating an (service Port) Endpoint I am getting following error;
Exception in thread "main" javax.xml.ws.WebServiceException: An attempt was made to construct the ServiceDelegate object with an service name that is not valid: {http://customer.webservices.temenos.com}CustomerServiceWS.
at org.apache.axis2.jaxws.ExceptionFactory.createWebServiceException(ExceptionFactory.java:173)
at org.apache.axis2.jaxws.ExceptionFactory.makeWebServiceException(ExceptionFactory.java:70)
at org.apache.axis2.jaxws.ExceptionFactory.makeWebServiceException(ExceptionFactory.java:118)
at org.apache.axis2.jaxws.spi.ServiceDelegate.<init>(ServiceDelegate.java:218)
at org.apache.axis2.jaxws.spi.Provider.createServiceDelegate(Provider.java:59)
at javax.xml.ws.Service.<init>(Service.java:56)
at com.temenos.webservices.customer.CustomerServiceWS.<init>(CustomerServiceWS.java:42)
at com.temenos.services.customer.client.Client.testGetLanguage(Client.java:32)
at com.temenos.services.customer.client.Client.main(Client.java:21)
I have tried many things but it does not seems to like anything. Am I missing anything?
Thanks,
--
SJunejo
The problem was that I had axis2 in lib path because of that the call happend to org.apache.axis2.jaxws.spi.Provider.createServiceDelegate (Axi2 Provider) instead of Java WS Provider. I removed the axis2 libs from classpath and it seems to be working ok now. (though I am still unable to call my web service via client)
See the description of WSDL file and check the targetNamespace for the url to be given in QName(). Also import necessary packages.

Resources