I have a very special requirements in my Spring Boot web application: I have internal and external users. Internal users login to the web application by using keycloak authentication (they can work in the web application), but our external users login by simple Spring Boot authentication (what they can do is just to download some files generated by web application)
What I want to do is to have multiple authentication model: all the path except /download/* to be authenticated by our Keycloak authentication, but the path /download/* to be authenticated by SpringBoot basic authentication.
At the moment I have the following:
@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Configuration
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@Order(1)
public static class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.regexMatcher("^(?!.*/download/export/test)")
.authorizeRequests()
.anyRequest().hasAnyRole("ADMIN", "SUPER_ADMIN")
.and()
.logout().logoutSuccessUrl("/bye");
}
}
@Configuration
@Order(2)
public static class DownloadableExportFilesSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/download/export/test")
.authorizeRequests()
.anyRequest().hasRole("USER1")
.and()
.httpBasic();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password1").roles("USER1");
}
}
}
But it does not work well, because every time the external user wants to download something (/download/export/test), it prompts the login form, but after entering the correct external user username and password, than it prompts the keycloak authentication login form.
I don't get any error just a warning:
2016-06-20 16:31:28.771 WARN 6872 --- [nio-8087-exec-6] o.k.a.s.token.SpringSecurityTokenStore : Expected a KeycloakAuthenticationToken, but found org.springframework.security.authentication.UsernamePasswordAuthenticationToken@3fb541cc: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER1; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: 4C1BD3EA1FD7F50477548DEC4B5B5162; Granted Authorities: ROLE_USER1
Do you have any ideas?
The Authentication Manager is only a interface and actual implementation of the authenticate method is provided by the ProviderManager. The ProviderManager has a list of AuthenticationProviders. From it's authenticate method it calls the authenticate method of the appropriate AuthenticateProvider.
From Spring Boot 2.7, WebSecurityConfigurerAdapter is deprecated. In this tutorial, I will show you how to update your Web Security Config class in Spring Security without the WebSecurityConfigurerAdapter example.
inMemoryAuthentication() is the method of AuthenticationManagerBuilder class is used to perform in-memory authentication in the Spring Security. This method is used for creating the user with respective roles and passwords.
AuthenticationEntryPoint is used to send an HTTP response that requests credentials from a client. Sometimes a client will proactively include credentials such as a username/password to request a resource.
I experienced some headaches when implementing basic authentication next to Keycloak authentication, because still while doing multiple WebSecurityAdapter implementations 'by the book', the Keycloak authentication filter was called even when basic authentication succeeded.
The reason lies here: http://www.keycloak.org/docs/latest/securing_apps/index.html#avoid-double-filter-bean-registration
So if you use the Keycloak Spring Security Adapter together with Spring Boot, make sure to add those two beans (in addition to the valid answer by Jacob von Lingen):
@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Configuration
@Order(1) //Order is 1 -> First the special case
public static class DownloadableExportFilesSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception
{
http
.antMatcher("/download/export/test")
.authorizeRequests()
.anyRequest().hasRole("USER1")
.and()
.httpBasic();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password1").roles("USER1");
}
}
@Configuration
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
//no Order, will be configured last => All other urls should go through the keycloak adapter
public static class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
// necessary due to http://www.keycloak.org/docs/latest/securing_apps/index.html#avoid-double-filter-bean-registration
@Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
// necessary due to http://www.keycloak.org/docs/latest/securing_apps/index.html#avoid-double-filter-bean-registration
@Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Override
protected void configure(HttpSecurity http) throws Exception
{
super.configure(http);
http
.authorizeRequests()
.anyRequest().hasAnyRole("ADMIN", "SUPER_ADMIN")
.and()
.logout().logoutSuccessUrl("/bye");
}
}
}
The key for multiple HttpSecurity is to register the 'special cases' before the normal one. In other words, the /download/export/test
authentication adapter should be registered before the keycloak adapter.
Another important thing to notice, once an authentication is successful, no other adapter is called (so the .regexMatcher("^(?!.*/download/export/test)")
is not necessary). More info for Multiple HttpSecurity can be found here.
Below you code with minimal changes:
@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Configuration
@Order(1) //Order is 1 -> First the special case
public static class DownloadableExportFilesSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/download/export/test")
.authorizeRequests()
.anyRequest().hasRole("USER1")
.and()
.httpBasic();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password1").roles("USER1");
}
}
@Configuration
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@Order(2) //Order is 2 -> All other urls should go through the keycloak adapter
public static class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
//removed .regexMatcher("^(?!.*/download/export/test)")
.authorizeRequests()
.anyRequest().hasAnyRole("ADMIN", "SUPER_ADMIN")
.and()
.logout().logoutSuccessUrl("/bye");
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With