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

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 .

Related

Default Login Form for Secured Methodes in Spring Security

Maybe I am thinking completely the wrong way. I want to use Spring Security for my web application and only secure some functions of my Controller class without specifying the URLs.
When I include the dependency spring-boot-starter-security everything is secured by default with a side default login form. So far so good.
Now I activate method security with #EnableGlobalMethodSecurity(securedEnabled = true) and mark some of my methods with #Secured("USER").
To have no security as default I define a custom SecurityFilterChain:
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests((requests) -> requests.anyRequest().permitAll())
.build();
}
Of course I have a test user defined:
#Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("test")
.password("test")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
I get a 403 Forbidden on the secured methods. And a 404 Not Found when I add this to the SecurityFilterChain:
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
Because the default login form is missing.
What do I need to do to get the default login form for the secured methods?
I want to use Spring Security for my web application and only secure some functions of my Controller class without specifying the URLs.
Spring Security's web support is URL-based, so this does seem to contradict it's purpose. You can use only method-based security, but all of the behavior you're missing is expected because the filter chain is not in play. See Spring Security filter chain and Method Security.
To have no security as default I define a custom SecurityFilterChain:
This would not be a best practice. Consider what happens when a developer forgets to add the #Secured or similar annotation. Defense in depth would be a better approach, which is why Spring Security requires every endpoint to be authenticated by default.
I get a 403 Forbidden on the secured methods. And a 404 Not Found when I add this to the SecurityFilterChain:
At the method level, the ExceptionTranslationFilter does not trigger the AuthenticationEntryPoint. This would explain why you don't get the default behavior of a redirect to /login.
When defining a custom login page with http.formLogin().loginPage("/login"), you are responsible for providing a login page. This would explain why you receive a 404 Not Found.
In order to get the default behavior, you need to specify something that causes requests to be processed appropriately by the filter chain prior to method security. For example, if all of your secured endpoints start with /secured, you should add that as an authorization rule:
http.authorizeHttpRequests((requests) -> requests
.mvcMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
);
But this still leaves a gap and does not practice defense in depth. So the best configuration would invert the rules and identify only URLs that should be allowed by default, such as static resources.
http.authorizeHttpRequests((requests) -> requests
.mvcMatchers("/static/**").permitAll()
.anyRequest().authenticated()
);
In either case, security exceptions would be thrown by the filter chain while processing requests to #Secured endpoints, and so the AuthenticationEntryPoint will be triggered. Keep in mind however that authentication and authorization are related but separate concepts. In Spring Security, it's the ExceptionTranslationFilter that is tying them together in a way that produces the expected user experience.

Use Keycloak and JWT Statelessly in 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/

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

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 boot security, applying an authentication filter only to certain routes

I'm building a web application which will contain an API and an admin interface in a single application. As a result, I need two types of authentication, token based auth for the API, and form based auth for the admin interface.
I've almost got it working by applying a filter to authenticate API tokens, however the filter is being executed for every request, and I only want it to be executes on paths matching '/api/**'.
Hopefully it's clear from my security configuration what I'm trying to do, but sadly it doesn't work as expected.
All API requests will start '/api/', while all admin interface requests will start '/admin/'. So I was hoping to apply different security rules to each.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/api/account/login").permitAll();
http.addFilterBefore(webServiceAuthenticationFilter, UsernamePasswordAuthenticationFilter.class).authorizeRequests().antMatchers("/api/**").hasAuthority("APIUSER");
http.authorizeRequests().antMatchers("/admin/**").authenticated().and()
.formLogin()
.loginPage("/admin/account/login").permitAll()
.passwordParameter("password")
.usernameParameter("username")
.failureUrl("/admin/account/login?error").permitAll()
.defaultSuccessUrl("/admin/dashboard")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/admin/account/logout"))
.logoutSuccessUrl("/admin/account/login");
http.exceptionHandling().accessDeniedPage("/admin/account/forbidden");
}
There is a way to configure several HttpSecuritys depending on the url by using the antMatcher (or in more advanced cases requestMatchers) on the HttpSecurity directly (not on authorizeRequests!). See: https://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/config/annotation/web/builders/HttpSecurity.html#antMatcher-java.lang.String-
This requires defining several WebSecurityConfigurerAdapters with defined #Orders such that Spring uses the first appropriate configuration depending on the given url and the order of the configurations. For more details please take a look at the docs at http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#multiple-httpsecurity
I don't know if this is the 'correct' way of doing it, but I've managed to only get the filters code to execute when a route is matched with '/api/**' by adding an if statement to the filter itself;
So within my filter I have the following;
AntPathMatcher urlMatch = new AntPathMatcher();
if (urlMatch.match("/api/**", httpRequest.getRequestURI().substring(httpRequest.getContextPath().length()))) {
// Token authentication in here
}

Resources