1. Overview
In this tutorial, we'll discuss how to implement SSO – Single Sign On – using Spring Security OAuth and Spring Boot.
We'll use three separate applications:
- An Authorization Server – which is the central authentication mechanism
- Two Client Applications: the applications using SSO
Very simply put, when a user tries to access a secured page in the client app, they'll be redirected to authenticate first, via the Authentication Server.
And we're going to use the Authorization Code grant type out of OAuth2 to drive the delegation of authentication.
Note: this article is using the Spring OAuth legacy project. For the version of this article using the new Spring Security 5 stack, have a look at our article Simple Single Sign-On with Spring Security OAuth2.
2. The Client App
Let's start with our Client Application; we'll, of course, use Spring Boot to minimize the configuration:
2.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-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency>
2.2. Security Configuration
Next, the most important part, the security configuration of our client application:
@Configuration @EnableOAuth2Sso public class UiSecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.antMatcher("/**") .authorizeRequests() .antMatchers("/", "/login**") .permitAll() .anyRequest() .authenticated(); } }
The core part of this configuration is, of course, the @EnableOAuth2Sso annotation we're using to enable Single Sign On.
Note that we need to extend the WebSecurityConfigurerAdapter – without it, all the paths will be secured – so the users will be redirected to log in when they try to access any page. In our case here, the index and login pages are the only pages that can be accessed without authentication.
Finally, we also defined a RequestContextListener bean to handle requests scopes.
And the application.yml:
server: port: 8082 servlet: context-path: /ui session: cookie: name: UISESSION security: basic: enabled: false oauth2: client: clientId: SampleClientId clientSecret: secret accessTokenUri: http://localhost:8081/auth/oauth/token userAuthorizationUri: http://localhost:8081/auth/oauth/authorize resource: userInfoUri: http://localhost:8081/auth/user/me spring: thymeleaf: cache: false
A few quick notes:
- we disabled the default Basic Authentication
- accessTokenUri is the URI to obtain the Access Tokens
- userAuthorizationUri is the authorization URI that users will be redirected to
- userInfoUri the URI of user endpoint to obtain current user details
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.
2.3. 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:
<h1>Spring Security SSO</h1> <a href="securedPage">Login</a>
And the securedPage.html:
<h1>Secured Page</h1> Welcome, <span th:text="${#authentication.name}">Name</span>
The securedPage.html page needed the users to be authenticated. If a non-authenticated user tries to access securedPage.html, they'll be redirected to the login page first.
3. The Auth Server
Now let's discuss our Authorization Server here.
3.1. Maven Dependencies
First, we need to define the dependencies in our pom.xml:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.3.RELEASE</version> </dependency>
3.2. OAuth Configuration
It's important to understand that we're going to run the Authorization Server and the Resource Server together here, as a single deployable unit.
Let's start with the configuration of our Resource Server – which doubles as our primary Boot application:
@SpringBootApplication @EnableResourceServer public class AuthorizationServerApplication extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(AuthorizationServerApplication.class, args); } }
Then, we'll configure our Authorization server:
@Configuration @EnableAuthorizationServer public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private BCryptPasswordEncoder passwordEncoder; @Override public void configure( AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("SampleClientId") .secret(passwordEncoder.encode("secret")) .authorizedGrantTypes("authorization_code") .scopes("user_info") .autoApprove(true) .redirectUris( "http://localhost:8082/ui/login","http://localhost:8083/ui2/login"); } }
Note how we're only enabling a simple client using the authorization_code grant type.
Also, note how autoApprove is set to true so that we're not redirected and promoted to manually approve any scopes.
3.3. Security Configuration
First, we'll disable the default Basic Authentication, via our application.properties:
server.port=8081 server.servlet.context-path=/auth
Now, let's move to the configuration and define a simple form login mechanism:
@Configuration @Order(1) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.requestMatchers() .antMatchers("/login", "/oauth/authorize") .and() .authorizeRequests() .anyRequest().authenticated() .and() .formLogin().permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("john") .password(passwordEncoder().encode("123")) .roles("USER"); } @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
Note that we used simple in-memory authentication, but we can simply replace it with a custom userDetailsService.
3.4. User Endpoint
Finally, we will create our user endpoint we used earlier in our configuration:
@RestController public class UserController { @GetMapping("/user/me") public Principal user(Principal principal) { return principal; } }
Naturally, this will return the user data with a JSON representation.
4. Conclusion
In this quick tutorial, we focused on implementing Single Sign-On using Spring Security Oauth2 and Spring Boot.
As always, the full source code can be found over on GitHub.