Reputation: 591
I have Spring Boot project with Keycloak as authentication provider. Is it possible to create a 'dummy auth' for a specific spring profile? I would like to have profile 'dummy-auth' which will always set KeycloakPrincipal as some dummy user. Maybe some kind of filter to replace? I have my keycloak config:
@KeycloakConfiguration
public class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
@Bean
public KeycloakSpringBootConfigResolver keycloakSpringBootConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.anyRequest()
.fullyAuthenticated();
}
}
and do not really have idea how to implement this dummy auth. Maybe better option is some kind of nested keycloak instance?
Upvotes: 0
Views: 1105
Reputation: 12629
If the aim is unit-testing a single @Component, I wouldn't touch the conf (maybe just @MockBean JwtDecoder jwtDecoder;
) and simply inject a mocked authentication in the security context, either manually with SecurityContextHolder.getContext().setAuthentication(auth)
or using something like @WithMockKeycloakAuth
from this lib I wrote. I provide various sample apps with unit tests, including one using KeycloakAuthenticationToken
as Authentication
impl. None of my unit tests require any running authorization-server.
If your intention is to start the app without an access to your company Keycloak instances, maybe should you consider running a "standalone" Keycloak instance on your dev machine. Changing more than properties values across profiles (pointing to Keycloak server A or B) has too often been the source of painful experience to me...
Upvotes: 0
Reputation: 2530
A couple of suggestions;
I will suggest different profiles for different purposes; keycloak, local, keycloak-local.
Use this profile when you want to integrate with Identity Provider and set Authenticated Object retrieved from Keycloak to your Spring context.
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@Profile( "keycloak" )
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
@Bean
public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy( )
{
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Autowired
public void configureGlobal( AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Override
protected void configure( HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/hello")
.authenticated()
.anyRequest()
.permitAll();
}
}
Create also a local profile where you don't want to ship with Identity Provider but still want to inject an authenticated user to your context
@Configuration
@EnableWebSecurity
@ComponentScan( basePackageClasses = KeycloakSecurityComponents.class )
@Profile( "local" )
public class LocalSecurityConfig extends WebSecurityConfigurerAdapter
{
@Override
protected void configure( HttpSecurity http ) throws Exception
{
http.authorizeRequests( )
.antMatchers( "/hello" )
.authenticated( )
.anyRequest( )
.permitAll( )
.and( )
.formLogin( );
}
@Autowired
public void configureGlobal( AuthenticationManagerBuilder auth )
throws Exception
{
auth
.inMemoryAuthentication( )
.withUser( "user" )
.password( passwordEncoder().encode( "password") )
.roles( "USER" );
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Or even better we can set real Keycloak Authenticated object to the context in specific profile. Use this approach if you need more complicated Authentication object in the context and you ship with KeycloakAuthenticationToken. Otherwise local profile is also fine since this requires more coding, but this is also working example:
Create a Filter, let's call it LocalFilter. This filter will create a KeycloakAuthenticationToken and set it to the spring context:
public class LocalFilter extends GenericFilterBean
{
@Override
public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain )
throws IOException, ServletException
{
SecurityContextHolder.getContext( ).setAuthentication( generateKeycloakToken( "stackoverflow.com", "ROLE_USER", "" ) );
filterChain.doFilter(servletRequest, servletResponse);
}
public static KeycloakAuthenticationToken generateKeycloakToken( String org, String roles, String permissions )
{
AccessToken accessToken = new AccessToken( );
if ( org != null && !org.isEmpty( ) )
{
accessToken.setOtherClaims( "org", org );
}
if ( permissions != null && !permissions.isEmpty( ) )
{
accessToken.setOtherClaims( "permissions", permissions );
}
RefreshableKeycloakSecurityContext rksc =
new RefreshableKeycloakSecurityContext( null, null, UUID.randomUUID( ).toString( ), accessToken, null, null,
null );
Set<String> rolesSet = new HashSet<>( );
String[] roleArr = roles.split( "," );
for ( String role : roleArr )
{
rolesSet.add( role.trim( ) );
}
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<>( "name", rksc );
Collection<GrantedAuthority> authorities = generateGrantedAuthority( roles );
return new KeycloakAuthenticationToken( new SimpleKeycloakAccount( principal, rolesSet, rksc ), false,
authorities );
}
public static Collection<GrantedAuthority> generateGrantedAuthority( String roles )
{
Collection<GrantedAuthority> authorities = new ArrayList<>( );
for ( String role : roles.split( "," ) )
{
authorities.add( new SimpleGrantedAuthority( role.trim( ) ) );
}
return authorities;
}
}
And configrue the Filter in your SecurityConfig:
@Configuration
@EnableWebSecurity
@ComponentScan( basePackageClasses = KeycloakSecurityComponents.class )
@Profile( "local-keycloak" )
public class LocalKeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
@Bean
public KeycloakSpringBootConfigResolver keycloakConfigResolver( )
{
return new KeycloakSpringBootConfigResolver( );
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy( )
{
return new RegisterSessionAuthenticationStrategy( new SessionRegistryImpl( ) );
}
@Autowired
public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception
{
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider( );
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper( new SimpleAuthorityMapper( ) );
auth.authenticationProvider( keycloakAuthenticationProvider );
}
@Override
protected void configure( HttpSecurity http ) throws Exception
{
super.configure( http );
http.addFilterBefore(new LocalFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests( )
.antMatchers( "/hello" )
.authenticated( )
.anyRequest( )
.permitAll( );
}
}
If you are using your client as a resource server to Keycloak there is a way to validate your authentication without interacting with your Identity Provider. Follow the steps;
issuer-uri
configure your service with public-key
and jws-algorithm
. By doing this, your service will not request to your Identity Provider to validate the Bearer.spring
security:
oauth2:
resourceserver:
jwt:
public-key-location: classpath:jwt_public_key.json
jws-algorithm: RS256 # this is default, you can skip setting it
Upvotes: 3