Spring Boot Security and Keycloak - receive 403 forbidden /sso/login after successfull login when using HTTPS - spring-boot

I have a Spring Boot App using Spring Security and Keycloak as IDP.
Keycloak is available under https://auth.example.com, having a realm backo with a client backo-core and a testuser configured with the required roles.
If I configure my local app, using
keycloak.enabled=true
keycloak.auth-server-url=https://auth.example.com/auth
keycloak.realm=backo
keycloak.public-client=true
keycloak.resource=backo-core
keycloak.ssl-required=external
keycloak.confidential-port=443
Everything works fine.
If I deploy it to my DEV environment under https://backo.example.com/backo with https://backo.example.com/sso/login, when accessing the app I get redirect to the Keycloak login. After successful login I get redirected to the redirect URL with the state, session-state and code parameters but receive 403.
https://backo.example.com/sso/login?state=...&session_state=...&code=...
with request initiator chain coming from https://auth.example.com/auth/realms/backo/login-action/authenticate?...
If I set locally keycloak.ssl-required=all, the redirect_url parameter when accessing the login page is https://localhost/sso/login, so I can change it manually to http://localhost:8080/sso/login. After a successful login I have the exact same problem with a 403 Forbidden response for
http://localhost:8080/sso/login?state=...&session_state=...&code=...
with request initiator chain coming from https://auth.example.com/auth/realms/backo/login-action/authenticate?...
This is how my security config looks like
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/backo").hasAnyRole(ROLE_ADMIN, ROLE_BACKOFFICE)
.anyRequest().permitAll();
http.csrf().disable();
http.headers().frameOptions().disable(); // otherwise Vaadin doesn't work properly
http
.logout()
.addLogoutHandler(keycloakLogoutHandler())
.logoutUrl("/sso/logout").permitAll()
.logoutSuccessUrl("/");
}
You can ignore this Vaadin workaround, as it fails in /sso/login already.
http.csrf().disable(); is required to prevent other 403 Forbidden errors on post request while authorization.
One thing that could be worth mentioning:
The Spring Boot App has no HTTPS enabled, in our dev environment it sits behind an AWS Application loadbalancer ALB. HTTPS is terminated in the ALB.

We have faced the similar problem three days ago with spring boot thymeleaf application. We have used keycloak adapter (13.0.1) for spring boot.
After successful login it was redirecting to /sso/login page with 403 error.
After lots of effort we finally got the configuration required to fix this issue. you need to use the below application properties :
server.use-forward-headers=true
keycloak.public-client=true
keycloak.ssl-required=all
keycloak.confidential-port=443

Related

How do I modify spring boot security redirect on login to not redirect back to login?

We have a spring boot application configured as an oauth2 client. Occasionally, we have people where their browser sends a request like this to the application:
https://app/login?code=XXX&state=ZZZ
The code and state were cached from a previous authentication attempt and are invalid right now.
Spring security sees that this person is not authenticated, so it redirects them to /login which then does the whole oauth2 authentication but then after they are authenticated, spring security sends them back to /login?code=XXX&state=ZZZ because that was their original request. When that happens, it tries to validate the code and state but fails and sends them to an error page. This is a problem when supporting the app because the user is authentcated.
Is there a way to change the logic of the the storing of the initial request so that if it is /login we can replace that with /? There might be other solutions we haven't thought of. Any help would be appreciated.
Our app is currently using Spring boot 2 but I've tried this with the latest version of Spring boot 3 and it is still an issue. We have been unable to change the browser behavior so would like to solve this on the server if possible.
Here is our configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/info", "/static/**").permitAll()
.anyRequest().authenticated().and()
.csrf();
}
If I understand you correctly, you want to avoid redirect only sometimes (so SpringSecurity's defaultSuccessUrl is not an option).
If so, you can implement your own AuthenricationSuccessHandler like this:
...
.successHandler(
(request, response, authentication) -> {
if (request.getRequestURI().equals("/your/invalid/path"))
response.sendRedirect("/");
}
...

Spring SAML SSO with OKTA - InResponseTo when changing web app context

We're having a lot of trouble with OKTA SAML SSO integration with Spring Security. We're using the saml-dsl extension to Spring Security to configure the auth, and everything works fine on HTTP, however when we try to use HTTPS the authentication only works when the app is deployed on root (/) context. When we change the context to anything else, it stops working and starts throwing InResponseTo field errors and sometimes with different configurations it comes to a redirect loop.
Here's the configuration we're using:
http
.csrf()
.disable();
http
.sessionManagement().sessionFixation().none();
http
.logout()
.logoutSuccessUrl("/");
http
.authorizeRequests()
.antMatchers("/saml*").permitAll()
.anyRequest().authenticated()
.and()
.apply(samlConfigurer())
.serviceProvider()
.keyStore()
.storeFilePath(config.getKeystorePath())
.password(config.getKeystorePassword())
.keyname(config.getKeyAlias())
.keyPassword(config.getKeystorePassword())
.and()
.protocol("https")
.hostname(String.format("%s:%s", serverURL, config.getServerPort()))
.basePath("/"+contextRoot)
.and()
.identityProvider()
.metadataFilePath(config.getMetadataUrl());
And we should have our OKTA setup properly as well ( https://localhost:8443/ourappcontext/saml/SSO for testing, all the other stuff too )
We've tried most of the solutions proposed on here and the Spring documentation ( empty factory, spring session management and session fixation and so on ) and nothing seems to work. Are we doing something wrong? We're currently not generation any SP metadata, could it be that this is at fault and the request is somehow redirected to the wrong place or something? Really confused as of right now, first time using SAML and I'm not sure if it's the extension, the OKTA config or the Spring config...
We're deploying on Wildfly and you set the application context on there through a separate jboss-web.xml, if that matters at all.
By default the HttpSessionStorageFactory is used and this provides HttpSessionStorage SAML message store.
--> The HTTP session cookie is the browser side key to the server side SAML message store.
When the HTTP session cookie is not sent by the browser when the SAML response is delivered to Spring Security SAML SP, it can not find the related SAML AuthNRequest and fails to compare 'InResponseTo' value with 'ID' value of matching AuthNRequest.
If you can not assure HTTP session cookie is delivered you may implement your own SAMLMessageStorageFactory and SAMLMessageStorage (as I did).

Spring Cloud Gateway deletes SESSION cookie after a call to resource server

I'm having a problem with SESSION cookie being reset by Spring Cloud Gateway after a call to a resource server.
I have an Angular application, a Spring Cloud Gateway application, an external Authorization Server and a Resource Server of my own.
The Angular application first authorizes via Spring Cloud Gateway app (who delegates this work to external Authorization Server using OAuth2) and receives a SESSION cookie. At this point the user is authenticated and Authentication object is available in Spring Cloud Gateway app.
Next, the Angular app calls an endpoint of Spring Cloud Gateway app, which actually forwards the call to the Resource Server (and includes the Bearer token in the call, so the call works fine), the Resource server returns some result, which is successfully send back through the Spring Cloud Gateway app to the Angular app. BUT alongside successful response the Spring Cloud Gateway app sends this header:
set-cookie: SESSION=; Max-Age=0; Expires=Sat, 17 Aug 2019 20:39:44 GMT; Path=/; HTTPOnly
which kills the cookie on the client side and makes subsequent calls impossible, even though the Authentication object is still alive and the session looks to fine as well.
Does anyone know what can be the reason of such behavior?
We had the exact issue in our WebFlux resource servers -- the API gateway would proxy a request to a resource server, so the first request worked, but subsequent requests would try to authenticate again because the SESSION cookie was cleared out, resulting in some X-XSRF-TOKEN errors.
We solved this by adding .requestCache().requestCache(NoOpServerRequestCache.getInstance()) to our securityWebFilterChain bean definition in our WebFlux resource servers.
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
class ResourceServerConfiguration {
#Value('${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}')
String jwkSetUri
#Bean
SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.csrf().disable()
.requestCache().requestCache(NoOpServerRequestCache.getInstance()).and()
.httpBasic().disable()
.formLogin().disable()
.oauth2ResourceServer().jwt().jwkSetUri(jwkSetUri)
.and().and()
.authorizeExchange()
.pathMatchers("/v1/**").authenticated()
}
}
In the "classic" MVC world, you would configure your ResourceServerConfigurer classes like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
Update 11/22/2022
Our microservice architecture has begun to expand, and we started seeing this issue again on services not owned by our team. Turns out they had stateful web services, meaning, sessions were created when calling them which caused the SESSION cookie to get overridden on the Spring Cloud Gateway client application. We applied this to our configuration to permanently resolve this issue:
server:
reactive:
session:
cookie:
name: SESSION_${spring.application.name}
👆 This eliminates an issue where SESSION cookies from other web service calls collide with the Gateway client's SESSION cookie.
I had encountered the same case.
Did the external Authorization Server produce a cookie which is base64 encode?
Such as Set-Cookie: SESSION=YWZjOTc4YmUtOTNmNy00N2UxLTg0NjgtYWJlNWMwZmNiOWUx
If so, it will cause the problem.
The CookieWebSessionIdResolver defined in Spring Web used by Spring Cloud Gateway does not deal with the base64 encoded cookie values. Instead, it directly uses the raw cookie value to find the corresponding session in the storage. Obviously, no Authentication object will be found. So that Spring Cloud Gateway choose to kill the "invalid" cookie given by the Angular app.
There are two solutions given below.
Disable base64 encoding of cookie values in the external Authorization Server if it is also managed by you.
Override WebSessionIdResolver to change the default behaviour so as to decode cookie values when reading by session manager. And don't forget to register it as a Spring Bean in your Spring Cloud Gateway implementation.
In my case, Solution 1 was choosen. My Authorization Server uses Spring Security + Spring Session. I changed the default settings of HttpSessionIdResolver like this.
CookieHttpSessionIdResolver cookieHttpSessionIdResolver = new CookieHttpSessionIdResolver();
DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
defaultCookieSerializer.setUseBase64Encoding(false);
defaultCookieSerializer.setCookieName("SESSION");
cookieHttpSessionIdResolver.setCookieSerializer(defaultCookieSerializer);

Using SAML with Spring Boot behind an ELB redirects to http instead of https

I'm trying to use Okta to authenticate users from a SpringBoot application.
I've setup the app following the Okta Tutorial from : https://developer.okta.com/blog/2017/03/16/spring-boot-saml
However my app is behind an ELB, and as such the TLS is being terminated at the LB. So I've modified the configuration from the tutorial to suit my needs.
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/saml*").permitAll()
.anyRequest().authenticated()
.and()
.apply(saml())
.serviceProvider()
.keyStore()
.storeFilePath(this.keyStoreFilePath)
.password(this.password)
.keyname(this.keyAlias)
.keyPassword(this.password)
.and()
.protocol("https")
.hostname(String.format("%s", serverName))
.basePath("/")
.and()
.identityProvider()
.metadataFilePath(this.metadataUrl);
}
This does the trick but there is a problem. After the user is authenticated by Okta, the user is finally redirected to a http URL instead of a https URL. I am thinking the reason for this is that the TLS is being terminated at the LB and my app is actually receiving the request with http which is being sent in the RelayState.
This is something I found : spring-boot-security-saml-config-options.md.
It contains a list of SAML properties for spring boot security. I added the following to the application.properties file
saml.sso.context-provider.lb.enabled = true
saml.sso.context-provider.lb.scheme=https
saml.sso.profile-options.relay-state=<https://my.website.com>
It doesn't change the http redirection. Is there something I am doing wrong?
When a SAML 2.0 IdP like Okta redirects back to you application the endpoint url is either based on the SAML 2.0 metadata you application expose or the configuration in the IdP.
Furthermore, it is optional to add a Destination property in SAML 2.0 AuthnRequest:
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
Destination="https://my.website.com" IssueInstant="2018-11-22T09:23:08.844Z" Version="2.0" ID="id-f8ee3ab1-6745-42d5-b00f-7845b97fe953">
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion"> ... </Issuer>
...
</samlp:AuthnRequest>

Spring Security Oauth2 SSO with Zuul Proxy

I'm modifying the oauth2-vanilla sample from Springs excellent security tutorials. The oauth2-vanilla combines the Zuul Proxy and the UI into a single application. I would like to seperate the Zuul Proxy and the UI. (The Zuul Proxy should act as an API gateway and as a reverse proxy for several UIs).
When accessing the UI via the zuul proxy, it should be able to do SSO based on Oauth2 between the UI and the resource backend.
The oauth2-vanilla looks like this
Where I want to move to something like this :
I've removed the UI part from the gateway, and added a zuul route for the ui
zuul:
routes:
resource:
url: http://localhost:9000
user:
url: http://localhost:9999/uaa/user
ui:
url: http://localhost:8080
I created a new UI webapp containing the UI (Angular stuff) with an #EnableOAuth2Sso annotation.
So I'm accessing the UI via http://localhost:8888 (through the zuul proxy).
After authenticating and doing through the UI flow, I can access the /user endpoint that returns me the user. (During debugging, I see that when I access the /user endpoint that I have an HTTP Session with an OAuth2Authentication.
When I access the /resource endpoint however, the HttpSessionSecurityContextRepository cannot find a session and is unable to build a context with the OAuth2Authentication.
I've created a git repository with the modified sample.
I'm guessing there is something wrong with the gateway configuration.
I've tried changing cookie paths, changing HttpSecurity rules in the proxy but I cannot get it to work.
What I don't understand is why the UI, when accessed through the proxy is able to resolve the /user endpoint fine (with an HTTP session and a OAuth2Authentication), but it is unable to access the /resource endpoint.
Also, as the UI is now running in the /ui context, it seems that I need to have the following code in the gateway for it to load up the angular css / js files.
.antMatchers("/ui/index.html", "/ui/home.html", "ui/css/**", "/ui/js/**").permitAll().anyRequest().authenticated();
It also doesn't seem right that I need to prefix it with the zuul ui route.
Any help would be appreciated.
I was never able to get the #EnableOauthSso to work. Instead, I annotated as an #EnableResourceServer and created a security config for Zuul.
#Configuration
#EnableResourceServer
public class JwtSecurityConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.antMatchers("/**").hasAuthority("ROLE_API")
.and()
.csrf().disable();
}
}

Resources