13 Authorization System How to Construct an Oauth2 Authorization Server

13 Authorization System How to Construct an OAuth2 Authorization Server #

In the previous lesson, we discussed the detailed content of the OAuth2 protocol, and we believe that you now understand how to use the OAuth2 protocol to implement authorization between microservices. However, before that, we need to build an OAuth2 authorization server in the microservice system. Today, we will discuss how to build this authorization server based on the Spring Security framework, and generate corresponding tokens based on the commonly used password mode, in order to provide a basis for service access control in the next lesson.

Building an OAuth2 Authorization Server #

In terms of its form, the OAuth2 authorization server is also an independent microservice, so the method of building the authorization server is to create a Spring Boot application. We need to introduce the corresponding Maven dependencies, as shown below:

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

Here, spring-security-oauth2 is the OAuth2 library from Spring Security. Now that the Maven dependencies have been added, the next step is to build a Bootstrap class as the entry point for access:

@SpringBootApplication
@EnableAuthorizationServer
public class AuthorizationServer {
    
    public static void main(String[] args) {
        SpringApplication.run(AuthorizationServer.class, args);
    }
}

Please note that a new annotation @EnableAuthorizationServer appears here. The purpose of this annotation is to provide an authorization service based on the OAuth2 protocol for the microservice runtime environment. This authorization service will expose a series of endpoints based on the RESTful style (such as /oauth/authorize and /oauth/token) for the use of the OAuth2 authorization process.

Building an OAuth2 authorization service is just the first step in integrating the OAuth2 protocol. The authorization server is a centralized system that manages all client and user information related to security processes. Therefore, next, we need to initialize this basic information in the authorization server, and Spring Security provides us with various configuration classes to achieve this goal.

Setting Client and User Authentication Information #

In the previous lesson, we mentioned that the OAuth2 protocol has four authorization modes, and mentioned that in the microservices architecture, the password mode has been widely used due to its simplicity. In the following content, we will explain the password mode as an example.

In the password mode, the user provides the username and password to the client and sends the username and password to the authorization server to request a token. The authorization server first authenticates the password credential information, and if it is correct, it issues a token to the client. The entire process is shown in the diagram below:

Drawing 0.png

Illustration of the password mode authorization process

Please note that the purpose of the authorization server to perform authentication operations here is to verify whether the incoming username and password are correct. In the password mode, this step is necessary, but in other authorization modes, it is not necessary to have user authentication.

After determining the use of the password mode, let’s see what development work needs to be done for the authorization server to implement this authorization mode. First, we need to set some basic data, including client information and user information.

Setting Client Information #

Let’s first look at how to set client information. When setting the client, the configuration class used is ClientDetailsServiceConfigurer, which is used to configure the client details service ClientDetailsService. The ClientDetails interface, used to describe client details, contains several important methods related to security control. Some of the methods defined in this interface are as follows:

public interface ClientDetails extends Serializable {

    // The unique ID of the client
    String getClientId();
    // Client secret
    String getClientSecret();

    // Client scope
    Set<String> getScope();

    // Client authorized grant types
    Set<String> getAuthorizedGrantTypes();

    ...
}

The above code contains several properties that are closely related to daily development work.

Firstly, ClientId is a required property used to uniquely identify the client. ClientSecret represents the client’s security code. The Scope is used to limit the client’s access range. If this property is empty, the client has access to the entire range. Common settings can be “webclient” or “mobileclient”, representing the web end and mobile end respectively.

Finally, authorizedGrantTypes represent the authorization modes that the client can use. The optional range includes “authorization_code” representing the authorization code grant type, “implicit” representing the implicit grant type, “password” representing the password grant type, and “client_credentials” representing the client credentials grant type. This property can also include “refresh_token” to obtain a new token through a refresh operation under the above authorization modes.

Similar to the authentication process, Spring Security also provides the AuthorizationServerConfigurerAdapter configuration adapter class to simplify the usage of configuration classes. We can configure the client information by extending this class and overriding the configure(ClientDetailsServiceConfigurer clients) method. The basic code structure for configuring client information using AuthorizationServerConfigurerAdapter is as follows:

@Configuration
public class SpringAuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("spring")
            .secret("{noop}spring_secret")
            .authorizedGrantTypes("refresh_token", "password", "client_credentials")
            .scopes("webclient", "mobileclient");
    }
}

As you can see, we created a SpringAuthorizationServerConfigurer class to inherit AuthorizationServerConfigurerAdapter, and set the grant type to password grant type through the ClientDetailsServiceConfigurer configuration class. There are two ways to store client information in the authorization server: one is an in-memory level storage as shown in the above code, and the other is to store detailed information in the database through JDBC. For simplicity, an in-memory level storage method is used here.

At the same time, we noticed that when setting the client’s security code, the format “{noop}spring_secret” is used. This is because in Spring Security 5, PasswordEncoder is used to encode passwords, and the format “{id}password” is required when setting passwords. The prefix “{noop}” represents the specific PasswordEncoder id, indicating that we are using NoOpPasswordEncoder. Regarding PasswordEncoder, you can review the content in the article “Password Security: What encryption and decryption techniques does Spring Security include?”.

As mentioned in the previous content, the @EnableAuthorizationServer annotation exposes a series of endpoints, and the authorization process is controlled by the AuthorizationEndpoint endpoint. To configure the behavior of this endpoint, you can use the AuthorizationServerEndpointsConfigurer configuration class. Similar to the ClientDetailsServiceConfigurer configuration class, we also configure using the AuthorizationServerConfigurerAdapter configuration adapter class.

Because we specified the password grant type, which includes an authentication step, we need to specify an authentication manager AuthenticationManager to authenticate the username and password. Also, because we specified the password-based authorization mode, we need to specify a custom UserDetailsService to replace the global implementation. We have discussed UserDetailsService in detail in the article “User Authentication: How to use Spring Security to build a user authentication system?”. We can explicitly configure AuthorizationServerEndpointsConfigurer using the following code:

@Configuration
public class SpringAuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Autowired
    ...
}
private AuthenticationManager authenticationManager;

@Autowired
private UserDetailsService userDetailsService;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService);
} 

So far, the client configuration work is complete. What we have done is implement a custom SpringAuthorizationServerConfigurer configuration class and override the corresponding configuration method.

Setting User Authentication Information #

The configuration class that sets user authentication information depends on the WebSecurityConfigurer class. Similarly, Spring Security provides the WebSecurityConfigurerAdapter class to simplify the use of this configuration class. We can inherit from the WebSecurityConfigurerAdapter class and override its configure() method to complete the configuration work.

Regarding the WebSecurityConfigurer configuration class, we first need to clarify the content of the configuration. In fact, setting user information is very simple, just specify the username, password, and role. The code is as follows:

@Configuration
public class SpringWebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    @Bean
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
        builder
            .inMemoryAuthentication()
            .withUser("spring_user")
            .password("{noop}password1")
            .roles("USER")
            .and()
            .withUser("spring_admin")
            .password("{noop}password2")
            .roles("USER", "ADMIN");
    }

}

Combining the above code, we can see that two users with different roles and passwords are built. Please note that “spring_user” represents a regular user, while “spring_admin” has the role of an administrator. When setting the password, we also need to add the prefix “{noop}”. At the same time, we also see that the authenticationManagerBean() and userDetailsServiceBean() methods respectively return the default implementation of the parent class, and the UserDetailsService and AuthenticationManager returned here will be used when setting the client earlier. You can review the content of user authentication mechanism in “User Authentication: How to Use Spring Security to Build User Authentication System?”.

Generating Tokens #

Now that the OAuth2 authorization server has been built, we can obtain a token by starting this authorization server. As mentioned when building the OAuth2 server, a batch of endpoints are exposed in the authorization server for HTTP requests to access, and the endpoint for obtaining tokens is http://localhost:8080/oauth/token. When using this endpoint, we need to provide the client information and user information configured earlier.

We use Postman to simulate the HTTP request. The method for setting client information is as shown in the following figure:

Drawing 1.png

Illustration of client information setting

In the “Authorization” request header, we specify the authentication type as “Basic Auth” and set the client name and client secret code as “spring” and “spring_secret” respectively.

Next, we specify the dedicated configuration information for the authorization mode. First, we set the grant_type attribute used to specify the authorization mode, and the scope attribute used to specify the client’s access scope. Here, we set them to “password” and “webclient”, respectively. Since the password mode is set, we also need to specify the username and password to identify the user’s identity. Here, we use the user “spring_user” as an example. The setting is as shown in the following figure:

Drawing 2.png

Illustration of user information setting

When executing this request in Postman, the following result is obtained:

{
    "access_token": "0efa61be-32ab-4351-9dga-8ab668ababae",
    "token_type": "bearer",
    "refresh_token": "738c42f6-79a6-457d-8d5a-f9eab0c7cc5e",
    "expires_in": 43199,
    "scope": "webclient"
}

As you can see, in addition to the scope, which is a request parameter, this result also contains attributes such as access_token, token_type, refresh_token, and expires_in. These attributes are all important, and we have explained them in detail in the previous lesson. Of course, because each request generates a unique token, the result you get when trying may be different from mine.

Summary and Preview #

The first prerequisite for securing microservice access is to generate an access token. In this lesson, we started with building the OAuth2 server and explained the implementation process of setting client information, user authentication information, and ultimately generating tokens based on the password mode. In this process, developers need to be familiar with the relevant concepts of the OAuth2 protocol and the configuration functions provided by the Spring Security framework.

The summary of this lesson is as follows:

13-2.jpg

Finally, I leave you with a question to think about: Based on the password mode, can you explain the specific development steps required to generate a token?