Skip to content

Commit db5310b

Browse files
jgrandjarwinch
authored andcommitted
Enable null-safety in spring-security-oauth2-core
Closes gh-17820
1 parent dfed528 commit db5310b

52 files changed

Lines changed: 735 additions & 391 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

oauth2/oauth2-core/spring-security-oauth2-core.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
id 'compile-warnings-error'
3+
id 'security-nullability'
34
}
45

56
apply plugin: 'io.spring.convention.spring-module'

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AbstractOAuth2Token.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
import java.io.Serializable;
2020
import java.time.Instant;
2121

22-
import org.springframework.lang.Nullable;
22+
import org.jspecify.annotations.Nullable;
23+
2324
import org.springframework.util.Assert;
2425

2526
/**
@@ -37,9 +38,9 @@ public abstract class AbstractOAuth2Token implements OAuth2Token, Serializable {
3738

3839
private final String tokenValue;
3940

40-
private final Instant issuedAt;
41+
private final @Nullable Instant issuedAt;
4142

42-
private final Instant expiresAt;
43+
private final @Nullable Instant expiresAt;
4344

4445
/**
4546
* Sub-class constructor.
@@ -78,17 +79,15 @@ public String getTokenValue() {
7879
* Returns the time at which the token was issued.
7980
* @return the time the token was issued or {@code null}
8081
*/
81-
@Nullable
82-
public Instant getIssuedAt() {
82+
public @Nullable Instant getIssuedAt() {
8383
return this.issuedAt;
8484
}
8585

8686
/**
8787
* Returns the expiration time on or after which the token MUST NOT be accepted.
8888
* @return the token expiration time or {@code null}
8989
*/
90-
@Nullable
91-
public Instant getExpiresAt() {
90+
public @Nullable Instant getExpiresAt() {
9291
return this.expiresAt;
9392
}
9493

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ClaimAccessor.java

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.util.List;
2222
import java.util.Map;
2323

24+
import org.jspecify.annotations.Nullable;
25+
2426
import org.springframework.core.convert.TypeDescriptor;
2527
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
2628
import org.springframework.util.Assert;
@@ -44,11 +46,11 @@ public interface ClaimAccessor {
4446
* type {@code T}.
4547
* @param claim the name of the claim
4648
* @param <T> the type of the claim value
47-
* @return the claim value
49+
* @return the claim value, or {@code null} if the claim does not exist
4850
* @since 5.2
4951
*/
5052
@SuppressWarnings("unchecked")
51-
default <T> T getClaim(String claim) {
53+
default <T> @Nullable T getClaim(String claim) {
5254
return !hasClaim(claim) ? null : (T) getClaims().get(claim);
5355
}
5456

@@ -71,7 +73,7 @@ default boolean hasClaim(String claim) {
7173
* @return the claim value or {@code null} if it does not exist or is equal to
7274
* {@code null}
7375
*/
74-
default String getClaimAsString(String claim) {
76+
default @Nullable String getClaimAsString(String claim) {
7577
return !hasClaim(claim) ? null
7678
: ClaimConversionService.getSharedInstance().convert(getClaims().get(claim), String.class);
7779
}
@@ -85,7 +87,8 @@ default String getClaimAsString(String claim) {
8587
* {@code Boolean}
8688
* @throws NullPointerException if the claim value is {@code null}
8789
*/
88-
default Boolean getClaimAsBoolean(String claim) {
90+
@SuppressWarnings("NullAway")
91+
default @Nullable Boolean getClaimAsBoolean(String claim) {
8992
if (!hasClaim(claim)) {
9093
return null;
9194
}
@@ -100,8 +103,12 @@ default Boolean getClaimAsBoolean(String claim) {
100103
* Returns the claim value as an {@code Instant} or {@code null} if it does not exist.
101104
* @param claim the name of the claim
102105
* @return the claim value or {@code null} if it does not exist
106+
* @throws IllegalArgumentException if the claim value cannot be converted to an
107+
* {@code Instant}
108+
* @throws NullPointerException if the claim value is {@code null}
103109
*/
104-
default Instant getClaimAsInstant(String claim) {
110+
@SuppressWarnings("NullAway")
111+
default @Nullable Instant getClaimAsInstant(String claim) {
105112
if (!hasClaim(claim)) {
106113
return null;
107114
}
@@ -116,8 +123,12 @@ default Instant getClaimAsInstant(String claim) {
116123
* Returns the claim value as an {@code URL} or {@code null} if it does not exist.
117124
* @param claim the name of the claim
118125
* @return the claim value or {@code null} if it does not exist
126+
* @throws IllegalArgumentException if the claim value cannot be converted to a
127+
* {@code URL}
128+
* @throws NullPointerException if the claim value is {@code null}
119129
*/
120-
default URL getClaimAsURL(String claim) {
130+
@SuppressWarnings("NullAway")
131+
default @Nullable URL getClaimAsURL(String claim) {
121132
if (!hasClaim(claim)) {
122133
return null;
123134
}
@@ -137,8 +148,8 @@ default URL getClaimAsURL(String claim) {
137148
* {@code Map}
138149
* @throws NullPointerException if the claim value is {@code null}
139150
*/
140-
@SuppressWarnings("unchecked")
141-
default Map<String, Object> getClaimAsMap(String claim) {
151+
@SuppressWarnings({ "unchecked", "NullAway" })
152+
default @Nullable Map<String, Object> getClaimAsMap(String claim) {
142153
if (!hasClaim(claim)) {
143154
return null;
144155
}
@@ -162,8 +173,8 @@ default Map<String, Object> getClaimAsMap(String claim) {
162173
* {@code List}
163174
* @throws NullPointerException if the claim value is {@code null}
164175
*/
165-
@SuppressWarnings("unchecked")
166-
default List<String> getClaimAsStringList(String claim) {
176+
@SuppressWarnings({ "unchecked", "NullAway" })
177+
default @Nullable List<String> getClaimAsStringList(String claim) {
167178
if (!hasClaim(claim)) {
168179
return null;
169180
}

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ClientAuthenticationMethod.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.io.Serializable;
2020

21-
import org.springframework.lang.NonNull;
2221
import org.springframework.util.Assert;
2322

2423
/**
@@ -105,7 +104,6 @@ static ClientAuthenticationMethod[] methods() {
105104
* constant, if any
106105
* @since 6.5
107106
*/
108-
@NonNull
109107
public static ClientAuthenticationMethod valueOf(String method) {
110108
for (ClientAuthenticationMethod m : methods()) {
111109
if (m.getValue().equals(method)) {

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/DefaultOAuth2AuthenticatedPrincipal.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.Collections;
2323
import java.util.Map;
2424

25+
import org.jspecify.annotations.Nullable;
26+
2527
import org.springframework.security.core.GrantedAuthority;
2628
import org.springframework.security.core.authority.AuthorityUtils;
2729
import org.springframework.util.Assert;
@@ -58,17 +60,21 @@ public DefaultOAuth2AuthenticatedPrincipal(Map<String, Object> attributes,
5860
/**
5961
* Constructs an {@code DefaultOAuth2AuthenticatedPrincipal} using the provided
6062
* parameters.
61-
* @param name the name attached to the OAuth 2.0 token
63+
* @param name the name attached to the OAuth 2.0 token, may be {@code null}
6264
* @param attributes the attributes of the OAuth 2.0 token
63-
* @param authorities the authorities of the OAuth 2.0 token
65+
* @param authorities the authorities of the OAuth 2.0 token, may be {@code null}
6466
*/
65-
public DefaultOAuth2AuthenticatedPrincipal(String name, Map<String, Object> attributes,
66-
Collection<GrantedAuthority> authorities) {
67+
public DefaultOAuth2AuthenticatedPrincipal(@Nullable String name, Map<String, Object> attributes,
68+
@Nullable Collection<GrantedAuthority> authorities) {
6769
Assert.notEmpty(attributes, "attributes cannot be empty");
6870
this.attributes = Collections.unmodifiableMap(attributes);
6971
this.authorities = (authorities != null) ? Collections.unmodifiableCollection(authorities)
7072
: AuthorityUtils.NO_AUTHORITIES;
71-
this.name = (name != null) ? name : (String) this.attributes.get("sub");
73+
// Ensure name is never null - use 'sub' attribute as fallback, then empty string
74+
// This satisfies AuthenticatedPrincipal.getName() contract which never returns
75+
// null
76+
String resolvedName = (name != null) ? name : (String) this.attributes.get("sub");
77+
this.name = (resolvedName != null) ? resolvedName : "";
7278
}
7379

7480
/**

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.Collections;
2323
import java.util.Set;
2424

25+
import org.jspecify.annotations.Nullable;
26+
2527
import org.springframework.util.Assert;
2628

2729
/**
@@ -52,25 +54,26 @@ public class OAuth2AccessToken extends AbstractOAuth2Token {
5254
* Constructs an {@code OAuth2AccessToken} using the provided parameters.
5355
* @param tokenType the token type
5456
* @param tokenValue the token value
55-
* @param issuedAt the time at which the token was issued
57+
* @param issuedAt the time at which the token was issued, may be {@code null}
5658
* @param expiresAt the expiration time on or after which the token MUST NOT be
57-
* accepted
59+
* accepted, may be {@code null}
5860
*/
59-
public OAuth2AccessToken(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt) {
61+
public OAuth2AccessToken(TokenType tokenType, String tokenValue, @Nullable Instant issuedAt,
62+
@Nullable Instant expiresAt) {
6063
this(tokenType, tokenValue, issuedAt, expiresAt, Collections.emptySet());
6164
}
6265

6366
/**
6467
* Constructs an {@code OAuth2AccessToken} using the provided parameters.
6568
* @param tokenType the token type
6669
* @param tokenValue the token value
67-
* @param issuedAt the time at which the token was issued
70+
* @param issuedAt the time at which the token was issued, may be {@code null}
6871
* @param expiresAt the expiration time on or after which the token MUST NOT be
69-
* accepted
72+
* accepted, may be {@code null}
7073
* @param scopes the scope(s) associated to the token
7174
*/
72-
public OAuth2AccessToken(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt,
73-
Set<String> scopes) {
75+
public OAuth2AccessToken(TokenType tokenType, String tokenValue, @Nullable Instant issuedAt,
76+
@Nullable Instant expiresAt, Set<String> scopes) {
7477
super(tokenValue, issuedAt, expiresAt);
7578
Assert.notNull(tokenType, "tokenType cannot be null");
7679
this.tokenType = tokenType;

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthenticatedPrincipal.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
import java.util.Collection;
2020
import java.util.Map;
2121

22-
import org.springframework.lang.Nullable;
22+
import org.jspecify.annotations.Nullable;
23+
2324
import org.springframework.security.core.AuthenticatedPrincipal;
2425
import org.springframework.security.core.GrantedAuthority;
2526

@@ -38,9 +39,8 @@ public interface OAuth2AuthenticatedPrincipal extends AuthenticatedPrincipal {
3839
* @param <A> the type of the attribute
3940
* @return the attribute or {@code null} otherwise
4041
*/
41-
@Nullable
4242
@SuppressWarnings("unchecked")
43-
default <A> A getAttribute(String name) {
43+
default <A> @Nullable A getAttribute(String name) {
4444
return (A) getAttributes().get(name);
4545
}
4646

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthenticationException.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.io.Serial;
2020

21+
import org.jspecify.annotations.Nullable;
22+
2123
import org.springframework.security.core.Authentication;
2224
import org.springframework.security.core.AuthenticationException;
2325
import org.springframework.util.Assert;
@@ -77,22 +79,25 @@ public OAuth2AuthenticationException(OAuth2Error error, Throwable cause) {
7779
/**
7880
* Constructs an {@code OAuth2AuthenticationException} using the provided parameters.
7981
* @param error the {@link OAuth2Error OAuth 2.0 Error}
80-
* @param message the detail message
82+
* @param message the detail message, may be {@code null}
8183
*/
82-
public OAuth2AuthenticationException(OAuth2Error error, String message) {
84+
public OAuth2AuthenticationException(OAuth2Error error, @Nullable String message) {
8385
this(error, message, null);
8486
}
8587

8688
/**
8789
* Constructs an {@code OAuth2AuthenticationException} using the provided parameters.
8890
* @param error the {@link OAuth2Error OAuth 2.0 Error}
89-
* @param message the detail message
90-
* @param cause the root cause
91+
* @param message the detail message, may be {@code null}
92+
* @param cause the root cause, may be {@code null}
9193
*/
92-
public OAuth2AuthenticationException(OAuth2Error error, String message, Throwable cause) {
93-
super(message, cause);
94+
public OAuth2AuthenticationException(OAuth2Error error, @Nullable String message, @Nullable Throwable cause) {
95+
super(message);
9496
Assert.notNull(error, "error cannot be null");
9597
this.error = error;
98+
if (cause != null) {
99+
initCause(cause);
100+
}
96101
}
97102

98103
/**

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2Error.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.io.Serializable;
2020

21+
import org.jspecify.annotations.Nullable;
22+
2123
import org.springframework.util.Assert;
2224

2325
/**
@@ -41,9 +43,9 @@ public class OAuth2Error implements Serializable {
4143

4244
private final String errorCode;
4345

44-
private final String description;
46+
private final @Nullable String description;
4547

46-
private final String uri;
48+
private final @Nullable String uri;
4749

4850
/**
4951
* Constructs an {@code OAuth2Error} using the provided parameters.
@@ -56,10 +58,10 @@ public OAuth2Error(String errorCode) {
5658
/**
5759
* Constructs an {@code OAuth2Error} using the provided parameters.
5860
* @param errorCode the error code
59-
* @param description the error description
60-
* @param uri the error uri
61+
* @param description the error description, may be {@code null}
62+
* @param uri the error uri, may be {@code null}
6163
*/
62-
public OAuth2Error(String errorCode, String description, String uri) {
64+
public OAuth2Error(String errorCode, @Nullable String description, @Nullable String uri) {
6365
Assert.hasText(errorCode, "errorCode cannot be empty");
6466
this.errorCode = errorCode;
6567
this.description = description;
@@ -76,17 +78,17 @@ public final String getErrorCode() {
7678

7779
/**
7880
* Returns the error description.
79-
* @return the error description
81+
* @return the error description, or {@code null} if not available
8082
*/
81-
public final String getDescription() {
83+
public final @Nullable String getDescription() {
8284
return this.description;
8385
}
8486

8587
/**
8688
* Returns the error uri.
87-
* @return the error uri
89+
* @return the error uri, or {@code null} if not available
8890
*/
89-
public final String getUri() {
91+
public final @Nullable String getUri() {
9092
return this.uri;
9193
}
9294

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2RefreshToken.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.io.Serial;
2020
import java.time.Instant;
2121

22+
import org.jspecify.annotations.Nullable;
23+
2224
/**
2325
* An implementation of an {@link AbstractOAuth2Token} representing an OAuth 2.0 Refresh
2426
* Token.
@@ -43,20 +45,20 @@ public class OAuth2RefreshToken extends AbstractOAuth2Token {
4345
/**
4446
* Constructs an {@code OAuth2RefreshToken} using the provided parameters.
4547
* @param tokenValue the token value
46-
* @param issuedAt the time at which the token was issued
48+
* @param issuedAt the time at which the token was issued, may be {@code null}
4749
*/
48-
public OAuth2RefreshToken(String tokenValue, Instant issuedAt) {
50+
public OAuth2RefreshToken(String tokenValue, @Nullable Instant issuedAt) {
4951
this(tokenValue, issuedAt, null);
5052
}
5153

5254
/**
5355
* Constructs an {@code OAuth2RefreshToken} using the provided parameters.
5456
* @param tokenValue the token value
55-
* @param issuedAt the time at which the token was issued
56-
* @param expiresAt the time at which the token expires
57+
* @param issuedAt the time at which the token was issued, may be {@code null}
58+
* @param expiresAt the time at which the token expires, may be {@code null}
5759
* @since 5.5
5860
*/
59-
public OAuth2RefreshToken(String tokenValue, Instant issuedAt, Instant expiresAt) {
61+
public OAuth2RefreshToken(String tokenValue, @Nullable Instant issuedAt, @Nullable Instant expiresAt) {
6062
super(tokenValue, issuedAt, expiresAt);
6163
}
6264

0 commit comments

Comments
 (0)