Faktor Zehn Tutorial: Keycloak + Spring Boot 2 + Spring Security 5

Tutorial Keycloak

Authentication is a crucial part of any enterprise application, especially single sign on. Keycloak provides an easy-to-use authorization server that enables authentication using the Open ID Connect, which is built on top of the OAuth 2 authorization framework.

In the last days, we tried to set up Keycloak in a Spring Boot 2 application with Spring Security 5. The learning and research process was surprisingly frustrating due to incomplete or incompatible tutorials. At the same time, the resulting solution was, not so surprisingly, extremely simple. With this tutorial, we hope to help other people like us who are not (yet) a Spring Security expert nor an authentication expert, but still want to get it work while also understanding WHY it works (or not).

 

Keycloak Setup

There are plenty of tutorials that go through the setup process of a Keycloak server, e.g. from Keycloak directly.

Hopefully, you already have a client for your application after having followed a tutorial to setup Keycloak. You have to make sure that the following properties as configured correctly:

  • Client name is not empty. We are not sure if this is really required. If you have the extra time to try without, tell us the result.
  • Access Type is set to “confidential”. If it is set to “confidential” correctly, you should see the tab “Credentials” containing a field “secret”. If not, you can change it in the field “Access Type” in the tab “Settings” of the client configuration. You have to save the change and reload the page again to see the “Credentials” tab.

 

Dependencies and Versions

One of the sources of frustration along the way was the fact that most tutorials did not work with Spring Security 5. To be very clear, these are the dependencies/versions we used when testing this tutorial.

  • Spring Boot 2.1.7.RELEASE
  • (Spring Security 5.1.6.RELEASE)
  • Keycloak Server 6.0.1
  • No Keycloack Adapter for Spring 

The following dependencies are necessary:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

 

OAuth2 Support in Spring Security 5

The Spring Team has decided to re-write the whole Spring Security OAuth (which includes support for OAuth as well as OAuth2) and to include OAuth2 Support in Spring Security 5. More detailed information can be found here:
https://spring.io/blog/2018/01/30/next-generation-oauth-2-0-support-with-spring-security

The current version of Spring Security 5 is not featured complete in regards of OAuth2. A Feature Matrix can be found here:

https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Features-Matrix

Be careful about the version that is used in tutorials or documentations.

 

Login

Spring Security 5 supports OAuth 2.0 out of the box. There is one thing to be done: register the client using the Spring Boot 2.x OAuth Client properties.

Once you have defined those properties in your application.properties, OAuth2ClientAutoConfiguration will automatically create a ClientRegistration and put it into the ClientRegistrationRepository with the defined registration ID. So first, choose a registration ID. (We simply chose “keycloak”.) In the documentation of the properties, a “providerID” is also used. We are not sure where it should come from. In all the examples in the documentation, it is simply the same as the registration ID. Consequently, we also simply used the registrationID as providerID.

Now for the properties:

  • spring.security.oauth2.client.registration.[registrationId].client-id
    ID of the client. Can be found in the “Settings” tab in the client configuration in the Keycloak Admin Console.
  • spring.security.oauth2.client.registration.[registrationId].client-secret
    Can be found in the “Credentials” tab in the client configuration in the Keycloak Admin Console. (See Keycloak Setup)
  • spring.security.oauth2.client.registration.[registrationId].client-name
    Can be found in the “Settings” tab in the client configuration in the Keycloak Admin Console.
  • spring.security.oauth2.client.registration.[registrationId].authorization-grant-type
    Use authorization_code. You can read more about it in the OAuth 2.0 specification of “Authorization Grant”.
  • spring.security.oauth2.client.registration.[registrationId].redirectUriTemplate
    Use {baseUrl}/login/oauth2/code/{registrationId}
  • spring.security.oauth2.client.registration.[registrationId].scope[0]
    Use openid. No matter which additional scopes you want to use, the specification requires openid as a scope. More information on scopes can be found in the Keycloak documentation of  “Client Scopes Linking“.
  • spring.security.oauth2.client.provider.[providerId].authorization-uri
    Can be found in the realms setting, when clicking on the “OpenID Endpoint Configuration”
  • spring.security.oauth2.client.provider.[providerId].token-uri
    Can be found in the realms setting, when clicking on the “OpenID Endpoint Configuration”
  • spring.security.oauth2.client.provider.[providerId].user-info-uri
    Can be found in the realms setting, when clicking on the “OpenID Endpoint Configuration”
  • spring.security.oauth2.client.provider.[providerId].jwk-set-uri
    Can be found in the realms setting, when clicking on the “OpenID Endpoint Configuration”
  • spring.security.oauth2.client.provider.[providerId].user-name-attribute
    Use preferred_username. “preferred_username” is supposed to contain the user name in the user info endpoint specified by OpenID connect.  According to the specification, “preferred_username” is a part of the Standard Claim that the response CAN contain, so it is unclear to us if it is actually guaranteed to be returned.

That’s it for the login part. Start the application and you should be redirected to the login page of Keycloak.

As stated before, we are no Spring Security experts. However, Spring provides excellent reference documentation. We recommend reading the following sections for some basic understanding about both Spring Security and OAuth 2/Open ID Connect.

 

Vaadin / CSRF

If you are building a Vaadin application, that is not it. You need to disable CSRF to make it work, otherwise you would get 403 error after login. According to Vaadin, CSRF is handled by Vaadin directly, so we can safely disable the CRSF of Spring Security.

To achieve this, you need to create your own WebSecurityConfigurerAdapter that disables the CSRF, then to the stuff that is otherwise done by the default OAuth2WebSecurityConfigurerAdapter. In Code, this means:


@EnableWebSecurity

public class CSRFDisabledOAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {


  @Override

   protected void configure(HttpSecurity http) throws Exception {

    http.crsf().disabled().authorizeRequests().anyRequest().authenticated().and().oauth2Login().and().oauth2Client();

  }

}

Now that is really it. Log in and enjoy!

 

How it works

Spring Security uses a Filter Chain to handle different parts of the security. This chain is configured in the the WebSecurityConfigurerAdapter. With the Call “.oauth2Login()”, a OAuth2LoginAuthenticationFilter is added to the filter chain to handle login. This executes the first step of the Authorization Code Grant. As a Result, an authorization code is obtained. Next, “.oauth2Client()” is configured to add an OAuth2AuthorizationCodeGrantFilter to exchange an access token with this code.

If you do not provide your own WebSecurityConfigurerAdapter, OAuth2WebSecurityConfigurerAdapter is automatically used which adds these filters.

Logout

Keycloak defines a logout endpoint. You can find the URI in the KeyCloak administration console. It is listed as “end_session_endpoint” in the OpenID Endpoint Configuration under the realm settings. For the logout, we simply have to redirect to this endpoint.

With Spring Security, this can be easily done by using a custom LogoutHandler in the WebSecurityConfigurerAdapter. So we first define a KeyCloakLogoutHandler that handles the redirect.

Here, we retrieve the logout endpoint from the application configuration. It is necessary to define the endpoints there using the configured key. In this handler, we also redirect user back to the login page after a successful logout. Next to the redirect, we need to invalidate the HttpSession . This task is done by the superclass SecurityContextLogoutHandler and triggered by calling super.logout().

@Component
public class KeycloakLogoutHandler extends SecurityContextLogoutHandler {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(KeycloakLogoutHandler.class);
 
    @Value(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;${keycloak-client.logout-uri}&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;)
    private String keycloakLogoutUri;
 
    @Value(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;${keycloak-client.logout-redirect-param}&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;)
    private String keycloakLogoutRedirectParam;
 
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        if (authentication == null) {
            LOGGER.warn(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Cannot log out without authentication&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
            return;
        } else {
            try {
                String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
                        .replacePath(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;login&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;)
                        .replaceQuery(null)
                        .build()
                        .toUriString();
                String logoutUrl = UriComponentsBuilder.fromHttpUrl(keycloakLogoutUri)
                        .queryParam(keycloakLogoutRedirectParam, redirectUri)
                        .build()
                        .toUriString();
                response.sendRedirect(logoutUrl);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
 
        super.logout(request, response, authentication);
    }
}

Now, the last step is to extend the WebConfigurerAdapter.


@Autowired
private KeycloakLogoutHandler logoutHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.csrf().disable().authorizeRequests().anyRequest().authenticated()
          .and().oauth2Login()
          .and().oauth2Client()
          .and().logout().addLogoutHandler(logoutHandler); 
}
 

Navigating to /logout should now log out the user and redirect to the login page.

 

Reading Recommendations

A guide to the OAuth2 protocol:

https://milapneupane.com.np/2019/09/02/a-complete-guide-to-oauth2-protocol/

Examples of various OAuth2 Flows including the OpenID Connect Flow

https://www.oauth.com/playground/index.html

JWT Decoder:

https://jwt.io/

 

Outro Rant

At the end of the day, the extra mile did force us to learn a lot. However, the disappointment and frustration did not contribute to our overall happiness. If this tutorial is being a source of frustration for you because it does not work for you and you simply don’t understand why, please leave a comment below so we can add any information that we may have forgotten to write down.

 

 

Matthias Knappik

4 Comments

  1. NIkhil

    Hi,
    Could you please let me know why the Keycloack Adapter for Spring is not being used in implementation?

    Reply
    1. Matthias Knappik

      Hi NIkhil,
      in order to be future-proof our aim was to use the relatively new Spring Security 5 library, that supports oauth2/oidc. However, we have faced runtime errors using the Keycloak Adapter in combination with Spring Security 5, so we decided to use Spring Security’s own oauth2/oidc capabilities. As a side effect our application is not bound to the Keycloak implementation as oauth2/oidc server.

      Reply
  2. Erick

    Hello Friend! Congratulations on the tutorial, it’s the best I’ve seen so far. However, I’m having problems with the Authorities/Roles, when i log in it doesn’t bring it correctly, it just brings some that I don’t know, except the one I created in Keycloak. Does this happen to you too? Or does it bring the roles correctly?

    Reply
    1. Matthias Knappik

      Hi Erick,
      Thank you! I haven’t seen problems regarding the authorities/roles on our side. How do you access them or validate against them in your application? A good way to see exactly what comes from keycloak is to extract the access token string while debugging your application after log in and decode it on https://jwt.io/. After comparing what comes from Keycloak and what you see in your application you can at least narrow down the issue.

      Reply

Leave a Comment

Your email address will not be published. Required fields are marked *

Solve : *
9 + 3 =