Spring Security makes it easy to implement OAuth2 as your protocol for authentication. In this article, we are going to implement an authentication server using Spring Security OAuth2. We are also going to implement a very basic client which will make use of the authentication server.
The following is required in order to follow along the article.
We are going to create two projects. The first one will be the authentication server, and the other one will be the client which will have a basic user interface and use the authentication server. It’s possible to have these both on the same server but I believe it’s easier to understand the examples if they are implemented separately, this also means that it will be much easier to switch to another authentication server in the future, for example, facebook or google.
Start by heading to Spring Initializr. I am creating a Maven application with Java and Spring Boot 1.5.9.
I uploaded the two projects to GitHub if you would like to inspect the final code; Authorization Server and Authorization Client.
For the authentication add the following dependencies:
I am also going to go ahead and add Couchbase as a dependency as it’s my preferable database, but you can, of course, use any database that you would like.
For the authentication client you will want to have the following dependencies:
We are going to start with implementing the Spring Security OAuth2 Authentication Server.
Under a package named config create a class AuthorizationServerConfig
.
package org.thecuriousdev.authserver.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private AuthenticationManager authenticationManager;
@Autowired
public AuthorizationServerConfig(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("ClientId")
.secret("secret")
.authorizedGrantTypes("authorization_code")
.scopes("user_info")
.autoApprove(true);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
}
The annotation @EnableAuthorizationServer
enables an AuthorizationEndpoint and a TokenEndpoint, and we also extend AuthorizationServerConfigurerAdapter to be able to override some configuration that we need. In configure(ClientDetailsServiceConfigurer)
we specify that we want to store the necessary data for creating sessions in memory. We choose a pretty basic clientId and secret in the example, but if you are taking this into production then I recommend setting it to something more appropriate. People should not be able to guess this value. There are some different authorizedGrantTypes that we can specify, but authorization_code is considered a “classic” OAuth2 flow. If you are interested in reading about different grant types, then I recommend checking out Authorization Grant. Additionally, we configure autoApprove to be true in order to disable the user approval process and do a redirect directly.
Our authentication server is also going to act as a resource server and therefore we need to create that configuration.
package org.thecuriousdev.authserver.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.thecuriousdev.authserver.service.CustomUserDetailsService;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Autowired
public ResourceServerConfig(AuthenticationManager authenticationManager,
CustomUserDetailsService customUserDetailsService) {
this.authenticationManager = authenticationManager;
this.customUserDetailsService = customUserDetailsService;
}
@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.parentAuthenticationManager(authenticationManager)
.userDetailsService(customUserDetailsService);
}
}
Pretty standard stuff, we declare two antMatchers “/login” and “/oauth/authorize”, and we also let Spring generate a login page that everyone is permitted to visit. For the AuthenticationManager we pass in a CustomUserDetailsService
which is a class we created in order to make it possible to read the user from the database. The CustomUserDetailsService
can make use of any database. The implementation is very dependant on which type of database that you use, but I will give you mine as an example and it should be very straightforward for you to retrieve the user from any database of your choice.
package org.thecuriousdev.authserver.service;
import java.util.Arrays;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.thecuriousdev.authserver.entity.User;
import org.thecuriousdev.authserver.repository.UserRepository;
import org.thecuriousdev.authserver.util.CustomUserDetails;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private UserRepository userRepository;
@Autowired
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@PostConstruct
public void test() {
User user = new User();
user.setUsername("planevik");
user.setPassword("password");
user.setRoles(Arrays.asList("ADMIN", "USER"));
if (!userRepository.findById("planevik").isPresent()) {
userRepository.create(user);
}
}
@Override
public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
return new CustomUserDetails(userRepository.findById(id)
.orElseThrow(() -> new UsernameNotFoundException("User not found")));
}
}
Our class implements the UserDetailsService which forces us to implement the loadUserByUsername method where we have to return UserDetails for the user. Because of this, we also create a CustomerUserDetails class which can look like the following. If you are interested in implementing a Couchbase as your database, I recommend checking out my previous article, NOSQL with Couchbase: Getting Started, where I basically implement a very similar repository. Of course, you can also check out the GitHub source code for this article.
package org.thecuriousdev.authserver.util;
import java.util.Collection;
import java.util.stream.Collectors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.thecuriousdev.authserver.entity.User;
public class CustomUserDetails implements UserDetails {
private String username;
private String password;
private Collection extends GrantedAuthority> authorities;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.authorities = user.getRoles().stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
Our class implements the UserDetails interface and implements the necessary methods.
We have a User
entity which we create a convenient constructor for that will construct our CustomUserDetails class from it. We leave out the implementation of account expiration, locked and enabled by simply returning true.
When a user has authenticated we want the resource server to return a resource so that we can check if it worked. We create a class ResourceController
.
package org.thecuriousdev.authserver;
import java.security.Principal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/rest/resource")
public class ResourceController {
@GetMapping
public Principal user(Principal principal) {
return principal;
}
}
We return the principal which also makes it possible to inspect the name of the authenticated user.
The last thing we need is some application configuration. I created the application.yml
under our resources folder.
server:
port: 12050
context-path: /auth
security.basic.enable: false
We specify the context-path /auth
to make it more clear that it’s the authentication server.
We move on to our second server which will make use of the Spring Security OAuth2 authentication server that we have created. The client application will be a very basic UI application with an index page instructing people to press a link to log in where they get redirected to the login form of the authentication server to retrieve an authorization token. This token will be stored as a cookie and used when retrieving resources from the resource server.
In order to enable OAuth2 as single sign on we can use the @EnableOAuth2Sso. We also extend the WebSecurityConfigurerAdapter
in order to override configure(HttpSecurity)
.
package org.thecuriousdev.authclient.config;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableOAuth2Sso
public class OAuthConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**")
.permitAll()
.anyRequest()
.authenticated();
}
}
We are going to create two templates, one index template which will be open to everyone. The second one, will be a secured template which requires a valid authentication token.
Under resources/templates, create the two following templates.
Spring Security OAuth2 Example
Login via OAuth2 here
secure.html
Spring Security OAuth2 Example - Secured
Welcome to the secured page!
Creating the controller for displaying the templates
Create a controller like the following in order to forward the request to the appropriate template that we created.
package org.thecuriousdev.authclient.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class BasicController {
@GetMapping
public String index() {
return "index";
}
@GetMapping("/secure")
public String secure() {
return "secure";
}
}
Our Spring Security OAuth2 client application also needs some configuration.
server:
port: 12051
context-path: /ui
session:
cookie:
name: UISESSION
security:
basic:
enabled: false
oauth2:
client:
clientId: ClientId
clientSecret: secret
accessTokenUri: http://localhost:12050/auth/oauth/token
userAuthorizationUri: http://localhost:12050/auth/oauth/authorize
resource:
userInfoUri: http://localhost:12050/auth/rest/resource
spring:
thymeleaf:
cache: false
We assign a context-path of /ui
so that it is more clear that it is the user interface. We also give the name “UISESSION” to our session cookie. In this cookie, the authentication token will be stored that we retrieved from the authentication server. In our OAuth2 client configuration, the clientId and secret have to match to what we created in the authentication server, and we also assign the URI for retrieving the token and on what URI the user should perform the authorization. Additionally, we specify what URL should be used to retrieve the user information. All of this has to match with what we created in the authentication server.
In order to inspect the authorization object (and use more nice functionalities with Spring Security via Thymeleaf, the following dependency should be added to both the server and the client.
org.thymeleaf.extras
thymeleaf-extras-springsecurity4
If we now boot both servers up and hit the client application on http://localhost:12051/ui
we should be presented with the index page where we can press to log in with OAuth2. Log in with OAuth2 should redirect us to http://localhost:12050/auth/login
where we can enter our user credentials. Assuming that you have implemented a CustomerDetailService with a database of your choice, you should be able to login with the details that we created in our @PostConstruct
in that service. Doing that redirects us back to our client application http://localhost:12051/ui/secure
and we have now accessed a secured template.
If we inspect our cookies in the browser you should now see a UISESSION cookie that was created which stores the authentication token that we received from our Spring Security OAuth2 authentication server.
We have created an Authentication Server which implements both an AuthorizationServer and a ResourceServer. Additionally, we created an Authentication Client application which uses the Authentication Server as the security mechanism. The Authentication Server that we created is basically the same type of service that Facebook and Google provides (but of course a lot primitive). Because we developed a separate client application, it is also very easy to switch it to use an existing trusted authentication server (such as Facebook or Google).
Streams has become a very popular way to process a collection of elements. But a…
A lot of focus on my previous blogs has been on how to build micro…
Learn how to work with high-quality reference genomes in this article by Tiago Antao, a…
Garbage collection is one of the key concepts of Java programming and up to now…
Learn about convolution in this article by Sandipan Dey, a data scientist with a wide…
Lombok comes with a very convenient way of creating immutable objects with the builder pattern.…