From 720f99ce748a32f8dea463406c0cceb60e5bd66a 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 | 25 +++++++++++++++++-- .../JwtAuthenticationToken.java | 18 +++++++++++++ ...JwtBearerTokenAuthenticationConverter.java | 14 +++++++++++ 3 files changed, 55 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 516078c67d..c9d1d63827 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 @@ -21,6 +21,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; +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; @@ -30,10 +31,12 @@ * @author Josh Cummings * @author Evgeniy Cheban * @author Olivier Antoine + * @author Andrey Litvitski * @since 5.1 */ public class JwtAuthenticationConverter implements Converter { + private Converter jwtPrincipalConverter; private Converter> jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); private String principalClaimName = JwtClaimNames.SUB; @@ -42,8 +45,26 @@ public class JwtAuthenticationConverter implements Converter authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); - 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 7c5b610454..9f3faa5174 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 @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.Map; +import org.springframework.security.core.AuthenticatedPrincipal; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.security.core.Transient; @@ -29,6 +30,7 @@ * {@link Jwt} {@code Authentication}. * * @author Joe Grandja + * @author Andrey Litvitski * @since 5.1 * @see AbstractOAuth2TokenAuthenticationToken * @see Jwt @@ -72,6 +74,22 @@ public JwtAuthenticationToken(Jwt jwt, Collection au this.name = 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.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 @@ * {@link BearerTokenAuthentication}. * * @author Josh Cummings + * @author Andrey Litvitski * @since 5.2 */ public final class JwtBearerTokenAuthenticationConverter implements Converter { @@ -58,4 +60,16 @@ public AbstractAuthenticationToken convert(Jwt jwt) { return new BearerTokenAuthentication(principal, accessToken, 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.jwtAuthenticationConverter.setJwtPrincipalConverter(jwtPrincipalConverter); + } + }