1. Overview

In this tutorial, we’ll discuss how to implement SSO – Single Sign On – using Spring Security OAuth and Spring Boot, using Keycloak as the Authorization Server.

We’ll use 4 separate applications:

  • An Authorization Server – which is the central authentication mechanism
  • A Resource Server – the provider of Foos
  • Two Client Applications – the applications using SSO

Very simply put, when a user tries to access a resource via one Client app, they’ll be redirected to authenticate first, through the Authorization Server. Keycloak will sign the user in, and while still being logged in the first app, if the second Client app is accessed using the same browser, the user will not need to enter their credentials again.

We’re going to use the Authorization Code grant type out of OAuth2 to drive the delegation of authentication.

We’ll use the OAuth stack in Spring Security 5. If you want to use the Spring Security OAuth legacy stack, have a look at this previous article: Simple Single Sign-On with Spring Security OAuth2 (legacy stack)

As per the migration guide:

Spring Security refers to this feature as OAuth 2.0 Login while Spring Security OAuth refers to it as SSO

Alright, let’s jump right in.

2. The Authorization Server

Previously, the Spring Security OAuth stack offered the possibility of setting up an Authorization Server as a Spring Application.

However, the OAuth stack has been deprecated by Spring and now we’ll be using Keycloak as our Authorization Server.

So this time, we’ll set up our Authorization Server as an embedded Keycloak server in a Spring Boot app.

In our pre-configuration, we’ll define two clients, ssoClient-1 and ssoClient-2, one for each Client Application.

3. The Resource Server

Next, we need a Resource Server, or the REST API which will provide us the Foos our Client App will consume.

It’s essentially the same as we used for our Angular Client Apps previously.

4. The Client Applications

Now let’s look at our Thymeleaf Client Application; we’ll, of course, use Spring Boot to minimize the configuration.

Do keep in mind that we’ll need to have 2 of these to demonstrate Single Sign-On functionality.

4.1. Maven Dependencies

First, we will need the following dependencies in our pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
    <groupId>io.projectreactor.netty</groupId>
    <artifactId>reactor-netty</artifactId>
</dependency>

To include all the client support we’ll require, including security, we just need to add spring-boot-starter-oauth2-client. Also, since the old RestTemplate is going to be deprecated, we’re going to use WebClient, and that’s why we added spring-webflux and reactor-netty.

4.2. Security Configuration

Next, the most important part, the security configuration of our first client application:

@EnableWebSecurity
public class UiSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/", "/login**")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .oauth2Login();
        return http.build();
    }

    @Bean
    WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, 
      OAuth2AuthorizedClientRepository authorizedClientRepository) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = 
          new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, 
          authorizedClientRepository);
        oauth2.setDefaultOAuth2AuthorizedClient(true);
        return WebClient.builder()
            .apply(oauth2.oauth2Configuration())
            .build();
    }

}

The core part of this configuration is the oauth2Login() method, which is used to enable Spring Security’s OAuth 2.0 Login support. Since we’re using Keycloak, which is by default a single sign-on solution for web apps and RESTful web services, we do not need to add any further configuration for SSO.

Finally, we also defined a WebClient bean to act as a simple HTTP Client to handle requests to be sent to our Resource Server.

And here’s the application.yml:

spring:
  security:
    oauth2:
      client:
        registration:
          custom:
            client-id: ssoClient-1
            client-secret: ssoClientSecret-1
            scope: read,write,openid
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8082/ui-one/login/oauth2/code/custom
        provider:
          custom:
            authorization-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/auth
            token-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/token
            user-info-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/userinfo
            jwk-set-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/certs
            user-name-attribute: preferred_username
  thymeleaf:
    cache: false
    
server: 
  port: 8082
  servlet: 
    context-path: /ui-one

resourceserver:
  api:
    project:
      url: http://localhost:8081/sso-resource-server/api/foos/        

Here, spring.security.oauth2.client.registration is the root namespace for registering a client. We defined a client with registration id custom. Then we defined its client-id, client-secret, scope, authorization-grant-type and redirect-uri, which of course, should be the same as that defined for our Authorization Server.

After that, we defined our service provider or the Authorization Server, again with the same id of custom, and listed down its different URI’s for Spring Security to use. That’s all we need to define, and the framework does the entire logging-in process, including redirection to Keycloak, seamlessly for us.

Also note that, in our example here, we rolled out our Authorization Server, but of course we can also use other, third-party providers such as Facebook or GitHub.

4.3. The Controller

Let’s now implement our controller in the Client App to ask for Foos from our Resource Server:

@Controller
public class FooClientController {

    @Value("${resourceserver.api.url}")
    private String fooApiUrl;

    @Autowired
    private WebClient webClient;

    @GetMapping("/foos")
    public String getFoos(Model model) {
        List<FooModel> foos = this.webClient.get()
            .uri(fooApiUrl)
            .retrieve()
            .bodyToMono(new ParameterizedTypeReference<List<FooModel>>() {
            })
            .block();
        model.addAttribute("foos", foos);
        return "foos";
    }
}

As we can see, we have only one method here that’ll dish out the resources to the foos template. We did not have to add any code for login.

4.4. Front End

Now, let’s take a look at the front-end configuration of our client application. We’re not going to focus on that here, mainly because we already covered in on the site.

Our client application here has a very simple front-end; here’s the index.html:

<a class="navbar-brand" th:href="@{/foos/}">Spring OAuth Client Thymeleaf - 1</a>
<label>Welcome !</label> <br /> <a th:href="@{/foos/}">Login</a>

And the foos.html:

<a class="navbar-brand" th:href="@{/foos/}">Spring OAuth Client Thymeleaf -1</a>
Hi, <span sec:authentication="name">preferred_username</span>   
    
<h1>All Foos:</h1>
<table>
  <thead>
    <tr>
      <td>ID</td>
      <td>Name</td>                    
    </tr>
  </thead>
  <tbody>
    <tr th:if="${foos.empty}">
      <td colspan="4">No foos</td>
    </tr>
    <tr th:each="foo : ${foos}">
      <td><span th:text="${foo.id}"> ID </span></td>
      <td><span th:text="${foo.name}"> Name </span></td>                    
    </tr>
  </tbody>
</table>

The foos.html page needs the users to be authenticated. If a non-authenticated user tries to access foos.html, they’ll be redirected to Keycloak’s login page first.

4.5. The Second Client Application

We’ll configure a second application, Spring OAuth Client Thymeleaf -2 using another client_id ssoClient-2.

It’ll mostly be the same as the first application we just described.

The application.yml will differ to include a different client_id, client_secret and redirect_uri in its spring.security.oauth2.client.registration:

spring:
  security:
    oauth2:
      client:
        registration:
          custom:
            client-id: ssoClient-2
            client-secret: ssoClientSecret-2
            scope: read,write,openid
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8084/ui-two/login/oauth2/code/custom

And, of course, we need to have a different server port for it as well, so that we can run them in parallel:

server: 
  port: 8084
  servlet: 
    context-path: /ui-two

Finally, we’ll tweak the front end HTMLs to have a title as Spring OAuth Client Thymeleaf – 2 instead of – 1 so that we can distinguish between the two.

5. Testing SSO Behavior

To test SSO behavior, let’s run our Applications.

We’ll need all our 4 Boot Apps – the Authorization Server, the Resource Server and both Client Applications – to be up and running for this.

Now let’s open up a browser, say Chrome, and log in to Client-1 using the credentials [email protected]/123. Next, in another window or tab, hit the URL for Client-2. On clicking the login button, we’ll be redirected to the Foos page straightaway, bypassing the authentication step.

Similarly, if the user logs in to Client-2 first, they need not enter their username/password for Client-1.

6. Conclusion

In this tutorial, we focused on implementing Single Sign-On using Spring Security OAuth2 and Spring Boot using Keycloak as the identity provider.

As always, the full source code can be found over on GitHub.


» 下一篇: Structurizr简介