Skip to content

Commit 0dc9709

Browse files
committed
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 455a2ec commit 0dc9709

File tree

5 files changed

+190
-8
lines changed

5 files changed

+190
-8
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -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: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@
4545
* @author Joe Grandja
4646
* @author Eddú Meléndez
4747
* @author Park Hyojong
48+
* @author YooBin Yoon
4849
* @since 5.0
4950
* @see OAuth2User
5051
*/
@@ -58,13 +59,17 @@ public class DefaultOAuth2User implements OAuth2User, Serializable {
5859

5960
private final String nameAttributeKey;
6061

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

82156
@Override
83157
public String getName() {
84-
return this.getAttribute(this.nameAttributeKey).toString();
158+
return this.username;
85159
}
86160

87161
@Override

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

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -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)