Spring OAuth Server: Default Configuration
Explore Spring Authorization server's default configuration: registration of new clients, testing endpoints, JWT customization, and reference/self-contained tokens.
Join the DZone community and get the full member experience.
Join For FreeSpring has come out with an OAuth2 solution, and in this article, we will look at the default configuration that comes bundled with the spring-oauth server. Details about how OAuth2.0 works are out of the scope of this article and the audience of this article is expected to have a basic understanding of it. You can find more details on it here.
In this and other articles, I will talk more about the technical aspects of the Spring OAuth2.0 solution.
Default Configuration
Spring OAuth server comes with some default settings. One can customize it by configuring the server/clients in the application.yml config file. The following flows (grant types) are supported:
- Client Credentials flow (/oauth2/token endpoint)
- Authorization Code flow (Including PKCE)
- Resource Owner Credential Flow (Deprecated)
- Implicit flow (Code flow without code :))
- Device Authorization flow
Details around the configuration can be found at the GitHub. This is a sample codebase.
Regarding default configuration, you can refer to the server configuration here where we can see how configuration can be done to use the default server configuration.
AuthorizationServerTest can be referred to see how we can verify different endpoints through functional testing. To make this test run successfully, the OAuth server should be running. To run the server, you can use the AuthorizationServerApplication class using IDE or from the command prompt as well using the command below:
mvn spring-boot:run
Let's look at the sample client "spring" (you can name it anything you want to) configuration and talk about the significance of each property below.
spring:
security:
oauth2:
authorizationserver:
client:
spring:
registration:
client-id: "spring-test" #The name to be used in different configurations to refer this client.
client-secret: "sMJ1ltm5wxdcOeEJGaE6WdFj9ArR75wkBqUgVE7vwwo=" ##Using D3PasswordEncoder
client-authentication-methods: #methods supported to authenticate the client
- "client_secret_basic"
- "client_secret_post"
authorization-grant-types: #The flows this client support
- "authorization_code"
- "refresh_token"
- "client_credentials"
redirect-uris: # The Url to be used at the end of successful authentication
- "https://127.0.0.1:9443/"
- "https://127.0.0.1:9443/login/oauth2/code/spring"
post-logout-redirect-uris:
- "http://127.0.0.1:8080/"
scopes:
- "openid"
- "profile"
- "email"
require-authorization-consent: true
client-id
: A unique ID assigned to the client; it will be used to identify the client and configuration whenever the client makes a call to the authorization server.client-secret
: Secret to be used by the client to authenticate itself when making a call to the authorization serverclient-authentication-methods
: Authentication methods that the client uses to authenticate itselfclient_secret_basic
: Basic authentication approach where credentials are provided as headers (httpHeaders -> httpHeaders.setBasicAuth(TEST_CLIENT_ID, TEST_SECRET)
)client_secret_post
: Authentication by providing credentials in the request body (application/x-www-form-urlencoded)
authorization-grant-types
: The grant types supported by the clientredirect-uris
: Theredirect-uri
(s) that is/are supported by the client and allow redirects that the client can use while starting theauthorize_code
flowpost-logout-redirect-uris
:redirect-uri
(s) after successful logout fromopenId
scopes
: Supported scopes by the clientrequire-authorization-consent
: If consent is required during the authorization code flow
Some of the default configurations that are good to know and not there by default in different examples are:
- Access token format by default is self-contained (jwt) and can be configured to opaque (reference) token through configuration.
- The refresh token TTL is 60min.
- The access token TTL is 5min.
- The authorization code TTL is 5min.
- The consent form is disabled by default.
We can override the above default behavior by overriding the configuration in the application.yml file as:
spring:
security:
oauth2:
authorizationserver:
client:
spring-client:
require-authorization-consent: true
token:
access-token-format: reference
authorization-code-time-to-live: PT10M
access-token-time-to-live: PT10M
refresh-token-time-to-live: PT2H
Default Security FilterChain
Sometimes I think about how the security framework is configured to make it work, so here is what I tried to put together on how the application is configured using different configurations, filters, and properties to make it work.
Starting With Application FilterChain
Filters(5):
ApplicationFilterConfig (characterEncodingFilter)
ApplicationFilterConfig (formContentFilter)
ApplicationFilterConfig (requestContextFilter)
ApplicationFilterConfig (springSecurityFilterChain)
ApplicationFilterConfig (Tomcat WebsocketFilter-JSR356)
ApplicationFilterConfig (springSecurityFilterChain)
Application filter config "springSecurityFilterChain
" is the main class that holds the filter (Spring Security) instances which are instantiated when a web application is started.
The filter instance it (springSecurityFilterChain-ApplicationFilterConfig
) holds is DelegatingFilterProxyRegistrationBean
. DelegatingFilterProxyRegistrationBean
is a ServletContextInitializer; it registers DelegatingFilterProxy
and holds the name of the actual delegate.
Filter (DelegatingFilterProxyRegistrationBean
) [springSecurityFilterChain urls=[/*] order=-100]
:
public class DelegatingFilterProxyRegistrationBean
extends AbstractFilterRegistrationBean<DelegatingFilterProxy>
implements ApplicationContextAware
- TargetBeanName:
springSecurityFilterChain
- Filter (DelegatingFilterProxy)
DelegatingFilterProxy
Spring provided filter implementation, a bridge between the servlet container and Spring's ApplicationContext
.
- TargetBeanName:
springSecurityFilterChain
- Delegate:
FilterChainProxy
FilterChainProxy
This holds the Spring Security filter chain(s). Concerning the OAuth authorization server, there are two security filter chains (DefaultSecurityFilterChain
): one for the OAuth endpoint and one for the rest.
- FilterChains: An overview of the
oauth2
filter chain- DefaultSecurityFilterChain (OAuth2 endpoints)
- RequestMatcher (
OAuth2AuthorizationServerConfigurer
)
Or [
OAuth2ClientuthenticationConfigurer
Or [
Ant [pattern='/oauth2/token', POST],
Ant [pattern='/oauth2/introspect', POST],
Ant [pattern='/oauth2/revoke', POST],
Ant [pattern='/oauth2/device_authorization', POST]
],
OAuth2AuthorizationServerMetadataEndpointConfigurer
Ant [pattern='/.well-known/oauth-authorization-server', GET],
OAuth2AuthorizationEndpointConfigurer
Or [
Ant [pattern='/oauth2/authorize', GET],
Ant [pattern='/oauth2/authorize', POST]
],
OAuth2TokenEndpointConfigurer
Ant [pattern='/oauth2/token', POST],
OAuth2TokenIntrospectionEndpointConfigurer
Ant [pattern='/oauth2/introspect', POST],
OAuth2TokenRevocationEndpointConfigurer
Ant [pattern='/oauth2/revoke', POST],
OAuth2DeviceAuthorizationEndpointConfigurer
Ant [pattern='/oauth2/device_authorization', POST],
OAuth2DeviceVerificationEndpointConfigurer
Or [
Ant [pattern='/oauth2/device_verification', GET],
Ant [pattern='/oauth2/device_verification', POST]
],
OidcConfigurer
Or [
OidcProviderConfigurationEndpointConfigurer
Ant [pattern='/.well-known/openid-configuration', GET],
OidcLogoutEndpointConfigurer
Or [
Ant [pattern='/connect/logout', GET],
Ant [pattern='/connect/logout', POST]
],
OidcUserInfoEndpointConfigurer
Or [
Ant [pattern='/userinfo', GET],
Ant [pattern='/userinfo', POST]]
],
NimbusJwkSetEndpointFilter
Ant [pattern='/oauth2/jwks', GET]
]
- Filters (25)
0 = {DisableEncodeUrlFilter}
1 = {WebAsyncManagerIntegrationFilter}
2 = {SecurityContextHolderFilter}
3 = {AuthorizationServerContextFilter}
4 = {HeaderWriterFilter}
5 = {CsrfFilter}
6 = {OidcLogoutEndpointFilter}
7 = {LogoutFilter}
8 = {OAuth2AuthorizationServerMetadataEndpointFilter}
9 = {OAuth2AuthorizationEndpointFilter}
10 = {OAuth2DeviceVerificationEndpointFilter}
11 = {OidcProviderConfigurationEndpointFilter}
12 = {NimbusJwkSetEndpointFilter}
13 = {OAuth2ClientAuthenticationFilter}
14 = {BearerTokenAuthenticationFilter}
15 = {RequestCacheAwareFilter}
16 = {SecurityContextHolderAwareRequestFilter}
17 = {AnonymousAuthenticationFilter}
18 = {ExceptionTranslationFilter}
19 = {AuthorizationFilter}
20 = {OAuth2TokenEndpointFilter}
21 = {OAuth2TokenIntrospectionEndpointFilter}
22 = {OAuth2TokenRevocationEndpointFilter}
23 = {OAuth2DeviceAuthorizationEndpointFilter}
24 = {OidcUserInfoEndpointFilter}
- DefaultSecurityFilterChain (other endpoints)
- RequestMatcher (
AnyRequestMatcher
) - Filters (14)
- RequestMatcher (
0 = {DisableEncodeUrlFilter@8893}
1 = {WebAsyncManagerIntegrationFilter@8894}
2 = {SecurityContextHolderFilter@8895}
3 = {HeaderWriterFilter@8896}
4 = {CsrfFilter@8897}
5 = {LogoutFilter@8898}
6 = {UsernamePasswordAuthenticationFilter@8899}
7 = {DefaultLoginPageGeneratingFilter@8900}
8 = {DefaultLogoutPageGeneratingFilter@8901}
9 = {RequestCacheAwareFilter@8902}
10 = {SecurityContextHolderAwareRequestFilter@8903}
11 = {AnonymousAuthenticationFilter@8904}
12 = {ExceptionTranslationFilter@8905}
13 = {AuthorizationFilter@8906}
Default Response
Token Endpoint
By default, the token response for the /oauth2/token
endpoint will be:
{
"access_token":"eyJraWQiOiJiOTM0NjIyMy00ZWJiLTQyZjItYTAyYy1hNDlkNDQwOWRlMjEiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjYwNjAiLCJzdWIiOiJzcHJpbmctdGVzdCIsImF1ZCI6InNwcmluZy10ZXN0IiwibmJmIjoxNjk3MTgzODU2LCJleHAiOjE2OTcxODQxNTYsImlhdCI6MTY5NzE4Mzg1Nn0.KzYvm4YAuLRvpF9eco-z1ESbYU-MCChvxbdEPuGgQN-8seco8MgLWWoGM4dbbMRBJLe3Rv3YAEGhJ9qqenNtpFmVnysAUFqw_S8GEUpPlXzzRTnV_qoeqY9YVazCn9TonJJkjzj_RATTHgDx4TD6ZXSP963L5fwNjLtQ2Cp_yoi5R8WDgMkpvOubmuhjAxYpRH7rBH3qzNWo3vqRPuWreeoyaRyK-9HNOTKxT3vj7aLkTK1wyxzfPxliXXXMJ4IsxjxUOTfzzfHF9qmOYZCoCCgVtNlopsSKmjJKRID8UVugzmYQx1pZkUSPMOxiz1AloEX1-6DhgoC3lMi0-Ez6pQ",
"token_type":"Bearer",
"expires_in":299
}
If you parse the access_token using https://jwt.io/, you can view the claims issued in the token. The default set of claims are:
{
"iss": "http://localhost:6060",
"sub": "spring-test",
"aud": "spring-test",
"nbf": 1697183856,
"exp": 1697184156,
"iat": 1697183856
}
Metadata Endpoints
1. http://localhost:6060/.well-known/oauth-authorization-server
2. http://localhost:6060/.well-known/openid-configuration
Metadata endpoints provide the details around the authorization-server and openId current configuration and endpoints exposed.
Code Flow Token response (/oauth2/token
)
{
"access_token":"eyJraWQiOiI4M2ZiMmRhYy1hZGNlLTRkNzgtODlhYy0wOGQzM2U3OGRmNGMiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMSIsImF1ZCI6InNwcmluZy10ZXN0IiwibmJmIjoxNjk4MTUxODMzLCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIl0sImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NjA2MCIsImV4cCI6MTY5ODE1MjEzMywiaWF0IjoxNjk4MTUxODMzfQ.jbNg1MyrL-9kHpfhhkarNfSq1VuS3fPJUZyXjSaliuaziKZzSrma2OyUtVrrPJYzv7FMk-pGrTZVJLZ8f6Jayq2IbHkuWl2XYexRRQmUUDSeC3WMxDhWqezqRc-AEyrTQXm2d0HNs0zdJX9H28bSpGg_SADuKuN-vLuFp3_5w2utveuYxq1e2Ts-IXE-9ulf9O19Mj0Wf9hgENTOZiKbqUWvvoZwXhsx4LzPXqGKM0MbZTS6kFpdSZIgzcbaPzcMX_Vq_B2AU9_UAlJua2Vzxh-9rdJ7SPDVxT-ezoUGp61c1s5eDop2zNszjDqd7kE4qepCiJy6bUuwvP7yewdreg",
"refresh_token":"ARM4_nA8LFzFajbTOzJjN1OTGByZAFu9HGoDeZ9mfciY9vEv5XbWc7MuzcQnAArZMMnB_ydsCxsLRC4HY4u0oh9DscHySysYPXb1BE-7JBwcdH_hVKM3pXWmO4NEiDY",
"scope":"openid profile",
"id_token":"eyJraWQiOiI4M2ZiMmRhYy1hZGNlLTRkNzgtODlhYy0wOGQzM2U3OGRmNGMiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMSIsImF1ZCI6InNwcmluZy10ZXN0IiwiYXpwIjoic3ByaW5nLXRlc3QiLCJhdXRoX3RpbWUiOjE2OTgxNTE4MzMsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NjA2MCIsImV4cCI6MTY5ODE1MzYzMywiaWF0IjoxNjk4MTUxODMzLCJzaWQiOiJfVmYzY1ZTREd1UDQtMEN6czNzR1BxQTZkaUk1ZjB1TE1pT1BkUzd3Z0c4In0.QDHOUa2p8RZKHVuhnHUsvMX-HmEvsGXXQ6QgfidXEMO0vDxJilmYIWW90z9Etc2cJ1SjfFk4OrUZWQF2foa2secatuAeffTbUx_9lTPD4KT_xzg9SsP69tHt55J2U35FcFef2WHuGF06MOj2hr6dVqlk8B5ORV0z_XiBM9FBEmnraLvXWtXtlwp_-jGA95O7y2U8SZt9H8wns-IpatXshB8lnUk-P5NjV8-CUwqtb9FHKOr9ie4KSXHQ8IpY2FaBMI0nA4E_hCUV2xpP_nBAb7Prh5EDYoCFkjHtO5ZXe-VYhyff9AydPzFsdSmEeF6BEK6SeJPBXRUvtL_bZykjdA",
"token_type":"Bearer",
"expires_in":299
}
In the code flow at the end of the flow when code is issued to the client at redirect_uri
, the backend service will collect the code and call the token endpoint /oauth2/token
.
Introspection Response When the Token Is Active
{
"active": true,
"client_id": "spring-test",
"iat": 1698151833,
"exp": 1698155433
}
Introspection Response Post Revoke
{
"active": false,
"iat": 0,
"exp": 0
}
I hope you found this article informative. I will put more details in another post on how to customize the token response by adding new claims to JWT.
Opinions expressed by DZone contributors are their own.
Comments