Use Keycloak and JWT Statelessly in Spring Boot - spring-boot

I need to use Keycloak and prefer to use stateless JWT tokens with my Spring Boot application. I can get it to run OK with sessions, but when converting this I need help
Forcing Spring Boot Security to check for logins
Allow a /logout URL (that goes to Keycloak)
My code "runs", but when I hit the initial page, I'm seeing log messages that seem to suggest it did not detect any sign of being logged in. When this happens, I'd like to force Spring Boot to redirect to the login page just like it would have had this been a stateful application.
org.springframework.web.servlet.FrameworkServlet: Failed to complete request: java.lang.NullPointerException: Cannot invoke "org.springframework.security.authentication.AbstractAuthenticationToken.getName()" because "authenticationToken" is null
org.springframework.security.web.context.HttpSessionSecurityContextRepository$SaveToSessionResponseWrapper: Did not store anonymous SecurityContext
org.springframework.security.web.context.SecurityContextPersistenceFilter: Cleared SecurityContextHolder to complete request
org.apache.juli.logging.DirectJDKLog: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.security.authentication.AbstractAuthenticationToken.getName()" because "authenticationToken" is null] with root cause
java.lang.NullPointerException: Cannot invoke "org.springframework.security.authentication.AbstractAuthenticationToken.getName()" because "authenticationToken" is null
Here's my HttpSecurity snippet:
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.csrf()
// .disable().exceptionHandling().accessDeniedPage("/access-denied")
.and()
.authorizeRequests()
.antMatchers("/sso/**").permitAll()
.antMatchers("/error/**").permitAll()
.antMatchers("/css/**","/contact-us","/actuator/**","/isalive/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()
.defaultSuccessUrl("/myfirstpage",true)
.and().exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.oauth2ResourceServer().jwt();
}
I know I'm missing something, but I thought Keycloak provided a lot of these things OOTB. The initial URL is /. I had hoped the .authenticated() would force it to authenticate against all patterns not permitted, but I'm likely wrong. What am I missing?
Please note, the internet is awash with examples of Spring Boot + Keycloak (a few are even good). It also has a lot of Spring Boot + OAuth + Stateless JWT. It does not have (that I can tell) a lot of Spring Boot + Keycloak + Stateless JWT. I got the little that I could find from this JHipster repo, but I feel like I'm missing some grand magical step.

Resource-server should return 401 (unauthorized) when authentication is missing or invalid (expired, wrong issuer, etc.) and client should handle redirection to authorization-server.
Things get messy when you try to merge different OAuth2 actors (client, resource-server and authorization-server) into a single application.
Are you sure you want a Spring client (and not an Angular / React / Vue / Flutter / whatever client-side rendering framework)?
If yes, maybe should you start by splitting client (presenting login, logout and Thymeleaf or whatever pages) and resource-server (REST API) apps. You'll better understand spring-security conf you write (and can assert it works as expected individually).
Resource-server configuration (REST API)
Be aware that Keycloak adapters for spring are deprecated.
Easiest solution is spring-addons-webmvc-jwt-resource-server (support multi-tenancy, stateless by default, CORS configuration from properties, easy Keycloak roles mapping to Spring authorities and more).
You can also work directly with spring-boot-starter-oauth2-resource-server, but it requires more java conf.
Client configuration (pages including login & logout)
if keeping Spring, refer to spring-boot documentation: it's clear and always up to date (as opposed to most tutorials)
if using a "modern" client-side framework, find a lib from certified list
I have a complete sample (Ionic-Angular UI with spring RESTful API) there, but it might be a little complicated as starter.

You will need spring-security and keycloak-adapters
This guid has full explanation how to setup and secure it
https://keepgrowing.in/java/springboot/keycloak-with-spring-boot-1-configure-spring-security-with-keycloak/

Related

How to setup ForwardedHeaderFilter for login using Spring Security without Spring Boot?

I am looking to setup the ForwardedHeaderFilter in spring security so I can let spring know which protocol to use after login. I have several app servers behind a load-balancer (using ssl termination) and spring security is redirecting the user using http (instead of https). Because of this, my users are now getting a obtrusive warning message. The only examples I can find online are with spring boot which I do not implement.
I thought of using "addFilterBefore()" method to my security configuration, but the filter is never called.
Any ideas?
// Apply sameOrigin policy for iframe embeddings
http.headers().frameOptions().sameOrigin();
// ********* Add filter here? *******
http.addFilterBefore(new ForwardedHeaderFilter(), ChannelProcessingFilter.class);
// Authorization filters
http.authorizeRequests().antMatchers("/sysAdmin/**", "/monitoring/**").access("isFullyAuthenticated() and hasRole('GOD')");
http.authorizeRequests().antMatchers("/app/**").authenticated();
http.authorizeRequests().antMatchers("/**").permitAll();
http.formLogin()
.loginPage("/public/login.jsp")
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/app/Dashboard.action", false)
.failureHandler(customAuthenticationFailureHandler());
// Disable so that logout "get" url works (otherwise you have to do a html form)
http.csrf().disable();
http.logout().logoutSuccessUrl("/public/login.jsp");
http.sessionManagement()
.invalidSessionUrl("/public/expiredSession.jsp?expiredId=2")
.maximumSessions(2)
.sessionRegistry(sessionRegistry())
.expiredUrl("/public/expiredSession.jsp?expiredId=3");
I ended up adding the filter like this and everything seemed to work
// Added for load balancer headers (X-Forwarded-For, X-Forwarded-Proto, etc)
http.addFilterBefore(new ForwardedHeaderFilter(), WebAsyncManagerIntegrationFilter.class);

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).

Configure communication between multiple OAuth2 authorization servers and a single resource server

I'm currently setting up a single resource server that will be validating access tokens from various authorization servers.
Spring security (using the Okta security starter with this as well) seems to only allow me to set a single issuer URI.
I managed to find a solution that works but I'm unsure if this is the best practice/standard way of doing it. In the code snippet below I've explicitly setup the resources with Spring's Java Config for simplicity.
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/api/protected/by/authserver1")
.and()
.oauth2ResourceServer()
.jwt()
.jwtDecoder(ReactiveJwtDecoders.fromOidcIssuerLocation("https://authserver1")
.and()
.and()
.authorizeExchange()
.pathMatchers("/api/protected/by/authserver2")
.and()
.oauth2ResourceServer()
.jwt()
.jwtDecoder(ReactiveJwtDecoders.fromOidcIssuerLocation("https://authserver2");
return http.build()
}
This seems to work exactly as intended, tokens minted from one auth server and used on the endpoint validating the other receive 401. When the minted tokens are used on their respective endpoint, they are successfully validated.
It looks a little funny having .and() calls back to back, I'm under the impression that these chained calls are just creating multiple web filters under the hood? Either way, is this the standard way of enabling this functionality in a Spring application with Spring Security and WebFlux?
Additionally, I came across this SO question but I don't know that I'll be able to setup a 'federation provider' within the context of this project. However, If that approach is the best practice I'd like to know. However, I think that's happening to some extent at the Okta level with the federation broker mode on the auth server access policies...?
Either way, is this the standard way of enabling this functionality in a Spring application with Spring Security and WebFlux?
No. What's more the example you've provided won't work. You can investigate the ServerHttpSecurity implementation and see why. Actually when you call oauth2ResourceServer() it sets new OAuth2ResourceServerSpec or returns the old one which can be modified. So in your case only the second JwtDecoder will be applied, because it overrides the first one. If you want to configure oauth2ResourceServer per path you'll have to define multiple SecurityWebFilterChain as posted here https://stackoverflow.com/a/54792674/1646298 .

JSF2 BackingBean method not being called after Spring Security configuration done

I integrated an webapp that uses JSF 2 with Spring Security 3.2 and Spring 4.0 (compatible, see documentation, and this thread), using annotations, and I have this configuation:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/my-account", "**/myAccount.**").authenticated()
.and()
.formLogin().loginPage("/login").permitAll();
The login page is being showed correctly, but when I submit the username and password, JSF BakcingBean method is never called. I want to process some validations (required fileds, etc) on this method and throw exceptions (required field messages).
If I comment the line that setup my custom login page, the desired method is called.
This article, and this other, are examples of what I'm trying to do. Notice that the methods declared on the managed beans, apparently, are being called.
The question are: am I forgetting some configuration? How to do to Spring let JSF perform my validations, display required fields messages, etc?
People, after searching all this day on the internet I haven't found any example of this being doing using annotations.
I just migrated to XML files and now everything works.
I'm not an expert of Spring, but based in just what I tested, I think that using annotations Spring created that automatic filters that, for some reason, intercepted all the requests coming from the configured custom login form, blocking the JSF from handling the requests. This can be happening because some undocumented incompatibility between "Spring 4" and Spring "Security 3.2". This incompatibility doesn't occur when using XMLs.
If you create the security filters on the classic manual way on the web.xml, and configure your custom login form on Spring XML files, you can use the JSF features on the login form again.
Same security configuration of annotation migrated to XML and it worked.
PS: Sorry, I can't share the detailed files because this time it's not open source.
I answered this in the Spring Security JIRA at SEC-2761, but I'm posting here to help anyone else that stumbles across this issue.
The problem is that Java Configuration defaults the login processing URL to be a POST to the value of the login form. This means since the login page is configured to be loginPage("/login") a POST to /login will be intercepted by Spring Security.
To avoid this problem, you can either
perform a POST to a different URL and have the LoginController process that URL.
configure Spring Security to intercept a different URL using .loginProcessingUrl("/j_spring_security_check")
An example configuration for option 2 can be seen below:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/j_spring_security_check")
...
}
I attached a working sample application to the previously mentioned JIRA. You can download it here.

Resources