ketrab321
ketrab321

Reputation: 591

Keycloak dummy configuration

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

Answers (2)

ch4mp
ch4mp

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

cmlonder
cmlonder

Reputation: 2530

A couple of suggestions;

1. Have multiple profiles

I will suggest different profiles for different purposes; keycloak, local, keycloak-local.

Keycloak

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

}

Local

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

}

Keycloak-Local

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

2. If Resource Server: Token without interacting Identity Provider

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;

  • Generate a JWT (You can make a dummy client to Keycloak and copy it if you need some sample or generate it anywhere, note down your algorithm and public key)
  • If you used Keycloak to generate jwt, copy your public key from Realm Settings -> Keys -> Copy public key belong to algorithm (Default is RS256) and save it to somewher in your application, e.g: src/main/test/resources
  • Instead of giving 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.
  • For spring security oauth2 library here is a sample configuration:
spring
  security:
    oauth2:
      resourceserver:
        jwt:
          public-key-location: classpath:jwt_public_key.json
          jws-algorithm: RS256 # this is default, you can skip setting it
  • Now send your token as a Bearer header.

Upvotes: 3

Related Questions