Tuesday, January 24, 2023

Keycloak Spring Security Adapter for SpringBoot application with SpringSecurity

 Generally in live application we want our microservice or springboot application relam/method need to accessed based the perticular role i.e. we want some method to be specific access with role as manager and some method that can be accessed on if the user is have role as employee we can do this using keycloak Spring Security Adapter

Lets try to do the live example.

First we will configure our Keycloak server as shown below.

i am going to provide a redirect url here this is an endpoint of my application once authentication is successful the user will be redirected to this url i also provide an admin url this is usedby key clock to push revocation policies such as single logout normally this url is the base url of the client application this url is implicitly supported by key clock client adapters if you are not using an adapter you need to manually implement it but it is an optional feature.

Now copy the credential this will be used in our sprigboot application from here.

client id – spring-security-adaptor-client
Client secret – HSU2EJoTQHVOM80GJn3DxAPQc11TSTxh

now lets create a three role as shwon belwo.

1- ROLE_MANAGER
2- ROLE_EMPLOYEE
3- ROLE_USER

now lets assign few of this role to user

1- MANAGER user with ROLE_MANAGER
2- Employee user with ROLE_EMPLOYEE
2- USER user with ROLE_USER

lets create credential and assign above role created to them.

We are done with the keycloak configuration part now let move to the spring boot application.

Please follow the below step to create a spring boot application we are using STS.

Now lets add few more dependecies in our project

i.e.
spring-boot-starter-web,spring-boot-starter-security,keycloak-spring-boot-starter and keycloak-spring-security-adapter

our final pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?xml version="1.0" encoding="UTF-8"?>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.siddhu</groupId>
    <artifactId>siddhu-keycloak-sprintboot-adaptor</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>siddhu-springsecurity-keycloak-adaptor</name>
    <description>This example shows spring security with keycloak server</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-security-adapter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</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>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.keycloak.bom</groupId>
                <artifactId>keycloak-adapter-bom</artifactId>
                <version>7.0.1</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>

Note we are using spring 2.2.0.RELEASE and keycloakVersion 7.0.1 here.

lets create our controller class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.siddhu.keycloak;
import java.util.Date;
 
import javax.servlet.http.HttpServletRequest;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SimpleController {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleController.class);
     
    @GetMapping("/public")
    public String publicEndpoint(){
        return "Response from public endpoint";
    }
 
    @GetMapping("/private/manager")
    String manager(HttpServletRequest request){
 
        /*
         * KeycloakAuthenticationToken token = (KeycloakAuthenticationToken)
         * request.getUserPrincipal(); KeycloakPrincipal principal=(KeycloakPrincipal)
         * token.getPrincipal(); KeycloakSecurityContext session =
         * principal.getKeycloakSecurityContext(); AccessToken accessToken =
         * session.getToken();
         *
         * LOGGER.info("username: {}", accessToken.getPreferredUsername());
         * LOGGER.info("emailId: {}", accessToken.getEmail());
         * LOGGER.info("lastname: {}",accessToken.getFamilyName());
         * LOGGER.info("firstname: {}", accessToken.getGivenName());
         * LOGGER.info("realmName: {}", accessToken.getIssuer());
         *
         * return "manager";
         */
        return "Manager:" + (new Date()).toString();
    }
     
    @GetMapping("/private/user")
    String user(HttpServletRequest request){
 
        /*
         * KeycloakAuthenticationToken token = (KeycloakAuthenticationToken)
         * request.getUserPrincipal(); KeycloakPrincipal principal=(KeycloakPrincipal)
         * token.getPrincipal(); KeycloakSecurityContext session =
         * principal.getKeycloakSecurityContext(); AccessToken accessToken =
         * session.getToken();
         *
         * LOGGER.info("username: {}", accessToken.getPreferredUsername());
         * LOGGER.info("emailId: {}", accessToken.getEmail());
         * LOGGER.info("lastname: {}",accessToken.getFamilyName());
         * LOGGER.info("firstname: {}", accessToken.getGivenName());
         * LOGGER.info("realmName: {}", accessToken.getIssuer());
         *
         * return "user";
         */
        return "User:" + (new Date()).toString();
    }
 
     
    @GetMapping("/private/employee")
    String employee(HttpServletRequest request){
 
        /*
         * KeycloakAuthenticationToken token = (KeycloakAuthenticationToken)
         * request.getUserPrincipal(); KeycloakPrincipal principal=(KeycloakPrincipal)
         * token.getPrincipal(); KeycloakSecurityContext session =
         * principal.getKeycloakSecurityContext(); AccessToken accessToken =
         * session.getToken();
         *
         * LOGGER.info("username: {}", accessToken.getPreferredUsername());
         * LOGGER.info("emailId: {}", accessToken.getEmail());
         * LOGGER.info("lastname: {}",accessToken.getFamilyName());
         * LOGGER.info("firstname: {}", accessToken.getGivenName());
         * LOGGER.info("realmName: {}", accessToken.getIssuer());
         *
         * return "employee";
         */
        return "Employee:" + (new Date()).toString();
    }
 
 
 
}

one configuration class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.siddhu.keycloak;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
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.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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;
 
@KeycloakConfiguration
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
 
    /**
     * Registers the KeycloakAuthenticationProvider with the authentication manager.
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
 
        // adding proper authority mapper for prefixing role with "ROLE_"
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
 
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }
 
    /**
     * Provide a session authentication strategy bean which should be of type
     * RegisterSessionAuthenticationStrategy for public or confidential applications
     * and NullAuthenticatedSessionStrategy for bearer-only applications.
     */
    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }
 
    /**
     * Secure appropriate endpoints
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
                .antMatchers("/private/user").hasRole("USER") // only user with role user are allowed to access
                .antMatchers("/private/employee").hasRole("EMPLOYEE") // only employee with role employee are allowed to access
                .antMatchers("/private/manger").hasRole("MANAGER") // only manager with role manager are allowed to access
                .anyRequest().permitAll();
    }
}

our main class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.siddhu.keycloak;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SiddhuSpringsecurityKeycloakAdaptorApplication {
     
     /**
     * Use properties in application.yml instead of keycloak.json
     */
    @Bean
    public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }
 
    public static void main(String[] args) {
        SpringApplication.run(SiddhuSpringsecurityKeycloakAdaptorApplication.class, args);
    }
 
}

Lets now access the url

http://localhost:8081/private/user

Source Code :- https://github.com/shdhumale/siddhu-springsecurity-keycloak-adaptor.git