Skip to content

Commit bb08484

Browse files
yybmionrwinch
authored andcommitted
Allow injecting the principal name into DefaultOAuth2User
- Add username field to DefaultOAuth2User for direct name injection - Add Builder pattern with DefaultOAuth2User.withUsername(String) static factory method - Deprecate constructor that uses nameAttributeKey lookup in favor of Builder pattern - Update Jackson mixins to support username field serialization/deserialization This change prepares for SpEL support in the next commit. Signed-off-by: yybmion <[email protected]>
1 parent 1d2d268 commit bb08484

File tree

5 files changed

+186
-4
lines changed

5 files changed

+186
-4
lines changed

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/DefaultOAuth2UserMixin.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
* This mixin class is used to serialize/deserialize {@link DefaultOAuth2User}.
3333
*
3434
* @author Joe Grandja
35+
* @author YooBin Yoon
3536
* @since 5.3
3637
* @see DefaultOAuth2User
3738
* @see OAuth2ClientJackson2Module
@@ -45,7 +46,7 @@ abstract class DefaultOAuth2UserMixin {
4546
@JsonCreator
4647
DefaultOAuth2UserMixin(@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
4748
@JsonProperty("attributes") Map<String, Object> attributes,
48-
@JsonProperty("nameAttributeKey") String nameAttributeKey) {
49+
@JsonProperty("nameAttributeKey") String nameAttributeKey, @JsonProperty("username") String username) {
4950
}
5051

5152
}

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/DefaultOidcUserMixin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
4141
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
4242
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
43-
@JsonIgnoreProperties(value = { "attributes" }, ignoreUnknown = true)
43+
@JsonIgnoreProperties(value = { "attributes", "username" }, ignoreUnknown = true)
4444
abstract class DefaultOidcUserMixin {
4545

4646
@JsonCreator

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationTokenMixinTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ private static String asJson(DefaultOAuth2User oauth2User) {
194194
" \"@class\": \"java.util.Collections$UnmodifiableMap\",\n" +
195195
" \"username\": \"user\"\n" +
196196
" },\n" +
197-
" \"nameAttributeKey\": \"username\"\n" +
197+
" \"nameAttributeKey\": \"username\",\n" +
198+
" \"username\": \"" + oauth2User.getName() + "\"\n" +
198199
" }";
199200
// @formatter:on
200201
}

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/DefaultOAuth2User.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
* @author Joe Grandja
4545
* @author Eddú Meléndez
4646
* @author Park Hyojong
47+
* @author YooBin Yoon
4748
* @since 5.0
4849
* @see OAuth2User
4950
*/
@@ -57,13 +58,17 @@ public class DefaultOAuth2User implements OAuth2User, Serializable {
5758

5859
private final String nameAttributeKey;
5960

61+
private final String username;
62+
6063
/**
6164
* Constructs a {@code DefaultOAuth2User} using the provided parameters.
6265
* @param authorities the authorities granted to the user
6366
* @param attributes the attributes about the user
6467
* @param nameAttributeKey the key used to access the user's &quot;name&quot; from
6568
* {@link #getAttributes()}
69+
* @deprecated Use {@link #withUsername(String)} builder pattern instead
6670
*/
71+
@Deprecated
6772
public DefaultOAuth2User(Collection<? extends GrantedAuthority> authorities, Map<String, Object> attributes,
6873
String nameAttributeKey) {
6974
Assert.notEmpty(attributes, "attributes cannot be empty");
@@ -76,11 +81,80 @@ public DefaultOAuth2User(Collection<? extends GrantedAuthority> authorities, Map
7681
: Collections.unmodifiableSet(new LinkedHashSet<>(AuthorityUtils.NO_AUTHORITIES));
7782
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
7883
this.nameAttributeKey = nameAttributeKey;
84+
this.username = attributes.get(nameAttributeKey).toString();
85+
}
86+
87+
/**
88+
* Constructs a {@code DefaultOAuth2User} using the provided parameters. This
89+
* constructor is used by Jackson for deserialization.
90+
* @param authorities the authorities granted to the user
91+
* @param attributes the attributes about the user
92+
* @param nameAttributeKey the key used to access the user's &quot;name&quot; from
93+
* {@link #getAttributes()} - preserved for backwards compatibility
94+
* @param username the user's name
95+
*/
96+
private DefaultOAuth2User(Collection<? extends GrantedAuthority> authorities, Map<String, Object> attributes,
97+
String nameAttributeKey, String username) {
98+
Assert.notEmpty(attributes, "attributes cannot be empty");
99+
100+
this.authorities = (authorities != null)
101+
? Collections.unmodifiableSet(new LinkedHashSet<>(this.sortAuthorities(authorities)))
102+
: Collections.unmodifiableSet(new LinkedHashSet<>(AuthorityUtils.NO_AUTHORITIES));
103+
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
104+
this.nameAttributeKey = nameAttributeKey;
105+
this.username = (username != null) ? username : attributes.get(nameAttributeKey).toString();
106+
107+
Assert.hasText(this.username, "username cannot be empty");
108+
}
109+
110+
/**
111+
* Creates a new {@code DefaultOAuth2User} builder with the username.
112+
* @param username the user's name
113+
* @return a new {@code Builder}
114+
* @since 6.5
115+
*/
116+
public static Builder withUsername(String username) {
117+
return new Builder(username);
118+
}
119+
120+
/**
121+
* A builder for {@link DefaultOAuth2User}.
122+
*
123+
* @since 6.5
124+
*/
125+
public static final class Builder {
126+
127+
private final String username;
128+
129+
private Collection<? extends GrantedAuthority> authorities;
130+
131+
private Map<String, Object> attributes;
132+
133+
private Builder(String username) {
134+
Assert.hasText(username, "username cannot be empty");
135+
this.username = username;
136+
}
137+
138+
public Builder authorities(Collection<? extends GrantedAuthority> authorities) {
139+
this.authorities = authorities;
140+
return this;
141+
}
142+
143+
public Builder attributes(Map<String, Object> attributes) {
144+
this.attributes = attributes;
145+
return this;
146+
}
147+
148+
public DefaultOAuth2User build() {
149+
Assert.notEmpty(this.attributes, "attributes cannot be empty");
150+
return new DefaultOAuth2User(this.authorities, this.attributes, null, this.username);
151+
}
152+
79153
}
80154

81155
@Override
82156
public String getName() {
83-
return this.getAttribute(this.nameAttributeKey).toString();
157+
return this.username;
84158
}
85159

86160
@Override

oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/DefaultOAuth2UserTests.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.security.oauth2.core.user;
1818

1919
import java.util.Collections;
20+
import java.util.HashMap;
2021
import java.util.Map;
2122
import java.util.Set;
2223

@@ -35,6 +36,7 @@
3536
* @author Vedran Pavic
3637
* @author Joe Grandja
3738
* @author Park Hyojong
39+
* @author Yoobin Yoon
3840
*/
3941
public class DefaultOAuth2UserTests {
4042

@@ -109,4 +111,108 @@ public void constructorWhenCreatedThenIsSerializable() {
109111
SerializationUtils.serialize(user);
110112
}
111113

114+
@Test
115+
public void withUsernameWhenValidParametersThenCreated() {
116+
String directUsername = "directUser";
117+
DefaultOAuth2User user = DefaultOAuth2User.withUsername(directUsername)
118+
.authorities(AUTHORITIES)
119+
.attributes(ATTRIBUTES)
120+
.build();
121+
122+
assertThat(user.getName()).isEqualTo(directUsername);
123+
assertThat(user.getAuthorities()).hasSize(1);
124+
assertThat(user.getAuthorities().iterator().next()).isEqualTo(AUTHORITY);
125+
assertThat(user.getAttributes()).containsOnlyKeys(ATTRIBUTE_NAME_KEY);
126+
assertThat(user.getAttributes().get(ATTRIBUTE_NAME_KEY)).isEqualTo(USERNAME);
127+
}
128+
129+
@Test
130+
public void withUsernameWhenUsernameIsEmptyThenThrowIllegalArgumentException() {
131+
assertThatIllegalArgumentException().isThrownBy(() -> DefaultOAuth2User.withUsername(""));
132+
}
133+
134+
@Test
135+
public void withUsernameWhenAttributesIsNullThenThrowIllegalArgumentException() {
136+
assertThatIllegalArgumentException().isThrownBy(
137+
() -> DefaultOAuth2User.withUsername("username").authorities(AUTHORITIES).attributes(null).build());
138+
}
139+
140+
@Test
141+
public void withUsernameWhenAttributesIsEmptyThenThrowIllegalArgumentException() {
142+
assertThatIllegalArgumentException().isThrownBy(() -> DefaultOAuth2User.withUsername("username")
143+
.authorities(AUTHORITIES)
144+
.attributes(Collections.emptyMap())
145+
.build());
146+
}
147+
148+
@Test
149+
public void withUsernameWhenCreatedThenIsSerializable() {
150+
DefaultOAuth2User user = DefaultOAuth2User.withUsername("directUser")
151+
.authorities(AUTHORITIES)
152+
.attributes(ATTRIBUTES)
153+
.build();
154+
SerializationUtils.serialize(user);
155+
}
156+
157+
@Test
158+
public void withUsernameWhenUsernameProvidedThenTakesPrecedenceOverAttributes() {
159+
Map<String, Object> attributes = new HashMap<>();
160+
attributes.put("username", "fromAttributes");
161+
attributes.put("id", "123");
162+
163+
DefaultOAuth2User user = DefaultOAuth2User.withUsername("directUsername")
164+
.authorities(AUTHORITIES)
165+
.attributes(attributes)
166+
.build();
167+
168+
assertThat(user.getName()).isEqualTo("directUsername");
169+
assertThat((String) user.getAttribute("username")).isEqualTo("fromAttributes");
170+
}
171+
172+
@Test
173+
public void constructorWhenSimpleAttributeKeyThenWorksAsUsual() {
174+
DefaultOAuth2User user = new DefaultOAuth2User(AUTHORITIES, ATTRIBUTES, ATTRIBUTE_NAME_KEY);
175+
176+
assertThat(user.getName()).isEqualTo(USERNAME);
177+
assertThat(user.getAttributes()).containsOnlyKeys(ATTRIBUTE_NAME_KEY);
178+
}
179+
180+
@Test
181+
public void withUsernameAndDeprecatedConstructorWhenSameDataThenEqual() {
182+
DefaultOAuth2User user1 = new DefaultOAuth2User(AUTHORITIES, ATTRIBUTES, ATTRIBUTE_NAME_KEY);
183+
DefaultOAuth2User user2 = DefaultOAuth2User.withUsername(USERNAME)
184+
.authorities(AUTHORITIES)
185+
.attributes(ATTRIBUTES)
186+
.build();
187+
188+
assertThat(user1.getName()).isEqualTo(user2.getName());
189+
assertThat(user1.getAuthorities()).isEqualTo(user2.getAuthorities());
190+
assertThat(user1.getAttributes()).isEqualTo(user2.getAttributes());
191+
assertThat(user1).isEqualTo(user2);
192+
}
193+
194+
@Test
195+
public void withUsernameWhenAuthoritiesIsNullThenCreatedWithEmptyAuthorities() {
196+
DefaultOAuth2User user = DefaultOAuth2User.withUsername("testUser")
197+
.authorities(null)
198+
.attributes(ATTRIBUTES)
199+
.build();
200+
201+
assertThat(user.getName()).isEqualTo("testUser");
202+
assertThat(user.getAuthorities()).isEmpty();
203+
assertThat(user.getAttributes()).isEqualTo(ATTRIBUTES);
204+
}
205+
206+
@Test
207+
public void withUsernameWhenAuthoritiesIsEmptyThenCreated() {
208+
DefaultOAuth2User user = DefaultOAuth2User.withUsername("testUser")
209+
.authorities(Collections.emptySet())
210+
.attributes(ATTRIBUTES)
211+
.build();
212+
213+
assertThat(user.getName()).isEqualTo("testUser");
214+
assertThat(user.getAuthorities()).isEmpty();
215+
assertThat(user.getAttributes()).isEqualTo(ATTRIBUTES);
216+
}
217+
112218
}

0 commit comments

Comments
 (0)