Ahoy Spring user!
Recently, I added Okta login to my application to secure the admin section of the website. This is simple enough to accomplish but suddenly I felt the need to run my application locally without any authentication for testing purposes. As I couldn't find any good info on this on the web I decided to share the solution I came up with.
Full source code is here:
trexinc / spring-multi-web-security-config
Spring Boot and Multiple Authentication Profiles (None, Password & Okta)
A few quick words on setting up Okta authentication
- Register for a free developer account at https://developer.okta.com/
- Create a Spring Boot project with the following Spring Initialzer settings. Dependencies: Spring Web, Spring Security, Thymeleaf & OAuth2 Client. This solution works specifically with OAuth2 Client and not the native Okta library.
- In Okta application configuration change the Login redirect URIs to: http://localhost:8080/login/oauth2/code/okta
- In your application.properties configure the following:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
spring.security.oauth2.client.registration.okta.client-id=<client> spring.security.oauth2.client.registration.okta.client-secret=<client-secret> spring.security.oauth2.client.provider.okta.issuer-uri=https://<your-company>.okta.com/oauth2/default - And you need to define a Web Security Config to enable Okta authentication:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) @EnableWebSecurity public class SecurityOktaConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // require authenticated access to all resources .anyRequest().authenticated() // set logout URL .and().logout().logoutSuccessUrl("/") // enable OAuth2/OIDC .and().oauth2Client() .and().oauth2Login(); } }
Back to the actual point
To enable several different security profiles I came up with the following solution. We define several Web Security Configurations each with its own @Profile("ProfileX)
annotation, which enables this profile only when this profile is enabled in Spring.
The code looks likes this:
@Configuration | |
class WebSecurityConfig { | |
@Profile("local") | |
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) | |
@EnableWebSecurity | |
public static class SecurityDisabledConfig extends WebSecurityConfigurerAdapter { | |
@Override | |
protected void configure(HttpSecurity http) throws Exception { | |
// allow all requests | |
http.authorizeRequests().anyRequest().permitAll(); | |
} | |
} | |
@Profile("password") | |
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) | |
@EnableWebSecurity | |
public static class SecurityPasswordConfig extends WebSecurityConfigurerAdapter { | |
@Override | |
protected void configure(HttpSecurity http) throws Exception { | |
http.authorizeRequests() | |
// require authenticated access to /admin | |
.antMatchers("/admin", "/admin/**").authenticated() | |
// allow anonymous access to all other requests | |
.anyRequest().permitAll() | |
// set logout URL | |
.and().logout().logoutSuccessUrl("/") | |
// enable http basic authorization | |
.and().formLogin().and().httpBasic(); | |
} | |
} | |
@Profile("okta") | |
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) | |
@EnableWebSecurity | |
public static class SecurityOktaConfig extends WebSecurityConfigurerAdapter { | |
@Override | |
protected void configure(HttpSecurity http) throws Exception { | |
http.authorizeRequests() | |
// require authenticated access to /admin | |
.antMatchers("/admin", "/admin/**").authenticated() | |
// allow anonymous access to all other requests | |
.anyRequest().permitAll() | |
// set logout URL | |
.and().logout().logoutSuccessUrl("/") | |
// enable OAuth2/OIDC | |
.and().oauth2Client() | |
.and().oauth2Login(); | |
} | |
} | |
} |
The relevant applications.properties sections look like this (of course only the one you want to use should be enabled, and it should be enabled only at runtime in your CI/CD pipeline):
#access open to all pages | |
spring.profiles.active=local | |
#require password for /admin | |
spring.profiles.active=password | |
spring.security.user.name=<user> | |
spring.security.user.password=<password> | |
#require Okta login for /admin | |
#in Okta dashboard, the web app login redirect URI must be set to (can be localhost if needed): | |
#<your-url>/login/oauth2/code/okta | |
spring.profiles.active=okta | |
spring.security.oauth2.client.registration.okta.client-id=<client> | |
spring.security.oauth2.client.registration.okta.client-secret=<client-secret> | |
spring.security.oauth2.client.provider.okta.issuer-uri=https://<your-company>.okta.com/oauth2/default |
What is the best way to run the code locally?
Once we have the code and know how to make it work one issue remains, what is the best way to run it locally with authentication disabled without modifying the application.properties files and risking pushing those modifications to productions.
Two options that I like the most:
- Configure the relevant Spring profile in InteliJ, go to "Edit Configurations..." And set the profile as local
- Configure a bootRun task in build.gradle that will set the profile to local (unless defined otherwise in the environment):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
//Override profile when running locally bootRun { environment SPRING_PROFILES_ACTIVE: environment.SPRING_PROFILES_ACTIVE ?: "local" }
That's it. Hope you found this useful. Leave comments and likes :)
Top comments (2)
Thank you for sharing. Do we need to set up an @Order when using different configurations by profile? For example, I have a configuration class in which I'd like just to change
.antMatchers("/**").fullyAuthenticated()
to.antMatchers("/**").permitAll()
for local settings in the overriddenconfigure(HttpSecurity http)
method.No need to use @Order as long as all security adapters have a specific profile set on them.