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 {}
@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 { ... }
}
And then annotate your Spring Data repository with @AuthorizeReturnObject
like so:
-
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.
When you annotate a method with @HandleAuthorizationDenied
like so:
-
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 KeyStore
s.
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-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)