diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverter.java index 05e2f93a13..c80fce5797 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/ReactiveJwtAuthenticationConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.util.Assert; /** @@ -30,6 +31,7 @@ import org.springframework.util.Assert; * a {@link AbstractAuthenticationToken Mono<AbstractAuthenticationToken>}. * * @author Eric Deandrea + * @author Marcus Kainth * @since 5.2 */ public final class ReactiveJwtAuthenticationConverter implements Converter> { @@ -37,12 +39,17 @@ public final class ReactiveJwtAuthenticationConverter implements Converter> jwtGrantedAuthoritiesConverter = new ReactiveJwtGrantedAuthoritiesConverterAdapter( new JwtGrantedAuthoritiesConverter()); + private String principalClaimName = JwtClaimNames.SUB; + @Override public Mono convert(Jwt jwt) { // @formatter:off return this.jwtGrantedAuthoritiesConverter.convert(jwt) .collectList() - .map((authorities) -> new JwtAuthenticationToken(jwt, authorities)); + .map((authorities) -> { + String principalName = jwt.getClaimAsString(this.principalClaimName); + return new JwtAuthenticationToken(jwt, authorities, principalName); + }); // @formatter:on } @@ -58,4 +65,14 @@ public final class ReactiveJwtAuthenticationConverter implements Converter this.jwtAuthenticationConverter.setPrincipalClaimName(null)) + .withMessage("principalClaimName cannot be empty"); + // @formatter:on + } + + @Test + public void whenSettingEmptyPrincipalClaimName() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.jwtAuthenticationConverter.setPrincipalClaimName("")) + .withMessage("principalClaimName cannot be empty"); + // @formatter:on + } + + @Test + public void whenSettingBlankPrincipalClaimName() { + // @formatter:off + assertThatIllegalArgumentException() + .isThrownBy(() -> this.jwtAuthenticationConverter.setPrincipalClaimName(" ")) + .withMessage("principalClaimName cannot be empty"); + // @formatter:on + } + + @Test + public void convertWhenPrincipalClaimNameSet() { + this.jwtAuthenticationConverter.setPrincipalClaimName("user_id"); + Jwt jwt = TestJwts.jwt().claim("user_id", "100").build(); + AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block(); + assertThat(authentication.getName()).isEqualTo("100"); + } + + @Test + public void convertWhenPrincipalClaimNameSetAndClaimValueIsNotString() { + this.jwtAuthenticationConverter.setPrincipalClaimName("user_id"); + Jwt jwt = TestJwts.jwt().claim("user_id", 100).build(); + AbstractAuthenticationToken authentication = this.jwtAuthenticationConverter.convert(jwt).block(); + assertThat(authentication.getName()).isEqualTo("100"); + } + }