Spring Authorization Server Documentation

repository·main·Indexed 26 days ago

https://github.com/spring-projects/spring-authorization-server

Documentation for Spring Authorization Server, covering the configuration of OAuth2 and OpenID Connect 1.0. Includes guides on using OAuth2AuthorizationServerConfiguration, managing RegisteredClientRepository, OAuth2AuthorizationService, and OAuth2AuthorizationConsentService, as well as customizing tokens via OAuth2TokenCustomizer and implementing client certificate-bound access tokens.

Tokens
23.8K
Snippets
39
Records
75
Agent score
89%

What's inside Spring Authorization Server

  1. Overview of Spring Authorization Server

    main

    Spring Authorization Server provides support for the OAuth 2.1 Authorization Server specification. It is designed to replace the legacy Spring Security OAuth project and is led by the Spring Security team.

    Important Migration Note: Spring Authorization Server has moved to Spring Security 7.0. The 1.5.x branch is the final generation of the standalone Spring Authorization Server project. Future features will be integrated directly into Spring Security starting with version 7.0.

  2. DPoP-bound Access Tokens (RFC 9449)

    main

    Demonstrating Proof-of-Possession (DPoP) is a mechanism for sender-constraining access tokens. Instead of using bearer tokens (which can be used by anyone who possesses them), DPoP binds an access token to a public key.

    To use DPoP:

    1. The client creates a DPoP Proof (a JWT) and sends it in the DPoP HTTP header during the access token request.
    2. The authorization server binds the access token to the public key from that proof.
    3. When the client uses the token at a resource server, it must include a new DPoP proof in the DPoP header to prove possession of the corresponding private key.
    4. The resource server verifies the proof against the bound public key and the access token hash.
  3. Customize Jwt Client Assertion Validation

    main

    To validate additional claims in a Jwt client assertion, customize the JwtClientAssertionDecoderFactory by providing a custom Function<RegisteredClient, OAuth2TokenValidator<Jwt>> via setJwtValidatorFactory(). This is applied to the JwtClientAssertionAuthenticationProvider.

    @Bean
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    	OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
    		OAuth2AuthorizationServerConfigurer.authorizationServer();
    
    http
    		.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
    		.with(authorizationServerConfigurer, (authorizationServer) ->
    			authorizationServer
    				.clientAuthentication(clientAuthentication ->
    					clientAuthentication
    						.authenticationProviders(configureJwtClientAssertionValidator())
    				)
    		);
    
    return http.build();
    }
    
    private Consumer<List<AuthenticationProvider>> configureJwtClientAssertionValidator() {
    	return (authenticationProviders) ->
    		authenticationProviders.forEach((authenticationProvider) -> {
    			if (authenticationProvider instanceof JwtClientAssertionAuthenticationProvider) {
    				// Customize JwtClientAssertionDecoderFactory
    				JwtClientAssertionDecoderFactory jwtDecoderFactory = new JwtClientAssertionDecoderFactory();
    				Function<RegisteredClient, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = (registeredClient) ->
    					new DelegatingOAuth2TokenValidator<>(
    						// Use default validators
    						JwtClientAssertionDecoderFactory.DEFAULT_JWT_VALIDATOR_FACTORY.apply(registeredClient),
    						// Add custom validator
    						new JwtClaimValidator<>("claim", "value"::equals));
    				jwtDecoderFactory.setJwtValidatorFactory(jwtValidatorFactory);
    
    				((JwtClientAssertionAuthenticationProvider) authenticationProvider)
    						.setJwtDecoderFactory(jwtDecoderFactory);
    				}
    			}
    		});
    }
  4. Enable OpenID Connect 1.0

    main

    OpenID Connect 1.0 is disabled by default. To enable it, initialize the OidcConfigurer within your SecurityFilterChain bean using OAuth2AuthorizationServerConfigurer.authorizationServer().

    @Bean
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    	OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
    		OAuth2AuthorizationServerConfigurer.authorizationServer();
    	http
    		.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
    		.with(authorizationServerConfigurer, (authorizationServer) ->
    			authorizationServer
    				.oidc(Customizer.withDefaults())	// Initialize `OidcConfigurer`
    		);
    	return http.build();
    }
  5. Run the Demo Sample

    main

    The demo sample includes an Authorization Server, a Client, and a Resource Server. You can run them using the following Gradle commands:

    1. Run Authorization Server: ./gradlew -b samples/demo-authorizationserver/samples-demo-authorizationserver.gradle bootRun
    2. Run Client: ./gradlew -b samples/demo-client/samples-demo-client.gradle bootRun
    3. Run Resource Server: ./gradlew -b samples/messages-resource/samples-messages-resource.gradle bootRun

    After starting the services, access the application at http://127.0.0.1:8080.

    Default Login Credentials:

    • Username: user1
    • Password: password
  6. Customize Authorization Request Validation

    main

    By default, OAuth2AuthorizationCodeRequestAuthenticationValidator validates redirect_uri and scope. You can override this validation by providing a custom Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> to the OAuth2AuthorizationCodeRequestAuthenticationProvider via setAuthenticationValidator().

    Important: If validation fails, your custom validator MUST throw an OAuth2AuthorizationCodeRequestAuthenticationException.

    @Bean
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    	OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
    		OAuth2AuthorizationServerConfigurer.authorizationServer();
    
    http
    	.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
    	.with(authorizationServerConfigurer, (authorizationServer) ->
    		authorizationServer
    			.authorizationEndpoint(authorizationEndpoint ->
    				authorizationEndpoint
    					.authenticationProviders(configureAuthenticationValidator())
    			)
    	);
    
    return http.build();
    }
    
    private Consumer<List<AuthenticationProvider>> configureAuthenticationValidator() {
    	return (authenticationProviders) ->
    		authenticationProviders.forEach((authenticationProvider) -> {
    			if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) {
    				Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator =
    						new CustomRedirectUriValidator()
    							.andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_SCOPE_VALIDATOR);
    
    				((OAuth2AuthorizationCodeRequestAuthenticationProvider) authenticationProvider)
    						.setAuthenticationValidator(authenticationValidator);
    				}
    		});
    }
    
    static class CustomRedirectUriValidator implements Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> {
    
    	@Override
    	public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
    		OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
    			authenticationContext.getAuthentication();
    		RegisteredClient registeredClient =
    			authenticationContext.getRegisteredClient();
    		String requestedRedirectUri =
    			authorizationCodeRequestAuthentication.getRedirectUri();
    
    		if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) {
    			OAuth2Error error =
    				new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
    			throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
    		}
    	}
    }
  7. Configure Redis core services in Spring

    main

    To activate the Redis implementation, configure your Spring application with the following steps:

    1. Enable Spring Data Redis repositories under your repository base package.
    2. Configure a Redis connector (e.g., Jedis).
    3. Register custom Converter beans to handle Object-to-Hash conversion before persisting to Redis.
    4. Register your custom repository and service beans:
      • RedisRegisteredClientRepository $\rightarrow$ OAuth2RegisteredClientRepository
      • RedisOAuth2AuthorizationService $\rightarrow$ OAuth2AuthorizationGrantAuthorizationRepository
      • RedisOAuth2AuthorizationConsentService $\rightarrow$ OAuth2UserConsentRepository
  8. Add custom claims to JWT access tokens

    main

    You can add custom claims to an access token by defining an OAuth2TokenCustomizer<JWTEncodingContext> as a @Bean.

    Important Considerations:

    • This @Bean can only be defined once in your configuration.
    • Ensure you check the token type within the customizer to avoid applying logic intended for access tokens to other token types (like ID tokens).
    • To customize ID tokens instead, refer to the User Info Mapper guide.
  9. Configure a Public Client with PKCE

    main

    Single Page Applications (SPAs) are considered public clients because they cannot securely store credentials. To support them, configure the client with the Client Authentication Method set to none and explicitly require Proof Key for Code Exchange (PKCE) to prevent downgrade attacks.

    Important Note: Spring Authorization Server will not issue refresh tokens for public clients. For better security, consider the Backend for Frontend (BFF) pattern instead of exposing a public client.

  10. Install Spring Authorization Server without Spring Boot

    main

    If you are not using Spring Boot, you can add the Spring Authorization Server library directly as a dependency. You must specify the {spring-authorization-server-version}.

    ### Maven
    ```xml
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-authorization-server</artifactId>
        <version>{spring-authorization-server-version}</version>
    </dependency>

    Gradle

    implementation "org.springframework.security:spring-security-oauth2-authorization-server:{spring-authorization-server-version}"