Search code examples
javaspringspring-bootspring-security

Configure Keycloak 21 with Spring Security 6


I'm trying to setup Spring Security to work with Keycloak 21 but unfortunately most of the tutorials on Internet are outdated. I configured client and realms into Keycloak but Spring security is not clear what should be. I tried the code from this link:

I added these gradle dependencies:

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client:3.1.0'
implementation 'org.springframework.boot:spring-boot-starter-security:3.1.0'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.1.0'

and this yml config:

spring:
  security:
    oauth2:
      client:
        provider:
          keycloak:
            issuer-uri: https://ip/realms/admin_console_realm
        registration:
          keycloak-login:
            authorization-grant-type: authorization_code
            client-name: My Keycloak instance
            client-id: admin_console_client
            client-secret: qwerty
            provider: keycloak
            scope: openid,profile,email,offline_access
      resourceserver:
        jwt:
          issuer-uri: https://ip/realms/admin_console_realm
          jwk-set-uri: https://ip/realms/admin_console_realm/protocol/openid-connect/certs

It's not clear what I need to add as a Spring security configuration here:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {

        httpSecurity.authorizeHttpRequests()
                .requestMatchers("/*").hasAuthority("ROLE_TECH_SUPPORT")
                .anyRequest().authenticated()
                .and()
                .oauth2Login();

        return  httpSecurity.build();
    }
}

I added the role into Keycloak client:

enter image description here

When I open a Rest API link into the browser I'm redirected to Keycloak's login page. After successful authentication I get:

Access to ip was deniedYou don't have authorization to view this page.
HTTP ERROR 403

Do you know how I can fix this issue?


Solution

  • if you are trying to expose rest APIs secured with Keycloak Server (Authentication Server) then your application is a OAuth2 resource server not a OAuth2 client server and also you can't use browser for accessing your rest APIs unless they are public. For Resource Server with rest APIs you can use the below code

    1. application.yml
    spring:
      security:
        oauth2:
          resourceserver:
            jwt:
              issuer-uri: http://<KEYCLOAK_SERVER_IP>/realms/<YOUR_REALM_NAME>
              jwk-set-uri: http://<KEYCLOAK_SERVER_IP>/realms/<YOUR_REALM_NAME>/protocol/openid-connect/certs
    
    1. Get JWT TOKEN from Keycloak Server (this curl is equivalent to postman request for keycloak to get access token, you can find more here https://documenter.getpostman.com/view/7294517/SzmfZHnd)
    curl --location 'http://<KEYCLOAK_SERVER_IP>/realms/test/protocol/openid-connect/token' \
    --header 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode 'username=<YOUR_USER_NAME>' \
    --data-urlencode 'password=<YOUR_USER_PASSWORD>' \
    --data-urlencode 'grant_type=password' \
    --data-urlencode 'client_id=<KEYCLOAK_CLIENT_ID>' \
    --data-urlencode 'client_secret=<KEYCLOAK_CLIENT_SECRET>' \
    --data-urlencode 'scope=openid'
    
    1. Inspect your access token using https://jwt.io/
    ...
    "realm_access": {
        "roles": [
          "default-roles-test",
          "offline_access",
          "MY_REALM_ROLE",
          "uma_authorization"
        ]
      },
      "resource_access": {
        "test-client": {
          "roles": [
            "MY_CLIENT_ROLE"
          ]
        },
    ...
    

    Now, we need to tell spring security from where it has look for Roles information in the JWT token.

    1. WebSecurityConfig.java
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig {
    
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    
    // Note: Please change '/mp-api/**' to your desired rest controller path.
            httpSecurity
                    .authorizeHttpRequests(registry -> registry
                            .requestMatchers("/my-api/**").hasRole("MY_REALM_ROLE")
                            .anyRequest().authenticated()
                    )
                    .oauth2ResourceServer(oauth2Configurer -> oauth2Configurer.jwt(jwtConfigurer -> jwtConfigurer.jwtAuthenticationConverter(jwt -> {
                        Map<String, Collection<String>> realmAccess = jwt.getClaim("realm_access");
                        Collection<String> roles = realmAccess.get("roles");
                        var grantedAuthorities = roles.stream()
                                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                                .collect(Collectors.toList());
                        return new JwtAuthenticationToken(jwt, grantedAuthorities);
                    })))
            ;
    
            return httpSecurity.build();
        }
    }
    

    Make sure you have proper Role Mappings for User in Keycloak.

    Now, for using your rest API you can use Postman. In Postman add Bearer Token Authorization and use the latest generated access token from keycloak server.