Beliebte Suchanfragen
//

Securing Spring Boot Admin & actuator endpoints with Keycloak

28.1.2019 | 9 minutes of reading time

Spring Boot Admin is a popular tool for monitoring and managing Spring Boot -based applications. In this blog post you’ll learn how to secure Spring Boot Admin itself and protect the actuator endpoints of monitored applications with Keycloak .

Overview

In our demo environment we’re going to have three components:

  • Keycloak Auth Server
  • Spring Boot Admin
  • Spring Boot App with actuator endpoints

The Keycloak Auth server is available via http://localhost:8080/auth
The Spring Boot Admin app is available via http://localhost:30001/admin
The monitored Spring Boot App is available via http://localhost:30002

Keycloak configuration

In Keycloak we’ll define a dedicated realm with the name bootadmin.

Then we’ll create two clients: app-admin, which represents the Spring Boot Admin application, and app-todo, which denotes the Spring Boot app respectively.
Well start with the definition of the monitored Spring Boot app that exposes actuator endpoints.

Client for Spring Boot app with actuator endpoints in Keycloak

Our example application is a simple to do management app with the client ID app-todo.

The app-todo client is configured as follows:

Client-Protocol: OpenID Connect
Access-Type: confidential
Standard-Flow Enabled: on
Direct-Access grants: off

Root URL: http://localhost:30002
Valid redirect URIs: /*
Base URL: /
Admin URL: /
Web Origins: +

In the credentials tab, you need to write down the Secret, as we’ll need this later for our Spring Boot app configuration.

Roles

We need to define the following roles for our app-todo client:

  • user – denotes the normal to do app users.
  • actuator – this role is used to access the actuator endpoints.

Scope

For the sake of simplicity, we set Fill Scope Allowed: on, however I’d recommend to be explicit about what roles a client might see to keep the tokens small. This also helps avoid exposing unnecessary information to a client application.

Client for Spring Boot Admin in Keycloak

The app-admin client is configured as follows:

Client-Protocol: OpenID Connect
Access-Type: confidential
Standard-Flow Enabled: on
Direct-Access grants: off
Service-Accounts Enabled: on

Root URL: http://localhost:30001
Valid redirect URIs: /*
Base URL: /admin
Admin URL: /
Web Origins: +

As before, in the credentials tab write down the Secret as we’ll need this later for our Spring Boot configuration.

Roles

We need to define the following roles for our app-admin client:

  • admin – denotes the users who can access Spring Boot Admin
  • actuator – internal Role for the service account user. This role is used to access the actuator endpoints of monitored applications.

Note that this actuator role is a composite role which includes the actuator roles of the monitored client apps.

Scope

As before we set Fill Scope Allowed: on.

Service accounts

We grant the actuator role of the app-admin to the service account user. Since the app-admin:actuator composite role includes the app-todo:actuator role, we also have access to its actuator endpoints. One can easily apply this pattern to securely monitor new applications.

With that set, the only thing that’s left to do on the Keycloak side is to create a user who can access the Spring Boot Admin UI.

For this we create a user with the username tester and password test. We also assign the admin role for the app-admin client.

The complete example, with more details about the Keycloak configuration, can be found in the spring-boot-admin-keycloak-example repository on Github.

After our Keycloak environment is configured, we can move on to the Spring Boot apps.
We begin with the Todo-Service app that we modelled as app-todo client.

Todo-Service

We’ll start with the Maven configuration for the to do service module, which looks like this:

1<?xml version="1.0" encoding="UTF-8"?>
2<project xmlns="http://maven.apache.org/POM/4.0.0"
3    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5    <modelVersion>4.0.0</modelVersion>
6    <groupId>com.example</groupId>
7    <artifactId>todo-service</artifactId>
8    <version>0.1.0-SNAPSHOT</version>
9    <packaging>jar</packaging>
10    <name>todo-service</name>
11    <description>Demo project for Spring Boot</description>
12    <parent>
13        <groupId>org.springframework.boot</groupId>
14        <artifactId>spring-boot-starter-parent</artifactId>
15        <version>2.1.2.RELEASE</version>
16        <relativePath /> <!-- lookup parent from repository -->
17    </parent>
18    <properties>
19        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
20        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
21        <java.version>1.8</java.version>
22        <keycloak.version>4.8.3.Final</keycloak.version>
23        <spring-boot-admin.version>2.1.2</spring-boot-admin.version>
24    </properties>
25    <dependencies>
26        <dependency>
27            <groupId>org.springframework.boot</groupId>
28            <artifactId>spring-boot-starter-security</artifactId>
29        </dependency>
30        <dependency>
31            <groupId>org.springframework.boot</groupId>
32            <artifactId>spring-boot-starter-web</artifactId>
33        </dependency>
34        <dependency>
35            <groupId>org.keycloak</groupId>
36            <artifactId>keycloak-spring-boot-starter</artifactId>
37        </dependency>
38        <dependency>
39            <groupId>org.springframework.boot</groupId>
40            <artifactId>spring-boot-starter-actuator</artifactId>
41        </dependency>
42        <dependency>
43            <groupId>de.codecentric</groupId>
44            <artifactId>spring-boot-admin-starter-client</artifactId>
45            <version>${spring-boot-admin.version}</version>
46        </dependency>
47        <dependency>
48            <groupId>org.projectlombok</groupId>
49            <artifactId>lombok</artifactId>
50            <optional>true</optional>
51        </dependency>
52        <dependency>
53            <groupId>org.springframework.boot</groupId>
54            <artifactId>spring-boot-starter-test</artifactId>
55            <scope>test</scope>
56        </dependency>
57        <dependency>
58            <groupId>org.springframework.security</groupId>
59            <artifactId>spring-security-test</artifactId>
60            <scope>test</scope>
61        </dependency>
62    </dependencies>
63    <dependencyManagement>
64        <dependencies>
65            <dependency>
66                <groupId>org.keycloak.bom</groupId>
67                <artifactId>keycloak-adapter-bom</artifactId>
68                <version>${keycloak.version}</version>
69                <type>pom</type>
70                <scope>import</scope>
71            </dependency>
72        </dependencies>
73    </dependencyManagement>
74    <build>
75        <plugins>
76            <plugin>
77                <groupId>pl.project13.maven</groupId>
78                <artifactId>git-commit-id-plugin</artifactId>
79            </plugin>
80            <plugin>
81                <groupId>org.springframework.boot</groupId>
82                <artifactId>spring-boot-maven-plugin</artifactId>
83                <executions>
84                    <execution>
85                        <goals>
86                            <goal>build-info</goal>
87                        </goals>
88                    </execution>
89                </executions>
90            </plugin>
91        </plugins>
92    </build>
93</project>

The Todo-Service is pretty simplistic and only shows the Spring Boot Admin Client configuration as well as the required actuator and Keycloak setup.
Our main class is the TodoServiceApplication which contains an embedded TodoController for the sake of brevity – Josh Long style FWT.

1package demo.todo;
2 
3import java.util.Arrays;
4 
5import org.springframework.boot.SpringApplication;
6import org.springframework.boot.autoconfigure.SpringBootApplication;
7import org.springframework.scheduling.annotation.EnableScheduling;
8import org.springframework.scheduling.annotation.Scheduled;
9import org.springframework.web.bind.annotation.GetMapping;
10import org.springframework.web.bind.annotation.RestController;
11 
12import lombok.extern.slf4j.Slf4j;
13 
14@Slf4j
15@EnableScheduling
16@SpringBootApplication
17public class TodoServiceApplication {
18 
19    public static void main(String[] args) {
20        SpringApplication.run(TodoServiceApplication.class, args);
21    }
22 
23    @Scheduled(fixedRate = 5_000)
24    public void doSomework() {
25 
26        // useful to demonstrate log dynamic level configuration
27        log.info("work info");
28        log.debug("work debug");
29        log.trace("work trace");
30        log.error("work error");
31    }
32}
33 
34@RestController
35class TodoController {
36 
37    @GetMapping("/")
38    Object getTodos() {
39        return Arrays.asList("Prepare talk...");
40    }
41}

The Keycloak configuration for the Todo-Service is denoted by the class KeycloakConfig:

1package demo.todo.keycloak;
2 
3import java.security.Principal;
4 
5import org.keycloak.KeycloakPrincipal;
6import org.keycloak.KeycloakSecurityContext;
7import org.keycloak.adapters.KeycloakConfigResolver;
8import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
9import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
10import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
11import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
12import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
13import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
14import org.springframework.beans.factory.annotation.Autowired;
15import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
16import org.springframework.boot.actuate.health.HealthEndpoint;
17import org.springframework.boot.actuate.info.InfoEndpoint;
18import org.springframework.boot.context.properties.EnableConfigurationProperties;
19import org.springframework.context.annotation.Bean;
20import org.springframework.context.annotation.Scope;
21import org.springframework.context.annotation.ScopedProxyMode;
22import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
23import org.springframework.security.config.annotation.web.builders.HttpSecurity;
24import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
25import org.springframework.security.core.session.SessionRegistry;
26import org.springframework.security.core.session.SessionRegistryImpl;
27import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
28import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
29import org.springframework.web.context.WebApplicationContext;
30import org.springframework.web.context.request.RequestContextHolder;
31import org.springframework.web.context.request.ServletRequestAttributes;
32 
33@KeycloakConfiguration
34@EnableConfigurationProperties(KeycloakSpringBootProperties.class)
35class KeycloakConfig extends KeycloakWebSecurityConfigurerAdapter {
36 
37    @Override
38    protected void configure(HttpSecurity http) throws Exception {
39        super.configure(http);
40 
41        http //
42                .csrf().disable() //
43                .authorizeRequests() //
44                .requestMatchers(EndpointRequest.to( //
45                        InfoEndpoint.class, //
46                        HealthEndpoint.class //
47                )).permitAll() //
48 
49                .requestMatchers(EndpointRequest.toAnyEndpoint()) //
50                .hasRole("ACTUATOR") //
51 
52                .anyRequest().permitAll() //
53        ;
54    }
55 
56    /**
57     * Load Keycloak configuration from application.properties or application.yml
58     *
59     * @return
60     */
61    @Bean
62    public KeycloakConfigResolver keycloakConfigResolver() {
63        return new KeycloakSpringBootConfigResolver();
64    }
65 
66    /**
67     * Use {@link KeycloakAuthenticationProvider}
68     *
69     * @param auth
70     * @throws Exception
71     */
72    @Autowired
73    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
74 
75        SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
76        grantedAuthorityMapper.setPrefix("ROLE_");
77        grantedAuthorityMapper.setConvertToUpperCase(true);
78 
79        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
80        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
81        auth.authenticationProvider(keycloakAuthenticationProvider);
82    }
83 
84    @Bean
85    @Override
86    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
87        return new RegisterSessionAuthenticationStrategy(buildSessionRegistry());
88    }
89 
90    @Bean
91    protected SessionRegistry buildSessionRegistry() {
92        return new SessionRegistryImpl();
93    }
94 
95    /**
96     * Allows to inject requests scoped wrapper for {@link KeycloakSecurityContext}.
97     *
98     * Returns the {@link KeycloakSecurityContext} from the Spring
99     * {@link ServletRequestAttributes}'s {@link Principal}.
100     * <p>
101     * The principal must support retrieval of the KeycloakSecurityContext, so at
102     * this point, only {@link KeycloakPrincipal} values and
103     * {@link KeycloakAuthenticationToken} are supported.
104     *
105     * @return the current <code>KeycloakSecurityContext</code>
106     */
107    @Bean
108    @Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
109    public KeycloakSecurityContext provideKeycloakSecurityContext() {
110 
111        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
112        Principal principal = attributes.getRequest().getUserPrincipal();
113        if (principal == null) {
114            return null;
115        }
116 
117        if (principal instanceof KeycloakAuthenticationToken) {
118            principal = Principal.class.cast(KeycloakAuthenticationToken.class.cast(principal).getPrincipal());
119        }
120 
121        if (principal instanceof KeycloakPrincipal) {
122            return KeycloakPrincipal.class.cast(principal).getKeycloakSecurityContext();
123        }
124 
125        return null;
126    }
127}

The application configuration for the Todo-Service is contained in application.yml

spring:
  main:
    allow-bean-definition-overriding: true

server:
  port: 30002

keycloak:
  realm: bootadmin
  auth-server-url: http://localhost:8080/auth
  resource: app-todo
  credentials:
     secret: 2cc653a3-24cc-4241-896d-813a726f9b33
  ssl-required: external
  principal-attribute: preferred_username
  autodetect-bearer-only: true
  use-resource-role-mappings: true
  token-minimum-time-to-live: 30

management:
  endpoints:
    web:
      exposure:
        include: '*'

Our Todo-Service application is now ready for service. We’ll now move on to the last path, the Admin-Service.

Admin-Service

The Admin-Service is denoted by the app-admin Keycloak client and hosts the Spring Boot Admin infrastructure. It uses a Keycloak service account to access the actuator endpoints of monitored applications. The app also exposes the Spring Boot Admin UI which is protected by Keycloak as well.
Only users with the role admin for the app-admin client will be able to login to the admin UI.

The Maven module configuration of Admin-Service looks like this:

1<?xml version="1.0" encoding="UTF-8"?>
2<project xmlns="http://maven.apache.org/POM/4.0.0"
3    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5    <modelVersion>4.0.0</modelVersion>
6    <groupId>com.example</groupId>
7    <artifactId>admin-service</artifactId>
8    <version>0.1.0-SNAPSHOT</version>
9    <packaging>jar</packaging>
10    <name>admin-service</name>
11    <description>Demo project for Spring Boot</description>
12    <parent>
13        <groupId>org.springframework.boot</groupId>
14        <artifactId>spring-boot-starter-parent</artifactId>
15        <version>2.1.2.RELEASE</version>
16        <relativePath /> <!-- lookup parent from repository -->
17    </parent>
18    <properties>
19        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
20        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
21        <java.version>1.8</java.version>
22        <keycloak.version>4.8.3.Final</keycloak.version>
23        <spring-boot-admin.version>2.1.2</spring-boot-admin.version>
24        <resteasy.version>3.6.1.Final</resteasy.version>
25        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
26    </properties>
27    <dependencies>
28        <dependency>
29            <groupId>org.springframework.boot</groupId>
30            <artifactId>spring-boot-starter-security</artifactId>
31        </dependency>
32        <dependency>
33            <groupId>org.springframework.boot</groupId>
34            <artifactId>spring-boot-starter-web</artifactId>
35        </dependency>
36        <dependency>
37            <groupId>de.codecentric</groupId>
38            <artifactId>spring-boot-admin-starter-server</artifactId>
39            <version>${spring-boot-admin.version}</version>
40        </dependency>
41        <dependency>
42            <groupId>org.springframework.cloud</groupId>
43            <artifactId>spring-cloud-starter</artifactId>
44        </dependency>
45        <dependency>
46            <groupId>org.jboss.resteasy</groupId>
47            <artifactId>resteasy-client</artifactId>
48            <version>${resteasy.version}</version>
49        </dependency>
50        <dependency>
51            <groupId>org.jboss.resteasy</groupId>
52            <artifactId>resteasy-jackson2-provider</artifactId>
53            <version>${resteasy.version}</version>
54        </dependency>
55        <dependency>
56            <groupId>org.keycloak</groupId>
57            <artifactId>keycloak-spring-boot-starter</artifactId>
58        </dependency>
59        <dependency>
60            <groupId>org.keycloak</groupId>
61            <artifactId>keycloak-admin-client</artifactId>
62            <version>${keycloak.version}</version>
63        </dependency>
64        <dependency>
65            <groupId>org.springframework.boot</groupId>
66            <artifactId>spring-boot-starter-test</artifactId>
67            <scope>test</scope>
68        </dependency>
69        <dependency>
70            <groupId>org.springframework.security</groupId>
71            <artifactId>spring-security-test</artifactId>
72            <scope>test</scope>
73        </dependency>
74    </dependencies>
75    <dependencyManagement>
76        <dependencies>
77            <dependency>
78                <groupId>org.keycloak.bom</groupId>
79                <artifactId>keycloak-adapter-bom</artifactId>
80                <version>${keycloak.version}</version>
81                <type>pom</type>
82                <scope>import</scope>
83            </dependency>
84            <dependency>
85                <groupId>org.springframework.cloud</groupId>
86                <artifactId>spring-cloud-dependencies</artifactId>
87                <version>${spring-cloud.version}</version>
88                <type>pom</type>
89                <scope>import</scope>
90            </dependency>
91        </dependencies>
92    </dependencyManagement>
93    <build>
94        <plugins>
95            <plugin>
96                <groupId>org.springframework.boot</groupId>
97                <artifactId>spring-boot-maven-plugin</artifactId>
98            </plugin>
99        </plugins>
100    </build>
101</project>

The main class of the Admin-Service is straightforward:

1package demo.admin;
2 
3import de.codecentric.boot.admin.server.config.EnableAdminServer;
4import org.springframework.boot.SpringApplication;
5import org.springframework.boot.autoconfigure.SpringBootApplication;
6 
7@EnableAdminServer
8@SpringBootApplication
9public class AdminServiceApplication {
10 
11    public static void main(String[] args) {
12        SpringApplication.run(AdminServiceApplication.class, args);
13    }
14}

The Keycloak configuration is more advanced though:

1package demo.admin.keycloak;
2 
3import java.security.Principal;
4 
5import org.keycloak.KeycloakPrincipal;
6import org.keycloak.KeycloakSecurityContext;
7import org.keycloak.OAuth2Constants;
8import org.keycloak.adapters.KeycloakConfigResolver;
9import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
10import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
11import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
12import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
13import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
14import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
15import org.keycloak.admin.client.Keycloak;
16import org.keycloak.admin.client.KeycloakBuilder;
17import org.springframework.beans.factory.annotation.Autowired;
18import org.springframework.boot.context.properties.EnableConfigurationProperties;
19import org.springframework.context.annotation.Bean;
20import org.springframework.context.annotation.Scope;
21import org.springframework.context.annotation.ScopedProxyMode;
22import org.springframework.http.HttpHeaders;
23import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
24import org.springframework.security.config.annotation.web.builders.HttpSecurity;
25import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
26import org.springframework.security.core.session.SessionRegistry;
27import org.springframework.security.core.session.SessionRegistryImpl;
28import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
29import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
30import org.springframework.web.context.WebApplicationContext;
31import org.springframework.web.context.request.RequestContextHolder;
32import org.springframework.web.context.request.ServletRequestAttributes;
33 
34import de.codecentric.boot.admin.server.web.client.HttpHeadersProvider;
35 
36@KeycloakConfiguration
37@EnableConfigurationProperties(KeycloakSpringBootProperties.class)
38class KeycloakConfig extends KeycloakWebSecurityConfigurerAdapter {
39 
40    /**
41     * {@link HttpHeadersProvider} used to populate the {@link HttpHeaders} for
42     * accessing the state of the disovered clients.
43     *
44     * @param keycloak
45     * @return
46     */
47    @Bean
48    public HttpHeadersProvider keycloakBearerAuthHeaderProvider(Keycloak keycloak) {
49        return (app) -> {
50            String accessToken = keycloak.tokenManager().getAccessTokenString();
51            HttpHeaders headers = new HttpHeaders();
52            headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
53            return headers;
54        };
55    }
56 
57    /**
58     * The Keycloak Admin client that provides the service-account Access-Token
59     *
60     * @param props
61     * @return
62     */
63    @Bean
64    public Keycloak keycloak(KeycloakSpringBootProperties props) {
65        return KeycloakBuilder.builder() //
66                .serverUrl(props.getAuthServerUrl()) //
67                .realm(props.getRealm()) //
68                .grantType(OAuth2Constants.CLIENT_CREDENTIALS) //
69                .clientId(props.getResource()) //
70                .clientSecret((String) props.getCredentials().get("secret")) //
71                .build();
72    }
73 
74    @Override
75    protected void configure(HttpSecurity http) throws Exception {
76        super.configure(http);
77 
78        http //
79                .csrf().disable() // for the sake of brevity...
80                .authorizeRequests() //
81                .antMatchers("/**/*.css", "/admin/img/**", "/admin/third-party/**").permitAll() //
82                .antMatchers("/admin").hasRole("ADMIN") //
83                .anyRequest().permitAll() //
84        ;
85    }
86 
87    /**
88     * Load Keycloak configuration from application.properties or application.yml
89     *
90     * @return
91     */
92    @Bean
93    public KeycloakConfigResolver keycloakConfigResolver() {
94        return new KeycloakSpringBootConfigResolver();
95    }
96 
97    /**
98     * Use {@link KeycloakAuthenticationProvider}
99     *
100     * @param auth
101     * @throws Exception
102     */
103    @Autowired
104    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
105 
106        SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
107        grantedAuthorityMapper.setPrefix("ROLE_");
108        grantedAuthorityMapper.setConvertToUpperCase(true);
109 
110        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
111        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
112        auth.authenticationProvider(keycloakAuthenticationProvider);
113    }
114 
115    @Bean
116    @Override
117    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
118        return new RegisterSessionAuthenticationStrategy(buildSessionRegistry());
119    }
120 
121    @Bean
122    protected SessionRegistry buildSessionRegistry() {
123        return new SessionRegistryImpl();
124    }
125 
126    /**
127     * Allows to inject requests scoped wrapper for {@link KeycloakSecurityContext}.
128     * <p>
129     * Returns the {@link KeycloakSecurityContext} from the Spring
130     * {@link ServletRequestAttributes}'s {@link Principal}.
131     * <p>
132     * The principal must support retrieval of the KeycloakSecurityContext, so at
133     * this point, only {@link KeycloakPrincipal} values and
134     * {@link KeycloakAuthenticationToken} are supported.
135     *
136     * @return the current <code>KeycloakSecurityContext</code>
137     */
138    @Bean
139    @Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
140    public KeycloakSecurityContext provideKeycloakSecurityContext() {
141 
142        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
143        Principal principal = attributes.getRequest().getUserPrincipal();
144        if (principal == null) {
145            return null;
146        }
147 
148        if (principal instanceof KeycloakAuthenticationToken) {
149            principal = Principal.class.cast(KeycloakAuthenticationToken.class.cast(principal).getPrincipal());
150        }
151 
152        if (principal instanceof KeycloakPrincipal) {
153            return KeycloakPrincipal.class.cast(principal).getKeycloakSecurityContext();
154        }
155 
156        return null;
157    }
158}

Note that we defined a dedicated Keycloak bean, which is used by the HttpHeadersProvider keycloakBearerAuthHeaderProvider bean to transparently retrieve (and renew) an OAuth2 Access-Token for the app-admin service account. All requests towards actuator endpoints of monitored applications will use this token.

In order to support a proper logout functionality, we’ll have to set up a dedicated /admin/logout endpoint.

1package demo.admin.keycloak;
2 
3import javax.servlet.http.HttpServletRequest;
4 
5import org.springframework.stereotype.Controller;
6import org.springframework.web.bind.annotation.PostMapping;
7 
8@Controller
9class KeycloakController {
10 
11    /**
12     * Propagates the logout to the Keycloak infrastructure
13     * @param request
14     * @return
15     * @throws Exception
16     */
17    @PostMapping("/admin/logout")
18    public String logout(HttpServletRequest request) throws Exception {
19        request.logout();
20        return "redirect:/admin";
21    }
22}

The spring configuration file application.yml for the Admin-Service looks like this:


server:
  port: 30001

spring:
  main:
    allow-bean-definition-overriding: true
  boot:
    admin:
      context-path: /admin
  cloud:
    discovery:
      client:
        simple:
          instances:
            app-todo:
              - uri: http://localhost:30002

keycloak:
  realm: bootadmin
  auth-server-url: http://localhost:8080/auth
  resource: app-admin
  credentials:
     secret: 97edad04-49ca-4770-8e4a-3bc97c1714ce
  ssl-required: external
  principal-attribute: preferred_username
  use-resource-role-mappings: true
  token-minimum-time-to-live: 30

Et voilà, we now have a setup that is fully secured via Keycloak 🙂

share post

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.