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
Related
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 "
))
We have a website hosted and configured to use ADFS 2.0 for SSO. When I browse the web site via https, I am getting the below error.
I think the reason is that the load balancer is hitting the web server with http. If I change the below entry in the web.config it may fix, but not sure about the side effects. Any experience on this?
<federatedAuthentication>
<wsFederation passiveRedirectEnabled="true"
issuer="https://localhost/abc"
realm="https://localhost/abc/" requireHttps="true"/>
<cookieHandler requireSsl="true"/>
</federatedAuthentication>
Exception Details: System.InvalidOperationException: ID1059: Cannot authenticate the user because the URL scheme is not https and requireSsl is set to true in the configuration, therefore the authentication cookie will not be sent. Change the URL scheme to https or set requireSsl to false on the cookieHandler element in configuration.
System.IdentityModel.Services.WSFederationAuthenticationModule.OnEndRequest(Object sender, EventArgs args) +726
This is a typical error when you install a load balancer that terminates SSL.
We have a number of sites like this - never found any side-effects.
Just ensure that ADFS traffic goes out and then in because ADFS doesn't allow http endpoints when configuring. It always uses https endpoints when redirecting back to the RP.
Word of warning - ADFS traffic cannot be terminated at the load balancer - it needs https all the way to the ADFS server.
I solved this error in development by changing the web project's setting for SSL Enabled to true. It was somehow set to false and had no SSL URL property value set either.
We run into the same issue. I think the flag requireSsl in cookieHandler is missused by the WSFederationAuthenticationModule.OnEndRequest. RequireSsl sets in abstract class System.IdentityModel.Services.CookieHandler and System.IdentityModel.Services.ChunkedCookieHandler the secure flag on cookies. If this flag is set to true a client (browser p.a.) is responsible to send the cookie only over a secure connections (https). But in WSFederationAuthenticationModule.OnEndRequest the flag is used to cancel the process if Request.Url is not a https request. This is in SSL offloading scenarios the wrong behavior.
One solution is to implement a custom WsFederationAuthenticationModule and override OnEndRequest:
protected override void OnEndRequest(object sender, EventArgs args)
{
var reqSsl = FederatedAuthentication.SessionAuthenticationModule.CookieHandler.RequireSsl;
//System.IdentityModel.Services.ChunkedCookieHandler
if (reqSsl)
{
FederatedAuthentication.SessionAuthenticationModule.CookieHandler.RequireSsl = false;
try
{
base.OnEndRequest(sender, args);
}
finally
{
FederatedAuthentication.SessionAuthenticationModule.CookieHandler.RequireSsl = reqSsl;
}
}
else
base.OnEndRequest(sender, args);
}
I have an ASP.NET MVC 4 application running behind Amazon's elastic load balancer. Everything works OK when I install my SSL certificate on the load balancer and the web server and terminate SSL at the web server layer.
However, when I attempt to terminate at the load balancer layer, forwarding internal traffic from the load balancer to web servers un-encrypted on port 80, the RequireHttps attribute causes a redirect loop. This seemingly makes sense as it is requesting an encrypted channel and doesn't know that it is getting one (between the browser and load balancer). Has anyone run into this same issue? Any suggestions would be appreciated!
Edit: The solution
The following links may be useful to anyone else who runs into this issue:
MVC3, RequireHttps and custom handler result in http 310
https://gist.github.com/915869
It seems that to use this functionality on AWS, you look at the "X-Forwarded-Proto" HTTP header item. If the initial request is HTTPS, the load balancer injects the header and it says "https". If it's HTTP, it says, "http".
Answer found here: http://aws.typepad.com/aws/2010/10/keeping-customers-happy-another-new-elastic-load-balancer-feature.html
I was experiencing the same issue I found that the following RequireHttpsAttribute filter worked for me.
public class SSLFilter : RequireHttpsAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.HttpContext.Request.IsLocal ||
(filterContext.RequestContext.HttpContext.Request.Headers.AllKeys.Contains("X-Forwarded-Proto") &&
filterContext.RequestContext.HttpContext.Request.Headers.Get("X-Forwarded-Proto").ToLower().Equals("https")))
{
return;
}
base.OnAuthorization(filterContext);
}
}
I provided a solution for this in the following link:
https://stackoverflow.com/questions/37954796/requirehttpsattribute-with-netcore-rc2-causes-http302-redirect-loop-on-azure#=
Which says:
You can work around this by adding the following lines to ConfigureServices in Startup.cs (and add "using Microsoft.AspNetCore.HttpOverrides;")
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedProto;
});
I'm using spring-security-core and have setup the secure-channel capabilities, which work fine on my development machine. I've got the following in Config.groovy
grails.plugins.springsecurity.secureChannel.definition = [
'/order/checkout': 'REQUIRES_SECURE_CHANNEL',
'/order/paymentComplete': 'REQUIRES_INSECURE_CHANNEL'
]
Also, deploying to Heroku the associated order processing works fine, as long as I comment out the above lines. As soon as I put them back in, I get:
I see many requests come in on the server, and the Firebug net view shows:
I've got the PiggyBack SSL added on to Heroku, and I'm able to specify an https://... address to navigate to other parts of the site, in which case the browser stays in SSL mode. But if I access the
https:/www.momentumnow.co/order/checkout
address directly, I get the same redirect loop problem. Do you know what the problem is or how I can debug this further. If the latter, would you please update the comment area, and I will respond with updates to the problem area. Thanks
PiggyBack SSL documentation indicates:
"Piggyback SSL will allow you to use https://yourapp.heroku.com, since it uses the *.heroku.com certification. You don't need to buy or configure a certificate, it just works. https://yourcustomdomain.com will work, but it will produce a warning in the browser."
I'll probably switch to another mode as I add a certificate, however that does not seem to be the problem, based on the previous statement.
On the server, I get:
You need to fix the values for the ports since they default to 8080 and 8443. See the section on Channel Security in the docs - http://grails-plugins.github.com/grails-spring-security-core/docs/manual/ - about the grails.plugins.springsecurity.portMapper.httpPort and grails.plugins.springsecurity.portMapper.httpsPort config attributes.
For anyone else stumbling into this (as I did) the problem is that your app doesn't actually receive the request as HTTPS. Rather, Heroku replaces the HTTPS with a "X-Forwarded-Proto" header. Spring-security's HTTPS redirection is then putting you into an infinite redirect loop because it always detects the request as HTTP.
You can write your own SecureChannelProcessor to deal with this:
public class HerokuSecureChannelProcessor extends SecureChannelProcessor {
#Override
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config)
throws IOException, ServletException {
Assert.isTrue((invocation != null) && (config != null),
"Nulls cannot be provided");
for (ConfigAttribute attribute : config) {
if (supports(attribute)) {
String header = invocation.getHttpRequest().getHeader("X-Forwarded-Proto");
if(header == null){
// proceed normally
if (!invocation.getHttpRequest().isSecure()) {
getEntryPoint().commence(invocation.getRequest(), invocation.getResponse());
}
} else {
// use heroku header instead
if("http".equals(header)) {
getEntryPoint().commence(invocation.getRequest(), invocation.getResponse());
}
}
}
}
}
}
When you initially set up IIS Express to enable SSL, it defaults the port to 44300. Unfortunately, when I try to access my site in on https://localhost/ it doesn't work unless I use the port number 44300 - https://localhost:44300/.
The links are generated using the following:
<%= Html.ActionLink("Index", "Index", "Home", new { #action = "https://" + Request.Hostname + Url.Action("Index", "Home") }) %>
Although an ugly solution, the #action keyword can override the generated route, but it means that the application would seemingly need to be aware of any non-standard ports (eg 44300).
The problem with that is that I'd be writing something to solve a problem that would only occur in a development environment.
So my question is... How do I change the port to 443 and have IIS Express like it?
Config for my site is below:
<site name="MySite" id="2" serverAutoStart="true">
<application path="/">
<virtualDirectory path="/" physicalPath="C:\Inetpub\MySite" />
</application>
<bindings>
<binding protocol="http" bindingInformation=":80:" />
<binding protocol="https" bindingInformation=":44300:" />
</bindings>
</site>
Many thanks in advance.
Update:
This question has been answered by Divya over on the IIS forums.
This question has been answered by Divya over on the IIS forums.
Once you enable SSL for a website in WebMatrix, it defaults to port 44300 and does all the bindings in the background. I am hoping that you tried to change this port to 443 in the config file. Once that is done and saved, you also need to modify the binding in http.sys. You would need to delete the existing entry for port 44300 and add the entry for port 443.
To do this, you could use httpcfg (WinXp/Win2003) or 'netsh http' (WinVista/Win2K8/Win7).
Here are the commands for netsh:
1) Get the appid and certhash for the existing entry of 44300 (I
assume, you are going to use the same certificate which WebMatrix
installs by default. If you want to change the certificate as well,
get the certificate hash of the certificate from the certificate
store): netsh http show sslcert. In the output search for entry for
port 44300 and copy certhash and appID.
2) Delete the entry for 44300: netsh http delete sslcert ipport=0.0.0.0:44300
3) Add a new entry for port 443 with certhash and appID copied in step
1. netsh http add sslcert ipport=0.0.0.0:443 certhash=<certhash> appid=<appid>
After configuring the entry in http.sys, you need to restart http
service for the changes to take effect.
net stop http
net start http
As noted by others, there are several nice ways of getting your SSL certs.
netsh http show sslcert > output.txt
or (my preferred method):
netsh http show sslcert | clip
Since I have spent much time on this topic , I would like to share my finding. I am reposting segment from my other post minus the code. Some background and explanation:
==========================================
After researching aroud, I was able to solve this issue with IIS Express and an override of the Controller class's OnAuthorization method (Ref#1). I have also gone with the route recommended by Hanselman (Ref#2). However, I was not complete satisfied with these two solutions due to two reasons:
Ref#1's OnAuthorization only works at the action level, not at the controller class level
Ref#2 requires a lot of setup (Win7 SDK for makecert), netsh commands, and, in order to use port 80 and port 443, I need to launch VS2010 as administrator, which I frown upon.
So, I came up with this solution that is quite simplistic with the following conditions:
I want to be able to use the RequireHttps attribute at Controller class or action level
I want MVC to use HTTPS when the RequireHttps attribute is present, and use HTTP if it is absent
I do not want to have to run Visual Studio as administrator
I want to be able to use any HTTP and HTTPS ports that are assigned by IIS Express
I can reuse the self-signed SSL cert of IIS Express, and I do not care if I see the invalid SSL Prompt
=========================================
You can find my solution/code here ==> ASP.NET MVC RequireHttps in Production Only
The port 44300 is sequential: 00 mean that its the first application you have configured as SSL enabled; 01 will be the second one and so on.
Since I also require my website to only work in HTTPS by adding the [RequireHttps] global attribute, I had some trouble debugging. When launched, it was automatically redirecting to https://localhost/
To fix this problem when debugging a web site, I simply create a new RequireHttpsAttribute that specify the port
#if DEBUG
public class RequireHttpsAttribute : System.Web.Mvc.RequireHttpsAttribute
{
protected override void HandleNonHttpsRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
base.HandleNonHttpsRequest(filterContext);
var result = (RedirectResult)filterContext.Result;
var uri = new UriBuilder(result.Url);
uri.Port = 44301;
filterContext.Result = new RedirectResult(uri.ToString());
}
}
#endif
Use this class when debugging only. When deployed to IIS7, you should use Url rewriting to redirect to HTTPS.
Dan answer is right but if you still have problems with configuring IIS Express to serve your website with http and https on standard ports here is nice tutorial that that guide you step by step:
http://www.lansweeper.com/kb/54/How-to-configure-SSL-in-IIS-Express.html
In my case I accidentally deleted IIS Express certificate. I think it is generated the first time you use SSL in Visual Studio (F4 on selected project to get properties window and checking 'SSS Enabled' checkbox). This tutorial guided me how to create certificate and fix it.
Create class
public class RequireSSLAttribute: RequireHttpsAttribute
{
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
base.HandleNonHttpsRequest(filterContext);
if (filterContext.HttpContext.Request.Url.Host.ToLower().Equals("localhost"))
{
// redirect to HTTPS version of page
string localhostSSLPort = System.Configuration.ConfigurationManager.AppSettings["localhostSSLPort"];
string url = "https://" + filterContext.HttpContext.Request.Url.Host + ":" + localhostSSLPort + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
}
And inside your web config add something like this
<appSettings>
<add key="localhostSSLPort" value="44300"/>
</appSettings>
And then you use it like
[RequireSSL]
public class AdminController : Controller
{
...
}