javaspring-bootspring-securitykeycloaksecurity-context

NullPointer trying to get user info with Keycloak in Spring Boot


I am using Keycloak in my REST application with spring-boot. It works correctly, I have defined roles in Keycloak, then in my Config.class I allow access to the end-points that interest me according to the role of each user. I have the problem when trying to retrieve the user information in my back (name, principal, authorities...).

I have read various SO POSTS like these:

how-to-get-principal-from-a-keycloak-secured-spring-boot-application

nullpointer-when-securing-spring-boot-rest-service-with-keycloak

but I can't find any that work for me. If I use SecurityContext or KeycloakAuthenticationToken, I get null when calling the getAuthentication method.

In my code, in Controller class I have the following:

private SecurityContext securityContext = SecurityContextHolder.getContext();
    
    @GetMapping(value = "/getLicenses")
    public ResponseEntity<List<License>> getLicenses() {
        System.out.println("SecurityContext: " + securityContext);
        System.out.println("Authentication securityContext: " + securityContext.getAuthentication());
        return new ResponseEntity<List<License>>(licenseService.getLicenses(), null, HttpStatus.OK);
    }

The output from console after access to /getLicenses is:

SecurityContext: org.springframework.security.core.context.SecurityContextImpl@ffffffff: Null authentication
Authentication securityContext: null

My KeycloakSecurityConfig.class

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    
    @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.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/getLicenses").hasAnyRole("GUEST", "ADMIN", "SUPERADMIN")
                .antMatchers("/getRevision/{id}").hasRole("SUPERADMIN")
                .permitAll();
    }
    
    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

It works fine, if I try to access to /getRevision/{id} end-point with a 'GUEST' role user, it can't permit it.

My .yml has a BD connection, JPA and keycloak connection:

keycloak.auth-server-url : XXXX
keycloak.realm: XXXX
keycloak.resource: login
keycloak.public-client: true

And Keycloak has 3 users with 3 different roles (1 rol each user). I can do login with Postman

Login with Postman

Does anyone know why I get null when trying to access SecurityContext? My goal is to retrieve user data


Solution

  • You are initializing the securityContext object in your controller when the controller is first created:

    private SecurityContext securityContext = SecurityContextHolder.getContext();
    

    At that time, there is no security context available, meaning that the context is always null.

    Don't use a private variable in a controller to store the context, instead, fetch it from the SecurityContextHolder in the getLicenses method:

    @GetMapping(value = "/getLicenses")
    public ResponseEntity<List<License>> getLicenses() {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        System.out.println("SecurityContext: " + securityContext);
        System.out.println("Authentication securityContext: " + securityContext.getAuthentication());
        return new ResponseEntity<List<License>>(licenseService.getLicenses(), null, HttpStatus.OK);
    }