What’s New in Spring Security 6.3

Spring Security 6.3 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.

Passive JDK Serialization Support

When it comes to its support for JDK-serialized security components, Spring Security has historically been quite aggressive, supporting each serialization version for only one Spring Security minor version. This meant that if you had JDK-serialized security components, then they would need to be evacuated before upgrading to the next Spring Security version since they would no longer be deserializable.

Now that Spring Security performs a minor release every six months, this became a much larger pain point. To address that, Spring Security now will maintain passivity with JDK serialization, like it does with JSON serialization, making for more seamless upgrades.

Authorization

An ongoing theme for the last several releases has been to refactor and improve Spring Security’s authorization subsystem. Starting with replacing the AccessDecisionManager API with AuthorizationManager it’s now come to the point where we are able to add several exciting new features.

Annotation Parameters - #14480

The first 6.3 feature is support for annotation parameters. Consider Spring Security’s support for meta-annotations like this one:

  • Java

  • Kotlin

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@PreAuthorize("hasAuthority('SCOPE_message:read')")
public @interface HasMessageRead {}
Kotlin
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@PreAuthorize("hasAuthority('SCOPE_message:read')")
annotation class HasMessageRead

Before this release, something like this is only helpful when it is used widely across the codebase. But now, you can add parameters like so:

  • Java

  • Kotlin

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@PreAuthorize("hasAuthority('SCOPE_{scope}')")
public @interface HasScope {
	String scope();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@PreAuthorize("hasAuthority('SCOPE_{scope}')")
annotation class HasScope (val scope:String)

making it possible to do things like this:

  • Java

  • Kotlin

@HasScope("message:read")
public String method() { ... }
@HasScope("message:read")
fun method(): String { ... }

and apply your SpEL expression in several more places.

Secure Return Values - #14596, #14597

Since the early days of Spring Security, you’ve been able to annotate Spring beans with @PreAuthorize and @PostAuthorize. But controllers, services, and repositories are not the only things you care to secure. For example, what about a domain object Order where only admins should be able to call the Order#getPayment method?

Now in 6.3, you can annotate those methods, too. First, annotate the getPayment method like you would a Spring bean:

  • Java

  • Kotlin

public class Order {

	@HasScope("payment:read")
	Payment getPayment() { ... }

}
class Order {

	@HasScope("payment:read")
	fun getPayment(): Payment { ... }

}
  • Java

  • Kotlin

public interface OrderRepository implements CrudRepository<Order, String> {

	@AuthorizeReturnObject
	Optional<Order> findOrderById(String id);

}
interface OrderRepository : CrudRepository<Order, String> {
    @AuthorizeReturnObject
    fun findOrderById(id: String?): Optional<Order?>?
}

At that point, Spring Security will protect any Order returned from findOrderById by way of proxying the Order instance.

Error Handling - #14598, #14600, #14601

In this release, you can also intercept and handle failure at the method level with its last new method security annotation.

  • Java

  • Kotlin

public class Payment {
    @HandleAuthorizationDenied(handlerClass=Mask.class)
    @PreAuthorize("hasAuthority('card:read')")
    public String getCreditCardNumber() { ... }
}
class Payment {
    @HandleAuthorizationDenied(handlerClass=Mask.class)
    @PreAuthorize("hasAuthority('card:read')")
    fun getCreditCardNumber(): String { ... }
}

and publish a Mask bean:

  • Java

  • Kotlin

@Component
public class Mask implements MethodAuthorizationDeniedHandler {
	@Override
    public Object handleDeniedInvocation(MethodInvocation invocation, AuthorizationResult result) {
		return "***";
    }
}
@Component
class Mask : MethodAuthorizationDeniedHandler {
    fun handleDeniedInvocation(invocation: MethodInvocation?, result: AuthorizationResult?): Any = "***"
}

then any unauthorized call to Payment#getCreditCardNumber will return *** instead of the number.

You can see all these features at work together in the latest Spring Security Data sample.

Compromised Password Checking - #7395

If you are going to let users pick passwords, it’s critical to ensure that such a password isn’t already compromised. Spring Security 6.3 makes this as simple as publishing a CompromisedPasswordChecker bean:

  • Java

  • Kotlin

@Bean
public CompromisedPasswordChecker compromisedPasswordChecker() {
    return new HaveIBeenPwnedRestApiPasswordChecker();
}
@Bean
fun compromisedPasswordChecker(): CompromisedPasswordChecker = HaveIBeenPwnedRestApiPasswordChecker()

spring-security-rsa is now part of Spring Security - #14202

Since 2017, Spring Security has been undergoing a long-standing initiative to fold various Spring Security extensions into Spring Security proper. In 6.3, spring-security-rsa becomes the latest of these projects which will help the team maintain and add features to it, long-term.

spring-security-rsa provides a number of handy BytesEncryptor implementations as well as a simpler API for working with KeyStores.

OAuth 2.0 Token Exchange Grant - #5199

One of the most highly-voted OAuth 2.0 features in Spring Security is now in place in 6.3, which is the support for the OAuth 2.0 Token Exchange grant.

For any client configured for token exchange, you can activate it in Spring Security by adding a TokenExchangeAuthorizedClientProvider instance to your OAuth2AuthorizedClientManager like so:

  • Java

  • Kotlin

@Bean
public OAuth2AuthorizedClientProvider tokenExchange() {
	return new TokenExchangeOAuth2AuthorizedClientProvider();
}
@Bean
fun tokenExchange(): OAuth2AuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()

and then use the @RegisteredOAuth2AuthorizedClient annotation as per usual to retrieve the appropriate token with the expanded privileges your resource server needs.

Additional Highlights

  • gh-14655 - Add DelegatingAuthenticationConverter

  • gh-6192 - Add Concurrent Sessions Control on WebFlux (docs)

  • gh-14193 - Added support for CAS Gateway Authentication

  • gh-13259 - Customize when UserInfo is called

  • gh-14168 - Introduce Customizable AuthorizationFailureHandler in OAuth2AuthorizationRequestRedirectFilter

  • gh-14672 - Customize mapping the OidcUser from OidcUserRequest and OidcUserInfo

  • gh-13763 - Simplify configuration of reactive OAuth2 Client component model

  • gh-14758 - Update reactive OAuth2 docs landing page with examples (docs)

  • gh-10538 - Support Certificate-Bound JWT Access Token Validation

  • gh-14265 - Support Nested username in UserInfo response

  • gh-14449 - Add SecurityContext argument resolver

  • gh-11440 - Simplify Disabling application/x-www-form-urlencoded Encoding Client ID and Secret (servlet docs, reactive docs)

And for an exhaustive list, please see the release notes for 6.3.0-RC1, 6.3.0-M3, 6.3.0-M2, and 6.3.0-M1.