What’s New in Spring Security 6.4

Spring Security 6.4 provides a number of new features. Below are the highlights of the release, or you can view the release notes for a detailed listing of each feature and bug fix.

Deprecation Notices

As we get closer to Spring Security 7, it’s important to stay up to date on deprecations. As such, this section points out deprecations in the 6.4 release.

  • Method Security - AuthorizationManager#check is deprecated in favor of AuthorizationManager#authorize. This is primarily to allow the return type to be an interface instead of a concrete class. If you are invoking AuthorizationManager#check, please invoke AuthorizationManager#authorize instead.

    Relatedly, AuthorizationEventPublisher#publishEvent that takes an AuthorizationDecision is deprecated in favor of a method of the same name that takes an AuthorizationResult interface instead.

  • Method Security - PrePostTemplateDefaults is deprecated in favor of the more generic AnnotationTemplateExpressionDefaults as there is now meta-annotation property support for @AuthenticationPrincipal and @CurrentSecurityContext as well. If you are constructing a PrePostTemplateDefaults, change this out for an AnnotationTemplateExpressionDefaults.

  • OAuth 2.0 - NimbusOpaqueTokenIntrospector has been deprecated in favor of SpringOpaqueTokenIntrospector in order to remove Spring Security OAuth 2.0 Resource Server’s reliance on the oidc-oauth2-sdk package. If you are constructing a NimbusOpaqueTokenIntrospector, replace it with SpringOpaqueTokenIntrospector's constructor

  • OAuth 2.0 - DefaultAuthorizationCodeTokenResponseClient, DefaultClientCredentialsTokenResponseClient, DefaultJwtBearerTokenResponseClient, DefaultPasswordTokenResponseClient, DefaultRefreshTokenTokenResponseClient, and DefaultTokenExchangeTokenResponseClient are deprecated in favor of their RestClient equivalents.

    Relatedly,JwtBearerGrantRequestEntityConverter, OAuth2AuthorizationCodeGrantRequestEntityConverter, OAuth2ClientCredentialsGrantRequestEntityConverter, OAuth2PasswordGrantRequestEntityConverter, OAuth2RefreshTokenGrantRequestEntityConverter are deprecated in favor of providing an instance of DefaultOAuth2TokenRequestParametersConverter to one of the above token response clients

    For example, if you have the following arrangement:

    private static class MyCustomConverter
        extends AbstractOAuth2AuthorizationGrantRequestEntityConverter<OAuth2AuthorizationCodeGrantRequest> {
    	@Override
        protected MultiValueMap<String, String> createParameters
                (OAuth2AuthorizationCodeGrantRequest request) {
    		MultiValueMap<String, String> parameters = super.createParameters(request);
    		parameters.add("custom", "value");
    		return parameters;
        }
    }
    
    @Bean
    OAuth2AccessTokenResponseClient authorizationCode() {
    	DefaultAuthorizationCodeTokenResponseClient client =
            new DefaultAuthorizationCodeTokenResponseClient();
    	Converter<AuthorizationCodeGrantRequest, RequestEntity<?>> entityConverter =
            new OAuth2AuthorizationCodeGrantRequestEntityConverter();
    	entityConverter.setParametersConverter(new MyCustomConverter());
    	client.setRequestEntityConverter(entityConverter);
        return client;
    }

    This configuration is deprecated since it uses DefaultAuthorizationCodeTokenResponseClient and OAuth2AuthorizationCodeGrantRequestEntityConverter. The recommended configuration is now:

    private static class MyCustomConverter implements Converter<OAuth2AuthorizationCodeGrantRequest, Map<String, String>> {
    	@Override
        public MultiValueMap<String, String> convert(OAuth2AuthorizeCodeGrantRequest request) {
    		MultiValueMap<String, String> parameters = OAuth2AuthorizationCodeGrantRequest.defaultParameters(request);
    		parameters.add("custom", "value");
    		return parameters;
        }
    }
    
    @Bean
    OAuth2AccessTokenResponseClient authorizationCode() {
    	RestClientAuthorizationCodeTokenResponseClient client =
            new RestClientAuthorizationCodeTokenResponseClient();
    	client.setParametersConverter(new MyCustomConverter());
        return client;
    }
  • SAML 2.0 - Unversioned OpenSAML implementations of Spring Security SAML 2.0 Service Provider’s interfaces have been deprecated in favor of versioned ones. For example, OpenSamlAuthenticationTokenConverter is now replaced by OpenSaml4AuthenticationTokenConverter and OpenSaml5AuthenticationTokenConverter. If you are constructing one of these deprecated versions, please replace it with the one that corresponds to the OpenSAML version you are using.

  • SAML 2.0 - Methods surrounding AssertingPartyDetails are deprecated in favor of equivalent methods that use the AssertingPartyMetadata interface.

  • LDAP - Usages of DistinguishedName are now deprecated in order to align with Spring LDAP’s deprecations

One-Time Token Login

Passkeys

Spring Security now has Passkeys support.

Method Security

  • All method security annotations now support Framework’s @AliasFor

  • @AuthenticationPrincipal and @CurrentSecurityContext now support annotation templates.

    This means that you can now use Spring’s meta-annotation support like so:

    • Java

    • Kotlin

    @Target(TargetType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @AuthenticationPrincipal("claims['{claim}']")
    @interface CurrentUsername {
    	String claim() default "sub";
    }
    
    // ...
    
    @GetMapping
    public String method(@CurrentUsername("username") String username) {
    	// ...
    }
    annotation CurrentUsername(val claim: String = "sub")
    
    // ...
    
    @GetMapping
    fun method(@CurrentUsername("username") val username: String): String {
    	// ...
    }
  • Several improvements were made to align Security’s annotation search with AbstractFallbackMethodSecurityMetadataSource's algorithm. This aids in migration from earlier versions of Spring Security.

  • Native applications can now use @AuthorizeReturnObject

  • Native applications can now reference beans in @PreAuthorize and @PostAuthorize

  • SecurityAnnotationScanners offers a convenient API for scanning for Security annotations and for adding Security’s selection and templating features to custom annotations

OAuth 2.0

  • oauth2Login() now accepts OAuth2AuthorizationRequestResolver as a @Bean

  • ClientRegistrations now supports externally obtained configuration

  • Added loginPage() to DSL in reactive oauth2Login()

  • OIDC Back-Channel support now accepts logout tokens of type logout+jwt

  • RestClient can now be configured with OAuth2ClientHttpRequestInterceptor to make protected resources requests

  • Added RestClient-based implementations of OAuth2AccessTokenResponseClient for more consistent configuration of access token requests.

    To opt-in to using RestClient support, simply publish a bean for each grant type as in the following example:

    • Java

    • Kotlin

    @Configuration
    public class SecurityConfig {
    
    	@Bean
    	public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
    		return new RestClientAuthorizationCodeTokenResponseClient();
    	}
    
    	@Bean
    	public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
    		return new RestClientRefreshTokenTokenResponseClient();
    	}
    
    	@Bean
    	public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
    		return new RestClientClientCredentialsTokenResponseClient();
    	}
    
    	@Bean
    	public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
    		return new RestClientJwtBearerTokenResponseClient();
    	}
    
    	@Bean
    	public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
    		return new RestClientTokenExchangeTokenResponseClient();
    	}
    
    }
    @Configuration
    class SecurityConfig {
    
    	@Bean
    	fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
    		return RestClientAuthorizationCodeTokenResponseClient()
    	}
    
    	@Bean
    	fun refreshTokenAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
    		return RestClientRefreshTokenTokenResponseClient()
    	}
    
    	@Bean
    	fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
    		return RestClientClientCredentialsTokenResponseClient()
    	}
    
    	@Bean
    	fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
    		return RestClientJwtBearerTokenResponseClient()
    	}
    
    	@Bean
    	fun tokenExchangeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
    		return RestClientTokenExchangeTokenResponseClient()
    	}
    
    }
  • Token Exchange now supports refresh tokens

SAML 2.0

  • Added OpenSAML 5 Support. Now you can use either OpenSAML 4 or OpenSAML 5; by default, Spring Security will select the write implementations based on what’s on your classpath.

  • Using EntityIDs for the registrationId is simplified.

    A common pattern is to identify asserting parties by their entityID. In previous versions, this required directly configuring OpenSamlAuthenticationRequestResolver. Now, the request resolver looks by default for the registrationId as a request parameter in addition to looking for it in the path. This allows you to use RelyingPartyRegistrations or OpenSaml4/5AssertingPartyMetadataRepository without also needing to modify the registrationId values or customize the request resolver.

    Relatedly, you can now configure your authenticationRequestUri to contain a query parameter

  • Asserting Parties can now be refreshed in the background according to the metadata’s expiry.

    For example, you can now use OpenSaml5AssertingPartyMetadataRepository to do:

    • Java

    • Kotlin

    @Component
    public class RefreshableRelyingPartyRegistrationRepository implements IterableRelyingPartyRegistrationRepository {
    	private final AssertingPartyMetadataRepository assertingParties = OpenSaml5AssertingPartyMetadataRepository
    		.fromTrustedMetadataLocation("https://idp.example.org").build();
    
    	@Override
    	public RelyingPartyRegistration findByRegistrationId(String registrationId) {
    		AssertingPartyMetadata assertingParty = this.assertingParties.findByEntityId(registrationId);
    		return RelyingPartyRegistration.withAssertingPartyMetadata(assertingParty)
    			// relying party configurations
    			.build();
    	}
    
    	// ...
    }
    @Component
    open class RefreshableRelyingPartyRegistrationRepository: IterableRelyingPartyRegistrationRepository {
    	private val assertingParties: AssertingPartyMetadataRepository = OpenSaml5AssertingPartyMetadataRepository
    		.fromTrustedMetadataLocation("https://idp.example.org").build()
    
    	override fun findByRegistrationId(String registrationId): RelyingPartyRegistration {
    		val assertingParty = this.assertingParties.findByEntityId(registrationId)
    		return RelyingPartyRegistration.withAssertingPartyMetadata(assertingParty)
    			// relying party configurations
    			.build()
    	}
    
    	// ...
    }

    This implementation also supports the validation of a metadata’s signature.

  • You can now sign relying party metadata

  • RelyingPartyRegistrationRepository results can now be cached. This is helpful if you want to defer the loading of the registration values til after application startup. It is also helpful if you want to control when metadata gets refreshed via Spring Cache.

  • To align with the SAML 2.0 standard, the metadata endpoint now uses the application/samlmetadata+xml MIME type

Web

  • CSRF BREACH tokens are now more consistent

  • The Remember Me cookie now is more customizable

  • Security Filter Chain finds more invalid configurations. For example, a filter chain declared after an any-request filter chain is invalid since it will never be invoked:

    • Java

    • Kotlin

    @Bean
    @Order(0)
    SecurityFilterChain api(HttpSecurity http) throws Exception {
        http
            // implicit securityMatcher("/**")
            .authorizeHttpRequests(...)
            .httpBasic(...)
    
        return http.build();
    }
    
    @Bean
    @Order(1)
    SecurityFilterChain app(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/app/**")
            .authorizeHttpRequests(...)
            .formLogin(...)
    
        return http.build();
    }
    @Bean
    @Order(0)
    fun api(val http: HttpSecurity): SecurityFilterChain {
        http {
    		authorizeHttpRequests {
    			// ...
    		}
    	}
        return http.build()
    }
    
    @Bean
    @Order(1)
    fun app(val http: HttpSecurity): SecurityFilterChain {
        http {
    		securityMatcher("/app/**")
    		authorizeHttpRequests {
    			// ...
    		}
    	}
        return http.build()
    }

    You can read more in the related ticket.

  • ServerHttpSecurity now picks up ServerWebExchangeFirewall as a @Bean

Observability

Observability now supports toggling authorization, authentication, and request observations separately For example, to turn off filter chain observations, you can publish a @Bean like this one:

  • Java

  • Kotlin

@Bean
SecurityObservationSettings allSpringSecurityObservations() {
	return SecurityObservationSettings.withDefaults()
            .shouldObserveFilterChains(false).build();
}
@Bean
fun allSpringSecurityObservations(): SecurityObservationSettings {
    return SecurityObservationSettings.builder()
            .shouldObserveFilterChains(false).build()
}

Kotlin

Acl