Spring Security OAuth2 Boot simplifies protecting your resources using Bearer Token authentication in two different token formats: JWT and Opaque.
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
.
Creating a minimal Spring Boot resource server consists of three basic steps:
@EnableResourceServer
annotation.
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.
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.
To indicate JWT, simply specify the JWK Set Uri hosted on your Authorization Server:
spring: 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.
To indicate opaque, simply specify the Authorization Server endpoint that knows how to decode the token:
spring: security: oauth2: resource: token-info-uri: https://idp.example.com/oauth2/introspect
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.
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:
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:
spring: security: oauth2: resource: jwt: key-value: | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC... -----END PUBLIC KEY-----
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.
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:
spring: 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.
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:
spring: 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.
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.
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.
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
.
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:
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 | |
---|---|
Resource Server’s default |
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(); } }
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.