Secure Legacy Apps with Spring Cloud Gateway

Secure Legacy Apps with Spring Cloud Gateway

This tutorial shows show you how to secure legacy applications with OAuth 2.0 and Spring Cloud Gateway.

Abhimanu Handoo

I have an existing application deployed in tomcat 7, I am trying to add the context path in gateway however after login it is taking to me tomcat home page only and not redirecting to the page I want. Can you help ?
My existing application runs as

http://localhost:8000/Test

One more thing how to add issuer url to tomcat ?

Thanks

Abhimanu Handoo

I have pushed the application as ROOT.war and now it is the default application for tomcat. For issuer URL I have added it on one setenv.bat for windows and added below in it
set CATALINA_OPTS="-Dokta.oauth2.issuer={issuerURL}" and then restarted the application and it worked like a charm!

Abhimanu Handoo

Hi @briandemers,
@briandemers I am facing issue when I try to deploy gateway application in external tomcat .

I am getting dependency error for various beans and for those beans are already in tomcat lib and as well in the project lib path.Can you help me in fixing this issue. One more thing have extended gateway class with SpringBootServletInitializer and overrided configure method to make it eligible for web application. Below is the error stake trace for the saem.

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘customRouteLocator’ defined in com.okta.tutorial.examplegateway.ExampleGatewayApplication: Unsatisfied dependency expressed through method ‘customRouteLocator’ parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘tokenRelayGatewayFilterFactory’ defined in class path resource [org/springframework/cloud/security/oauth2/gateway/TokenRelayAutoConfiguration.class]: Unsatisfied dependency expressed through method ‘tokenRelayGatewayFilterFactory’ parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:769)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:509)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBean…:1320)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBean…:1159)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBean…:555)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBean…:515)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistr…:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory…:860)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext…:878)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext…:550)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplication…:141)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391)
at org.springframework.boot.Sp…(SpringApplication.java:312)
at org.springframework.boot.we…(SpringBootServletInitialize…:151)
at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitialize…:131)
at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.onStartup(SpringBootServletInitialize…:91)
at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerIniti…:172)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5128)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
… 38 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘tokenRelayGatewayFilterFactory’ defined in class path resource [org/springframework/cloud/security/oauth2/gateway/TokenRelayAutoConfiguration.class]: Unsatisfied dependency expressed through method ‘tokenRelayGatewayFilterFactory’ parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:769)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:509)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBean…:1320)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBean…:1159)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBean…:555)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBean…:515)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistr…:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory…:1271)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory…:1191)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760)
… 60 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory…:1678)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory…:1237)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory…:1191)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760)
… 74 more

Abhimanu Handoo

Hi @briandemers,

I am able to successfully run this application on my local however unable to run on my server where I am using the custom configuration of okta instead of default provided by my company.

I am able to get the login form of okta and after that getting "

2020-07-09 00:30:39.035 ERROR 5981 — [ctor-http-nio-1] a.w.r.e.AbstractErrorWebExceptionHandler : [2beae42a] 500 Server Error for HTTP GET “/login/oauth2/code/okta?code=x7E2mYsXEpJINsuLfLSw&state=QfvZ-kQq247e5J5iWNPpnY0KxGYi5hdBDAYkthCoIww%3D” io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: for port number okta domaninname:443

I did telnet domainname 443 and the result is same connection refused.

Wanted to know why application trying to access 443 port in this case and is there anyway to skip this call.

Thanks,
Abhimanu Handoo

Brian Demers

Sorry @abhimanuhandoo I missed your question.

Spring Cloud Gateway requires a reactive stack, and cannot be run in Tomcat. You would need to run the Gateway app as a Jar file.

NOTE: you CAN still run your other apps as WAR files though.

Brian Demers

Hey @abhimanuhandoo
(again sorry about the delay)

I’ve seen this type of problem when the JVM was old (i.e. the certificates were out of date), or against domains that didn’t have a full certificate chain (for example if you are using Okta with a custom domain, and the domain is using LetEncrypt, the “chain” is required: https://developer.okta.com/… )

Which JVM/version/OS are you using? And if you are still running into issues make sure you hit up our support if you don’t get an answer right away here: developers@okta.com

Abhimanu Handoo

Hi Brain,

Thanks for the reply.My okta domain is custom one and when i make request to that custom domain it is under proxy server.Don’t know how to configure proxy for this application.I tried using proxy while invoking the application i.e parameter in java command.

JVM version is : 1.8
OS : Linux.

Thanks,
Abhimanu handoo

Brian Demers

Some of this depends on your proxy server. However most set the x-forwarded-for and x-forwarded-proto headers and spring detects them out of the box.

You would need to make sure your redirect URI, which in this article is: http://localhost:8080/login/oauth2/code/okta

gets updated to <a href="http://your-custom-domain.com/login/oauth2/code/okta" rel="nofollow noopener" title="http://your-custom-domain.com/login/oauth2/code/okta">http://your-custom-domain.c...</a>

If you have this set wrong you will see an error message when redirecting to Okta for your login page, If this is the case you can find your actual redirect URI in the URL. (you can also use your browsers dev tools to capture the redirect from your app to Okta)

Abhimanu Handoo

Hi Brian,

I am able to get the Login page of custom domain and after it redirects to back the application is showing error connection refused. When I enquired more on this , the guys that handle the custom domain suggested me to add proxy in the application as they said without adding proxy application won’t be able to move forward.Custom domain guys say " While browsing on browser, browser is able to detect the proxy and move forward, however for the application we need to add the same."I don’t know how to add the proxy in this application other than to add same in ( parameter in java command).

Can you guide me about the same.

Thanks,
Abhimanu Handoo

Brian Demers

The last leg of the OAuth dance, will make a connection from your Java backend (in this post the Spring Cloud Gateway app) to Okta. So you will need to configure Java to use your proxy.

If possible testing things out first, without the additional proxy/VPN may help. Once you know it works then adding in the proxy (I understand this isn’t alwasys possible though)

Abhimanu Handoo

Hi Brian,

I installed the Fiddler and checked the traffic and found that when the below is landed on gateway
/login/oauth2/code/okta?code=<somestring>&state=<somestring>
then only the gateway application is unable to process the above request.
Can you help me understand what possibly could be wrong ?

Thanks,
Abhimanu Handoo

Brian Demers

Where you able to configure your proxy?
The ?code=somestring request will need to verify the code on the backend by making a new HTTP request back to Okta. If the backend server is unable to make this request the user will be unauthorized.

Abhimanu Handoo

I tried but was not able to configure the proxy. I am bit confused with the flow as first time when the request is made it is able to go through however second time ( /login/oauth2/code/okta?code=<somestring>&state=<somestring>) is unable to go through.

Brian Demers

With this type of OAuth flow the there are a few different requests in order to ensure users are logged in securely.

* Unauthenticated users are redirect to Okta (or other OAuth IdP), Spring does this automatically
* The user logs in via the IdP, how this is implemented is up to the IdP, which means your application doesn’t need to worry about the details. (You can configure what is needed, i.e. MFA, via the Okta Admin Console)
* Once the user logs in, the IdP will redirect back to your application with a “code” (as you noted above)
NOTE: Up until now everything has been done through the front channel, (the browser)
* Your backend makes a call back to the IdP to verify the “code” using a client secret. This is a direct HTTP call from your backend to Okta (not through a browser).

This last request is why your proxy settings are required.
There is a note about this in the Spring Security docs: https://docs.spring.io/spri…

This video should walk through the OAuth specific bits in more detail
https://www.youtube.com/wat…

Abhimanu Handoo

Hi,

As per ( https://github.com/spring-c… ) isue, java argument proxy is not set there, so we need to add some dependency so as to make http proxy work.Tyring that and will update you on this here

Brian Demers

Keep us posted!

Brian Demers

Hey @abhimanuhandoo I just found a similar question, and wanted to update you.
This likely isn’t directly possible with Spring Security. But I added a few ideas/thoughts of things to try here: https://devforum.okta.com/t…