Using JWT with Spring Security OAuth2
Tags: java, security, oauth2, jwt
Published: 2023-02-14 11:24:31

### Overview

OAuth 2.0 is the industry standard authorization protocol. OAuth 2.0 focuses on simplicity for client developers, while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices.

The OAuth authorization server is responsible for authenticating users and issuing access tokens containing user data and appropriate access policies.

Below we will use [Spring Authorization Server](https://spring.io/projects/spring-authorization-server#learn) to build a simple authorization server.

> 💡 Note: If you don’t want to read till the end, you can view the [source code](https://github.com/ReLive27/spring-security-oauth2-sample/tree/main/oauth2-jwt) here.Don’t forget to give a star to the project if you like it!

### OAuth2 authorization server implementation

Let's start with the OAuth2 authorization server configuration implementation.



#### Maven dependencies

```xml
 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
   <version>2.6.7</version>
</dependency>

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-oauth2-authorization-server</artifactId>
  <version>0.3.1</version>
</dependency>
```



#### Configuration

First let's configure the database connection information through `application.yml`.

```yml
spring:
  application:
    name: auth-server
  datasource:
    druid:
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/integrated_oauth?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
      username: <<username>> # modify username
      password: <<password>> # change Password
```

Then we create an `AuthorizationServerConfig` configuration class, in this class we will create the specific beans required by the OAuth2 authorization server. The first one will be the client service repository, where we create a client using the *RegisteredClient* builder type and persist it to the database.

```java
 @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("relive-client")
                .clientSecret("{noop}relive-client")
                .clientAuthenticationMethods(s -> {
                    s.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
                    s.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
                })
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizationGrantType(AuthorizationGrantType.PASSWORD)
                .redirectUri("http://127.0.0.1:8070/login/oauth2/code/messaging-client-authorization-code")
                .scope("message.read")
                .clientSettings(ClientSettings.builder()
                        .requireAuthorizationConsent(true)
                        .requireProofKey(false)
                        .build())
                .tokenSettings(TokenSettings.builder()
                        .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
                        .accessTokenTimeToLive(Duration.ofSeconds(30 * 60))
                        .refreshTokenTimeToLive(Duration.ofSeconds(60 * 60))
                        .reuseRefreshTokens(true)
                        .build())
                .build();

        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        registeredClientRepository.save(registeredClient);

        return registeredClientRepository;
    }
```

The properties we configure are:

- id--RegisteredClient unique id
- clientId--client identifier
- clientSecret--client secret
- clientAuthenticationMethods--the authentication method the client may use. Supported values are `client_secret_basic`, `client_secret_post`, `private_key_jwt`, `client_secret_jwt`, and `none`
- authorizationGrantTypes--the types of grants the client can use. Supported values are `authorization_code`, `client_credentials` and `refresh_token`
- redirectUris--client has registered redirect URI
- scopes--The ranges that clients are allowed to request
- clientSettings--client Custom Settings

- tokenSettings--custom settings for OAuth2 tokens issued to clients


<br />

Next let's configure the central component OAuth2AuthorizationService that stores new authorizations and queries existing ones.

```java
   @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }
```



For the authorization "consent" of an OAuth2 authorization request, Spring provides *OAuth2AuthorizationConsentService* components for storing new authorization consents and querying existing authorization consents.

```java
   @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }
```



Next let's create a bean, configure the OAuth2 authorization service with other default configurations, and use it to redirect the request to the login page for unauthenticated authorization requests.

```java
   @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        return http.exceptionHandling(exceptions -> exceptions.
                authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))).build();
    }
```



Every authorization server needs a signing key for tokens, let's generate an RSA key:

```java
final class KeyGeneratorUtils {

    private KeyGeneratorUtils() {
    }

    static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }
}
```

```java
public final class Jwks {

	private Jwks() {
	}

	public static RSAKey generateRsa() {
		KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
		return new RSAKey.Builder(publicKey)
				.privateKey(privateKey)
				.keyID(UUID.randomUUID().toString())
				.build();
	}
}
```

```java
 @Bean
    public JWKSource<SecurityContext> jwkSource() {
        RSAKey rsaKey = Jwks.generateRsa();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }
```

After processing the signing key of the token, the authorization server also needs an issuer URL, which we can create through *ProviderSettings*:

```java
    @Bean
    public ProviderSettings providerSettings() {
        return ProviderSettings.builder()
                .issuer("http://127.0.0.1:8080")
                .build();
    }
```



Finally we will enable the Spring Security security configuration class to secure our service.

```java
@EnableWebSecurity
@Configuration
public class DefaultSecurityConfig {


    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests.anyRequest().authenticated()
                )
                .formLogin(withDefaults())
        return http.build();
    }

  //...
}
```

Here *authorizeRequests*.*anyRequest()*.*authenticated*() makes all requests require authentication and provides Form-based authentication.

<br />

We also need to define the user information used by the test, the following creates a memory-based user information repository.

```java
    @Bean
    UserDetailsService users() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("admin")
                .password("password")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }
```



### Resource server implementation

Now we will create a resource server, the API interface in the service will only allow requests authenticated by the OAuth2 authorization server.

#### Maven dependencies

```xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
  <version>2.6.7</version>
</dependency>
```



#### Configuration

First let's configure the service port via application.yml.

```yml
server:
  port: 8090
```

Next, for OAuth2 security configuration, we need to use the issuerUri set by the previous authorization server in *ProviderSettings*.

```java
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://127.0.0.1:8080
```

The resource server will use this Uri to further configure itself, discover the public key of the authorization server, and pass in the JwtDecoder used to verify the JWT. A consequence of this process is that the authorization server must start and receive requests for the resource server to start successfully.

<br />

If the resource server must be able to start independently of the authorization server, then `jwk-set-uri` can be provided. This will be our further property to add in the OAuth2 security configuration:

```yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://127.0.0.1:8080
          jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks

```

Now that we can set up the Spring Security security configuration, every request to the service resource should be authorized and have the appropriate permissions:

```java
@EnableWebSecurity
public class ResourceServerConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.requestMatchers()
                .antMatchers("/resource/test/**")
                .and()
                .authorizeRequests()
                .mvcMatchers("/resource/test/**")
                .access("hasAuthority('SCOPE_message.read')")
                .and()
                .oauth2ResourceServer()
                .jwt();
        return http.build();
    }
}
```



<br />

Finally, we will create a REST controller that will return the jwt claims information.

```java
@RestController
public class ResourceServerTestController {

    @GetMapping("/resource/test")
    public Map<String, Object> getArticles(@AuthenticationPrincipal Jwt jwt) {
        return jwt.getClaims();
    }
}
```



### OAuth2 client

Now we want to create a client, which first requests authorization from the authorization server to obtain an access token, and then accesses the corresponding resource on the resource server.

#### Maven dependencies

```xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webflux</artifactId>
  <version>5.3.9</version>
</dependency>
```



#### Configuration

First of all, we will configure the client's access port 8070 in `application.yml`.

```yml
server:
  port: 8070
```



Next we'll define the configuration properties for the OAuth2 client:

```yaml
spring:
  security:
    oauth2:
      client:
        registration:
          messaging-client-authorization-code:
            provider: client-provider
            client-id: relive-client
            client-secret: relive-client
            authorization-grant-type: authorization_code
            redirect-uri: "http://127.0.0.1:8070/login/oauth2/code/{registrationId}"
            scope: message.read
            client-name: messaging-client-authorization-code
        provider:
          client-provider:
            authorization-uri: http://127.0.0.1:8080/oauth2/authorize
            token-uri: http://127.0.0.1:8080/oauth2/token
```

Now let's create a WebClient instance to perform HTTP requests to the resource server:

```java
  @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        return WebClient.builder()
                .apply(oauth2Client.oauth2Configuration())
                .build();
    }
```

`WebClient` adds an OAuth2 authorization filter, which requires `OAuth2AuthorizedClientManager` as a dependency. Only the authorization code and refresh token are configured here, and other modes can be added if necessary:

```java
@Bean
    OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
                                                          OAuth2AuthorizedClientRepository authorizedClientRepository) {

        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
                .builder()
                .authorizationCode()
                .refreshToken()
                .build();
        DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }
```

Finally, we'll configure the Spring Security security configuration:

```java
 @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests ->
                        //Easy to test, open permissions
                        authorizeRequests.anyRequest().permitAll()
                )
                .oauth2Client(withDefaults());
        return http.build();
    }
```

Here we release all client API permissions, but in actual situations, client services require authentication. The OAuth2 protocol itself is an authorization protocol and does not care about the specific form of authentication. You can add simple forms authentication.

#### Access resource list

Finally, we create a controller where we will use the previously configured WebClient to make HTTP requests to our resource server:

```java
@RestController
public class ClientTestController {
    @Autowired
    private WebClient webClient;

    @GetMapping(value = "/client/test")
    public Map<String, Object> getArticles(@RegisteredOAuth2AuthorizedClient("messaging-client-authorization-code") OAuth2AuthorizedClient authorizedClient) {
        return this.webClient
                .get()
                .uri("http://127.0.0.1:8090/resource/test")
                .attributes(oauth2AuthorizedClient(authorizedClient))
                .retrieve()
                .bodyToMono(Map.class)
                .block();
    }
}
```

In the above example, we use the @*RegisteredOAuth2AuthorizedClient* annotation to bind *OAuth2AuthorizedClient*, and trigger the OAuth2 authorization code mode process to obtain an access token.

### Conclusion

This example mainly demonstrates the secure communication between two services using the OAuth2 protocol, especially in complex Internet scenarios, where client services and resource services are provided by different platforms. OAuth2 is very good at obtaining the user's entrusted decision, In many ways, it is simpler and safer than other solutions. <br />

The source code used in this article is available on [GitHub](https://github.com/ReLive27/spring-security-oauth2-sample/tree/main/oauth2-jwt).

You might want to read on to the next one: 
- [Customize the OAuth2 Authorization Consent Page](https://dev.to/relive27/customize-the-oauth2-authorization-consent-page-24n6)
- [Spring Security Persistent OAuth2 Client](https://dev.to/relive27/spring-security-persistent-oauth2-client-1gcc)
- [Spring Security OAuth2 Client Credentials Grant](https://dev.to/relive27/spring-security-oauth2-client-credentials-grant-fj5)
- [Authorization Code Flow with Proof Key for Code Exchange (PKCE)](https://dev.to/relive27/authorization-code-flow-with-proof-key-for-code-exchange-pkce-4h22)
- [Spring Security OAuth2 Login](https://dev.to/relive27/spring-security-oauth2-login-51lj)

Thanks for reading!