2. Resource Server

Spring Security OAuth2 Boot simplifies protecting your resources using Bearer Token authentication in two different token formats: JWT and Opaque.

2.1 Dependencies

To use the auto-configuration features in this library, you need spring-security-oauth2, which has the OAuth 2.0 primitives and spring-security-oauth2-autoconfigure. Note that you need to specify the version for spring-security-oauth2-autoconfigure, since it is not managed by Spring Boot any longer, though it should match Boot’s version anyway.

For JWT support, you also need spring-security-jwt.

2.2 Minimal OAuth2 Boot Configuration

Creating a minimal Spring Boot resource server consists of three basic steps:

  1. Including the dependencies.
  2. Including the @EnableResourceServer annotation.
  3. Specifying a strategy for verifying the bearer token.

2.2.1 Enabling the Resource Server

Similar to other Spring Boot @Enable annotations, you can add the @EnableResourceServer annotation to the class that contains your main method, as the following example shows:

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

Adding this annotation adds the OAuth2AuthenticationProcessingFilter, though it will need one more configuration to know how to appropriately process and validate tokens.

2.2.2 Specifying a Token Verification Strategy

Bearer Tokens typically come in one of two forms: JWT-encoded or opaque. You will need to configure the resource server with one or the other strategy.

JWT

To indicate JWT, simply specify the JWK Set Uri hosted on your Authorization Server:

security:
  oauth2:
    resource:
      jwk:
        key-set-uri: https://idp.example.com/.well-known/jwks.json

Instead of a JWK Set Uri, you can also specify a key.

Note that with this configuration, your authorization server needs to be up in order for Resource Server to start up.

Opaque

To indicate opaque, simply specify the Authorization Server endpoint that knows how to decode the token:

security:
  oauth2:
    resource:
      token-info-uri: https://idp.example.com/oauth2/introspect
[Note]Note

It’s likely this endpoint requires some kind of authorization separate from the token itself, for example, client authentication.

That’s it! But, what do you do with it? We cover that next.

2.2.3 Accessing a Resource

To confirm that Resource Server is correctly processing tokens, you can add a simple controller endpoint like so:

@RestController
public class SimpleController
	@GetMapping("/whoami")
	public String whoami(@AuthenticationPrincipal(expression="name") String name) {
		return name;
    }
}

Then, obtain an active access token from your Authorization Server and present it to the Resource Server:

curl -H "Authorization: $TOKEN" http://localhost:8080/whoami

And you should see the value of the user_name attribute in the token.

From this point, you may want to learn more about three alternative ways to authenticate using bearer tokens:

2.3 How to Use JWT with a Single Key

Instead of a JWK Set endpoint, you may have a local key you want to configure for verification. While this is weaker due to the key being static, it may be necessary in your situation.

Configuring the resource server with the appropriate symmetric key or PKCS#8 PEM-encoded public key is simple, as can be seen below:

security:
  oauth2:
    resource:
      jwt:
        key-value: |
          -----BEGIN PUBLIC KEY-----
          MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC...
          -----END PUBLIC KEY-----
[Tip]Tip

The pipe in yaml indicates a multi-line property value.

You can also instead supply a key-store, key-store-password, key-alias, and key-password properties.

Or you can use the key-uri endpoint to get the key remotely from your authorization server, which is something of a happy medium between static, local configuration and a JWK Set endpoint.

2.4 How to Configure the Token Info Endpoint

The token info endpoint, also sometimes called the introspection endpoint, likely requires some kind of client authentication, either Basic or Bearer. Generally speaking, the bearer token in the SecurityContext won’t suffice since that is tied to the user. Instead, you’ll need to specify credentials that represent this client, like so:

security:
  oauth2:
    client:
      clientId: client-id
      clientSecret: client-secret
    resource:
      tokenInfoUri: https://idp.example.com/oauth2/check_token

By default, this will use Basic authentication, using the configured credentials, to authenticate against the token info endpoint.

2.5 How to Configure the User Info Endpoint

It’s atypical for a resource server to need to call a user info endpoint. This is because, fundamentally, a resource server is about authorizing a request, not authenticating it. That said, it is at times necessary.

If you specify a user info endpoint like so:

security:
  oauth2:
    resource:
      userInfoUri: https://idp.example.com/oauth2/userinfo

Then Resource Server will send it the bearer token that is part of the request and enhance the Authentication object with the result.

2.5.1 Customizing the User Info Request

Internally, Resource Server uses an OAuth2RestTemplate to invoke the /userinfo endpoint. At times, it may be necessary to add filters or perform other customization for this invocation. To customize the creation of this bean, you can expose a UserInfoRestTemplateCustomizer, like so:

@Bean
public UserInfoRestTemplateCustomizer customHeader() {
	return restTemplate ->
			restTemplate.getInterceptors().add(new MyCustomInterceptor());
}

This bean will be handed to a UserInfoTemplateFactory which will add other configurations helpful to coordinating with the /userinfo endpoint.

And, of course, you can replace the UserInfoTemplateFactory completely, if you need complete control over `OAuth2RestTemplate’s configuration.

2.6 Customizing Authorization Rules

Similar to how Spring Security works, you can customize authorization rules by endpoint in Spring Security OAuth, like so:

public class HasAuthorityConfig
		extends ResourceServerConfigurerAdapter {

	@Override
	public void configure(HttpSecurity http) throws Exception {
		// @formatter:off
		http
			.authorizeRequests()
				.antMatchers("/flights/**").hasAuthority("#oauth2.hasScope('message:read')")
				.anyRequest().authenticated();
		// @formatter:on
	}

Though, note that if a server is configured both as a resource server and as an authorization server, then there are certain endpoint that require special handling. To avoid configuring over the top of those endpoints (like /token), it would be better to isolate your resource server endpoints to a targeted directory like so:

public class ResourceServerEndpointConfig
		extends ResourceServerConfigurerAdapter {

	@Override
	public void configure(HttpSecurity http) throws Exception {
		// @formatter:off
		http
			.antMatchers("/resourceA/**", "/resourceB/**")
			.authorizeRequests()
				.antMatchers("/resourceA/**").hasAuthority("#oauth2.hasScope('resourceA:read')")
				.antMatchers("/resourceB/**").hasAuthority("#oauth2.hasScope('resourceB:read')")
				.anyRequest().authenticated();
		// @formatter:on
	}

As the above configuration will target your resource endpoints and not affect authorization server-specific endpoints.

2.7 Less Common Features

2.7.1 Changing the Token Type

Google and certain other third-party identity providers are more strict about the token type name that is sent in the headers to the user info endpoint. The default is Bearer, which suits most providers and matches the spec. However, if you need to change it, you can set security.oauth2.resource.token-type.

2.7.2 Changing the Filter Order

OAuth2 resources are protected by a filter chain with the order specified by security.oauth2.resource.filter-order.

By default the filters in AuthorizationServerConfigurerAdapter come first, followed by those in ResourceServerConfigurerAdapter, followed by those in WebSecurityConfigurerAdapter.

This means that all application endpoints will require bearer token authentication unless one of two things happens:

  1. The filter chain order is changed or
  2. The ResourceServerConfigurerAdapter set of authorized requests is narrowed

The first, changing the filter chain order, can be done by moving WebSecurityConfigurerAdapter in front of ResourceServerConfigurerAdapter like so:

@Order(2)
@EnableWebSecurity
public WebSecurityConfig extends WebSecurityConfigurerAdapter {
	// ...
}
[Note]Note

Resource Server’s default @Order value is 3 which is why the example sets Web’s @Order to 2, so that it’s evaluated earlier.

While this may work, it’s a little odd since we may simply trade one problem:

ResourceServerConfigurerAdapter is handling requests it shouldn’t

For another:

WebSecurityConfigurerAdapter is handling requests it shouldn’t

The more robust solution, then, is to indicate to ResourceServerConfigurerAdapter which endpoints should be secured by bearer token authentication.

For example, the following configures Resource Server to secure the web application endpoints that begin with /rest:

@EnableResourceServer
public ResourceServerConfig extends ResourceServerConfigurerAdapter {
	@Override
    protected void configure(HttpSecurity http) {
        http
            .requestMatchers()
                .antMatchers("/rest/**")
            .authorizeRequests()
                .anyRequest().authenticated();
    }
}

2.7.3 Permitting the /error Endpoint

Resource Server, when also configured as a client, may rely on a request-scoped OAuth2ClientContext bean during the authentication process. And, in some error situations, Resource Server forwards to the ERROR servlet dispatcher.

By default, request-scoped beans aren’t available in the ERROR dispatch. And, because of this, you may see a complaint about the OAuth2ClientContext bean not being available.

The simplest approach may be to permit the /error endpoint, so that Resource Server doesn’t try and authenticate the request:

public class PermitErrorConfig extends ResourceServerConfigurerAdapter {
    @Override
	public void configure(HttpSecurity http) throws Exception {
		// @formatter:off
		http
			.authorizeRequests()
				.antMatchers("/error").permitAll()
				.anyRequest().authenticated();
		// @formatter:on
	}
}

Other solutions are to configure Spring so that the RequestContextFilter is registered with the error dispatch or to register a RequestContextListener bean.