From aabc9fc1ccafd1c98b1af0805fb3ff38a2757c5c Mon Sep 17 00:00:00 2001 From: Andrey Litvitski Date: Sun, 1 Jun 2025 17:48:10 +0300 Subject: [PATCH] Support Custom Principal in Jwt Authentication Flow Closes gh-6237 Signed-off-by: Andrey Litvitski --- .../JwtAuthenticationConverter.java | 26 +++++++++++++++++-- .../JwtAuthenticationToken.java | 18 +++++++++++++ ...JwtBearerTokenAuthenticationConverter.java | 14 ++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java index 90dd2414c4..29a00ce1ed 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java @@ -23,6 +23,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.FactorGrantedAuthority; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.util.Assert; @@ -32,12 +33,14 @@ import org.springframework.util.Assert; * @author Josh Cummings * @author Evgeniy Cheban * @author Olivier Antoine + * @author Andrey Litvitski * @since 5.1 */ public class JwtAuthenticationConverter implements Converter { private static final String AUTHORITY = FactorGrantedAuthority.BEARER_AUTHORITY; + private Converter jwtPrincipalConverter; private Converter> jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); private String principalClaimName = JwtClaimNames.SUB; @@ -46,8 +49,27 @@ public class JwtAuthenticationConverter implements Converter authorities = new HashSet<>(this.jwtGrantedAuthoritiesConverter.convert(jwt)); authorities.add(FactorGrantedAuthority.fromAuthority(AUTHORITY)); - String principalClaimValue = jwt.getClaimAsString(this.principalClaimName); - return new JwtAuthenticationToken(jwt, authorities, principalClaimValue); + + if (this.jwtPrincipalConverter == null) { + String principalClaimValue = jwt.getClaimAsString(this.principalClaimName); + return new JwtAuthenticationToken(jwt, authorities, principalClaimValue); + } else { + OAuth2AuthenticatedPrincipal principal = this.jwtPrincipalConverter.convert(jwt); + authorities.addAll(principal.getAuthorities()); + return new JwtAuthenticationToken(jwt, principal, authorities); + } + } + + /** + * Sets the {@link Converter Converter<Jwt, Collection<OAuth2AuthenticatedPrincipal>>} + * to use. + * @param jwtPrincipalConverter The converter + * @since 6.5.0 + */ + public void setJwtPrincipalConverter( + Converter jwtPrincipalConverter) { + Assert.notNull(jwtPrincipalConverter, "jwtPrincipalConverter cannot be null"); + this.jwtPrincipalConverter = jwtPrincipalConverter; } /** diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationToken.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationToken.java index 7e52b30e00..65204c4065 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationToken.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationToken.java @@ -22,6 +22,7 @@ import java.util.Map; import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticatedPrincipal; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.Transient; import org.springframework.security.oauth2.jwt.Jwt; @@ -32,6 +33,7 @@ import org.springframework.util.Assert; * {@link Jwt} {@code Authentication}. * * @author Joe Grandja + * @author Andrey Litvitski * @since 5.1 * @see AbstractOAuth2TokenAuthenticationToken * @see Jwt @@ -80,6 +82,22 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok this.name = builder.name; } + /** + * Constructs a {@code JwtAuthenticationToken} using the provided parameters. + * @param jwt the JWT + * @param principal the principal + * @param authorities the authorities assigned to the JWT + */ + public JwtAuthenticationToken(Jwt jwt, Object principal, Collection authorities) { + super(jwt, principal, jwt, authorities); + this.setAuthenticated(true); + if (principal instanceof AuthenticatedPrincipal) { + this.name = ((AuthenticatedPrincipal) principal).getName(); + } else { + this.name = jwt.getSubject(); + } + } + @Override public Map getTokenAttributes() { return this.getToken().getClaims(); diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtBearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtBearerTokenAuthenticationConverter.java index 4070823173..e657f532ed 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtBearerTokenAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtBearerTokenAuthenticationConverter.java @@ -26,6 +26,7 @@ import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrinci import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.util.Assert; /** * A {@link Converter} that takes a {@link Jwt} and converts it into a @@ -41,6 +42,7 @@ import org.springframework.security.oauth2.jwt.Jwt; * {@link BearerTokenAuthentication}. * * @author Josh Cummings + * @author Andrey Litvitski * @since 5.2 */ public final class JwtBearerTokenAuthenticationConverter implements Converter { @@ -58,4 +60,16 @@ public final class JwtBearerTokenAuthenticationConverter implements Converter jwtPrincipalConverter) { + Assert.notNull(jwtPrincipalConverter, "jwtPrincipalConverter cannot be null"); + this.jwtAuthenticationConverter.setJwtPrincipalConverter(jwtPrincipalConverter); + } + }