If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:
>> LEARN SPRING SECURITYMocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
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
Further reading:
Spring Security β OAuth2 Login
New in Spring Security OAuth2 - Verify Claims
A Secondary Facebook Login with Spring Social
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.
