2

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?

Peter Penzov
  • 1,126
  • 134
  • 430
  • 808
  • 1
    No, the answer you link is not "for older versions of Spring Security". I suspect you have little knowledge of OAuth2 in general and of it's Spring implementations in particular. Maybe should you start with [my tutorials](https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials)? – ch4mp May 22 '23 at 17:46
  • 1
    Does this answer your question? [Use Keycloak Spring Adapter with Spring Boot 3](https://stackoverflow.com/questions/74571191/use-keycloak-spring-adapter-with-spring-boot-3) – ch4mp May 22 '23 at 20:04

3 Answers3

3

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.

Ritik Sharma
  • 379
  • 6
  • I implemented the code but I get just `401 Unauthorized`. How I can redirect the user to keycloak's login page when user is not authenticated? – Peter Penzov May 27 '23 at 23:51
  • 1
    You really need to read [the tutorials](https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials) I linked a few times already. You won't get redirected to login with a resource server configuration. Resource server is not responsible for fetching tokens, it is responsible for validating it and performing access control (login is clients business). Plus, read the answers with more attention, the author of this one is very explicit on what you should and should not use to send a request to a resource server. – ch4mp May 28 '23 at 02:49
  • 1
    For resource server it will not be redirected to login page. Redirection flow only works with browser when Oauth2 client server is used and for Oauth2 resource server you need to make a rest call to your api with request header **Authorization** (JWT access token) which can't be achieved with browser. Its well phrased in link provided by @ch4mp. `resource-server: an API (most frequently REST). It should not care about login, logout or any OAuth2 flow. From its point of view, all that matters is if a request is authorized with a valid access token and taking access decisions based on it.` – Ritik Sharma May 28 '23 at 10:38
  • ok, I checked the tutorial. Can you show me what configuration I need to do for `Authorization-Code` flow? Is it possible to use Keycloak's default login page for login and also password reset? Is it mandatory to add Spring gateway? – Peter Penzov May 29 '23 at 00:50
  • In postman select Authorization option and go to Bearer then paste the access token in the input box from keycloak. Watch this to see how to add your access token in postman. https://learning.postman.com/docs/sending-requests/authorization/ or https://egghead.io/lessons/http-add-a-custom-bearer-token-in-postman-to-authorize-an-api-request. Also please mark this answer as accepted answer. – Ritik Sharma May 29 '23 at 14:31
  • I tested your code and it's working. I need also to implemented the code using keycloak's login page because I want to use the keycloak's reset password page. Can you guide me what I need to implement in order to have this functionality? – Peter Penzov May 29 '23 at 22:27
  • From where you want user to redirected to login page? Is there are any UI application for your REST APIs? Like Angular/React? – Ritik Sharma May 30 '23 at 09:31
  • I want when I open any page like `home` page users to be redirected to keycloak's login page if the user is not authenticated. – Peter Penzov May 30 '23 at 09:45
  • All UI applications will be React based. BE will not serve web content - only Rest API – Peter Penzov May 30 '23 at 09:46
  • For securing front end applications (React.js), you can configure Keycloak's javascript adapter as mentioned in the docs. https://www.keycloak.org/docs/latest/securing_apps/#_javascript_adapter After successful login you have to pass access token from keycloak to HTTP calls made to your BE rest APIs in the Authorization header. You can refer to this article to get the idea but little outdate version https://blog.logrocket.com/implement-keycloak-authentication-react/ – Ritik Sharma May 30 '23 at 14:15
  • Any option to implement it without it because later on it will easy to migrate to OKTA for example? – Peter Penzov May 30 '23 at 14:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/253883/discussion-between-ritik-sharma-and-peter-penzov). – Ritik Sharma May 30 '23 at 14:24
1

We recently migrated our application to use Spring Boot 3.0.4 and Keycloak 21.0.1 The configuration look like below,

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

YAML config:

spring.security.oauth2.resourceserver.jwt.issuer-uri=https://<keycloak-base-url>/realms/<realms-name>
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://<keycloak-base-url>/realms/<realms-name>/protocol/openid-connect/certs

Spring Security configuration is:

@Configuration
public class WebSecurityConfig {
    
    @Autowired
    private CorsConfigurationSource corsConfigurationSource;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
            .headers()
                .frameOptions().disable();
        httpSecurity
            .authorizeHttpRequests()
                .anyRequest().authenticated()
                .and()
            .oauth2ResourceServer().jwt();
        httpSecurity
            .cors()
               .configurationSource(corsConfigurationSource)
               .and()
            .csrf().disable();
        return httpSecurity.build();
    }
}
Abhishek
  • 156
  • 1
  • 10
  • This only covers resource server case (REST API secured with access token). At some point you'll probably need to add authorities (Keycloak "roles") mapping. Also, on a resource server, you'd probably want to ensure that 401 is returned when Authorization is missing or invalid. Last, when disabling CSRF protection, you'd probably ensure that sessions are disabled too (STATELESS session management). To make it short, [this other answer](https://stackoverflow.com/a/74572732/619830) is much more complete. – ch4mp May 22 '23 at 19:49
  • 1. The answer linked contains options with and without spring-addons (and so do the tutorials hosted on spring-addons Github repo). 2. Maybe should you have looked in details what spring-addons Boot starters are (it is open-source and not that big) before deciding not to use it: it would probably have saved you quite some misconfigurations (as those listed in my previous comment) – ch4mp May 23 '23 at 06:47
  • yes, your right, non of your options helped us so we refered below url, https://www.baeldung.com/spring-security-oauth-resource-server#:~:text=In%20the%20context%20of%20OAuth,a%20resource%20to%20the%20client. – Abhishek May 23 '23 at 06:59
  • I am puzzled when I read your arguments and the article linked: this a downgraded version of the configuration I provide (the one without "my" starters) and what is there for tests is mostly a joke: fetching actual tokens from an actual authorization-server during unit test is just not acceptabke (way too much coupling, way too slow and way too unstable) – ch4mp May 23 '23 at 13:11
  • @peter-penzov if you had followed the links until you reached the Github repo hosting my starters and [tutorials](https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials), you'd have found complete samples with imports and unit tests... – ch4mp May 23 '23 at 13:16
  • @PeterPenzov Please go through https://www.baeldung.com/spring-security-oauth-resource-server and I have provided code for WebSecurityConfig in my answer. you can use latest version with example proivded in above url. – Abhishek May 23 '23 at 13:21
  • it work with latest version as well. we used it with spring boot 3.0.4 and Keycloak 21.0.1. – Abhishek May 23 '23 at 13:25
1

In case someone comes here looking for what an implementation in version 6.1 of Spring Security, Spring Boot 3.1 configured to work with keycloack would look like.

If you get an error about JwtDecoder, this line takes care of it: .jwkSetUri(jwkSetUri)

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity(debug = true)
public class WebSecurityConfig {

    public static final String ADMIN = "admin";
    public static final String USER = "user";


    @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
    private String jwkSetUri;

    private final JwtAuthConverter jwtAuthConverter;


    @Bean
    public SecurityFilterChain securityFilterChain2(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((authorizeHttpRequests) ->
                        authorizeHttpRequests
                                .requestMatchers("/api/duenos/**").hasAnyRole(ADMIN, USER)
                                .requestMatchers("/api/objetos_extraviados/**").hasAnyRole(ADMIN, USER)
                                .anyRequest().authenticated()
                )
                .oauth2ResourceServer((oauth2ResourceServer) ->
                        oauth2ResourceServer
                                .jwt((jwt) ->
                                        jwt
                                                .jwtAuthenticationConverter(jwtAuthConverter)
                                                .jwkSetUri(jwkSetUri)
                                )
                )
                .sessionManagement((sessionManagement) -> sessionManagement
                                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                );
        return http.build();
    }

}

Las dependencias que usé: spring-boot-starter-oauth2-resource-server spring-security-oauth2-jose spring-boot-starter-security

SebaGQ
  • 11
  • 2
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 22 '23 at 21:34