Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get Principal from a Keycloak secured Spring Boot application

I am trying to modify code from a Keycloak and Spring Boot tutorial that I found online in various places (the code looks to have been duplicated). In any event, although Keycloak secures the proper URLs and Spring Security facilitates, when attempting to retrieve the Principal, it comes back as null. Here is my application.properties file:

keycloak.auth-server-url=http://localhost:9090/auth
keycloak.realm=<some realm>
keycloak.resource=<some resource>
keycloak.ssl-required=external
keycloak.public-client=true
keycloak.principal-attribute=preferred_username
keycloak.use-resource-role-mappings=true
server.port=8100

Here is my SecurityConfig.java file (pristine from the tutorials):

package com.bme.keycloakdemo.configuration;

import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    // Submits the KeycloakAuthenticationProvider to the AuthenticationManager
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    // Specifies the session authentication strategy
    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
            .antMatchers("/demo*")
            .hasRole("user")
            .anyRequest()
            .permitAll();
    }
}

My web controller follows:

package com.bme.keycloakdemo.controllers;

import java.security.Principal;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/demo")
public class DemoController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @GetMapping(value = "/first", produces = {MediaType.APPLICATION_JSON_VALUE})
    public String first(Principal principal) {
        logger.info("WE GOT HERE");
        String retval = "WE GOT HERE! ";
        if (principal != null) {
            retval += principal.toString();
        }
        else {
            retval += "PRINCIPAL IS NULL";
        }
        return retval;
    }

    @GetMapping(value = "/second", produces = {MediaType.APPLICATION_JSON_VALUE})
    public String second(Principal principal) {
        logger.info("WE GOT HERE");
        String retval = "WE GOT HERE! " + principal.getName();
        return retval;
    }

    @GetMapping(value = "/third", produces = {MediaType.APPLICATION_JSON_VALUE})
    public String third(HttpServletRequest request) {
        logger.info("WE GOT HERE");
        Principal principal = request.getUserPrincipal();
        String retval = "WE GOT HERE! ";
        if (principal != null) {
            retval += principal.toString();
        }
        else {
            retval += "PRINCIPAL IS NULL";
        }
        return retval;
    }
}

Finally, here is my Maven configuration in case it makes a difference:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.batman-evolution</groupId>
    <artifactId>keycloakjournal</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>keycloakjournal</name>
    <description>Journal Secured with Keycloak</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.16.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <keycloak.version>3.4.0.Final</keycloak.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.keycloak.bom</groupId>
                <artifactId>keycloak-adapter-bom</artifactId>
                <version>${keycloak.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

So to recap the question, I am looking to get the Principal from Keycloak. I am currently using Spring Security to return the Principal but really do not care one way or the other so long as the Principal is usable. Thanks for your help!

like image 963
Clayton Avatar asked Dec 03 '18 02:12

Clayton


People also ask

How does a spring boot connect to a Keycloak?

The Keycloak Spring Boot adapter capitalizes on Spring Boot's auto-configuration, so all we need to do is add the Keycloak Spring Boot starter to our project. The following embedded containers are supported now and don't require any extra dependencies if using Spring Boot Keycloak Starter: Tomcat. Undertow.


1 Answers

This worked for me:

@GetMapping("/details")
public MyUserDetails getUserDetails(Principal principal) {

    KeycloakAuthenticationToken kp = (KeycloakAuthenticationToken) principal;

    SimpleKeycloakAccount simpleKeycloakAccount = (SimpleKeycloakAccount) kp.getDetails();

    AccesToken token  = simpleKeycloakAccount.getKeycloakSecurityContext().getToken();
    
    return  new MyUserDetails(token.getGivenName(), token.getFamilyName(), token.getEmail());
}
like image 155
Prodromos Sarakinou Avatar answered Oct 09 '22 07:10

Prodromos Sarakinou