Browse Source

Enable null-safety in spring-security-oauth2-core

Closes gh-17820
pull/18633/head
Joe Grandja 2 months ago committed by Robert Winch
parent
commit
db5310bee8
No known key found for this signature in database
  1. 1
      oauth2/oauth2-core/spring-security-oauth2-core.gradle
  2. 13
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AbstractOAuth2Token.java
  3. 31
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ClaimAccessor.java
  4. 2
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/ClientAuthenticationMethod.java
  5. 16
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/DefaultOAuth2AuthenticatedPrincipal.java
  6. 17
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AccessToken.java
  7. 6
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthenticatedPrincipal.java
  8. 17
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2AuthenticationException.java
  9. 20
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2Error.java
  10. 12
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2RefreshToken.java
  11. 8
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2Token.java
  12. 91
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenIntrospectionClaimAccessor.java
  13. 23
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/authorization/package-info.java
  14. 4
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ClaimConversionService.java
  15. 4
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToBooleanConverter.java
  16. 4
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToInstantConverter.java
  17. 4
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToListStringConverter.java
  18. 7
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToMapStringObjectConverter.java
  19. 4
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToStringConverter.java
  20. 4
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToURLConverter.java
  21. 23
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/package-info.java
  22. 11
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/DefaultMapOAuth2AccessTokenResponseConverter.java
  23. 50
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java
  24. 59
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java
  25. 39
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationResponse.java
  26. 33
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2DeviceAuthorizationResponse.java
  27. 3
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/package-info.java
  28. 4
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/HttpMessageConverters.java
  29. 6
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java
  30. 23
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2DeviceAuthorizationResponseHttpMessageConverter.java
  31. 7
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java
  32. 23
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/package-info.java
  33. 23
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/package-info.java
  34. 41
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/AddressStandardClaim.java
  35. 62
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/DefaultAddressStandardClaim.java
  36. 90
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/IdTokenClaimAccessor.java
  37. 18
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/OidcIdToken.java
  38. 132
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/StandardClaimAccessor.java
  39. 3
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/endpoint/package-info.java
  40. 3
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/package-info.java
  41. 26
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUser.java
  42. 9
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/OidcUser.java
  43. 26
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/OidcUserAuthority.java
  44. 3
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/package-info.java
  45. 3
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/package-info.java
  46. 16
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/DefaultOAuth2User.java
  47. 26
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java
  48. 3
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/package-info.java
  49. 23
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/web/package-info.java
  50. 23
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/web/reactive/function/package-info.java
  51. 23
      oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/web/reactive/package-info.java
  52. 4
      oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationTests.java

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

@ -1,5 +1,6 @@
plugins { plugins {
id 'compile-warnings-error' id 'compile-warnings-error'
id 'security-nullability'
} }
apply plugin: 'io.spring.convention.spring-module' apply plugin: 'io.spring.convention.spring-module'

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

@ -19,7 +19,8 @@ package org.springframework.security.oauth2.core;
import java.io.Serializable; import java.io.Serializable;
import java.time.Instant; import java.time.Instant;
import org.springframework.lang.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -37,9 +38,9 @@ public abstract class AbstractOAuth2Token implements OAuth2Token, Serializable {
private final String tokenValue; private final String tokenValue;
private final Instant issuedAt; private final @Nullable Instant issuedAt;
private final Instant expiresAt; private final @Nullable Instant expiresAt;
/** /**
* Sub-class constructor. * Sub-class constructor.
@ -78,8 +79,7 @@ public abstract class AbstractOAuth2Token implements OAuth2Token, Serializable {
* Returns the time at which the token was issued. * Returns the time at which the token was issued.
* @return the time the token was issued or {@code null} * @return the time the token was issued or {@code null}
*/ */
@Nullable public @Nullable Instant getIssuedAt() {
public Instant getIssuedAt() {
return this.issuedAt; return this.issuedAt;
} }
@ -87,8 +87,7 @@ public abstract class AbstractOAuth2Token implements OAuth2Token, Serializable {
* Returns the expiration time on or after which the token MUST NOT be accepted. * Returns the expiration time on or after which the token MUST NOT be accepted.
* @return the token expiration time or {@code null} * @return the token expiration time or {@code null}
*/ */
@Nullable public @Nullable Instant getExpiresAt() {
public Instant getExpiresAt() {
return this.expiresAt; return this.expiresAt;
} }

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

@ -21,6 +21,8 @@ import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.security.oauth2.core.converter.ClaimConversionService; import org.springframework.security.oauth2.core.converter.ClaimConversionService;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -44,11 +46,11 @@ public interface ClaimAccessor {
* type {@code T}. * type {@code T}.
* @param claim the name of the claim * @param claim the name of the claim
* @param <T> the type of the claim value * @param <T> the type of the claim value
* @return the claim value * @return the claim value, or {@code null} if the claim does not exist
* @since 5.2 * @since 5.2
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default <T> T getClaim(String claim) { default <T> @Nullable T getClaim(String claim) {
return !hasClaim(claim) ? null : (T) getClaims().get(claim); return !hasClaim(claim) ? null : (T) getClaims().get(claim);
} }
@ -71,7 +73,7 @@ public interface ClaimAccessor {
* @return the claim value or {@code null} if it does not exist or is equal to * @return the claim value or {@code null} if it does not exist or is equal to
* {@code null} * {@code null}
*/ */
default String getClaimAsString(String claim) { default @Nullable String getClaimAsString(String claim) {
return !hasClaim(claim) ? null return !hasClaim(claim) ? null
: ClaimConversionService.getSharedInstance().convert(getClaims().get(claim), String.class); : ClaimConversionService.getSharedInstance().convert(getClaims().get(claim), String.class);
} }
@ -85,7 +87,8 @@ public interface ClaimAccessor {
* {@code Boolean} * {@code Boolean}
* @throws NullPointerException if the claim value is {@code null} * @throws NullPointerException if the claim value is {@code null}
*/ */
default Boolean getClaimAsBoolean(String claim) { @SuppressWarnings("NullAway")
default @Nullable Boolean getClaimAsBoolean(String claim) {
if (!hasClaim(claim)) { if (!hasClaim(claim)) {
return null; return null;
} }
@ -100,8 +103,12 @@ public interface ClaimAccessor {
* Returns the claim value as an {@code Instant} or {@code null} if it does not exist. * Returns the claim value as an {@code Instant} or {@code null} if it does not exist.
* @param claim the name of the claim * @param claim the name of the claim
* @return the claim value or {@code null} if it does not exist * @return the claim value or {@code null} if it does not exist
* @throws IllegalArgumentException if the claim value cannot be converted to an
* {@code Instant}
* @throws NullPointerException if the claim value is {@code null}
*/ */
default Instant getClaimAsInstant(String claim) { @SuppressWarnings("NullAway")
default @Nullable Instant getClaimAsInstant(String claim) {
if (!hasClaim(claim)) { if (!hasClaim(claim)) {
return null; return null;
} }
@ -116,8 +123,12 @@ public interface ClaimAccessor {
* Returns the claim value as an {@code URL} or {@code null} if it does not exist. * Returns the claim value as an {@code URL} or {@code null} if it does not exist.
* @param claim the name of the claim * @param claim the name of the claim
* @return the claim value or {@code null} if it does not exist * @return the claim value or {@code null} if it does not exist
* @throws IllegalArgumentException if the claim value cannot be converted to a
* {@code URL}
* @throws NullPointerException if the claim value is {@code null}
*/ */
default URL getClaimAsURL(String claim) { @SuppressWarnings("NullAway")
default @Nullable URL getClaimAsURL(String claim) {
if (!hasClaim(claim)) { if (!hasClaim(claim)) {
return null; return null;
} }
@ -137,8 +148,8 @@ public interface ClaimAccessor {
* {@code Map} * {@code Map}
* @throws NullPointerException if the claim value is {@code null} * @throws NullPointerException if the claim value is {@code null}
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings({ "unchecked", "NullAway" })
default Map<String, Object> getClaimAsMap(String claim) { default @Nullable Map<String, Object> getClaimAsMap(String claim) {
if (!hasClaim(claim)) { if (!hasClaim(claim)) {
return null; return null;
} }
@ -162,8 +173,8 @@ public interface ClaimAccessor {
* {@code List} * {@code List}
* @throws NullPointerException if the claim value is {@code null} * @throws NullPointerException if the claim value is {@code null}
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings({ "unchecked", "NullAway" })
default List<String> getClaimAsStringList(String claim) { default @Nullable List<String> getClaimAsStringList(String claim) {
if (!hasClaim(claim)) { if (!hasClaim(claim)) {
return null; return null;
} }

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

@ -18,7 +18,6 @@ package org.springframework.security.oauth2.core;
import java.io.Serializable; import java.io.Serializable;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -105,7 +104,6 @@ public final class ClientAuthenticationMethod implements Serializable {
* constant, if any * constant, if any
* @since 6.5 * @since 6.5
*/ */
@NonNull
public static ClientAuthenticationMethod valueOf(String method) { public static ClientAuthenticationMethod valueOf(String method) {
for (ClientAuthenticationMethod m : methods()) { for (ClientAuthenticationMethod m : methods()) {
if (m.getValue().equals(method)) { if (m.getValue().equals(method)) {

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

@ -22,6 +22,8 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -58,17 +60,21 @@ public final class DefaultOAuth2AuthenticatedPrincipal implements OAuth2Authenti
/** /**
* Constructs an {@code DefaultOAuth2AuthenticatedPrincipal} using the provided * Constructs an {@code DefaultOAuth2AuthenticatedPrincipal} using the provided
* parameters. * parameters.
* @param name the name attached to the OAuth 2.0 token * @param name the name attached to the OAuth 2.0 token, may be {@code null}
* @param attributes the attributes of the OAuth 2.0 token * @param attributes the attributes of the OAuth 2.0 token
* @param authorities the authorities of the OAuth 2.0 token * @param authorities the authorities of the OAuth 2.0 token, may be {@code null}
*/ */
public DefaultOAuth2AuthenticatedPrincipal(String name, Map<String, Object> attributes, public DefaultOAuth2AuthenticatedPrincipal(@Nullable String name, Map<String, Object> attributes,
Collection<GrantedAuthority> authorities) { @Nullable Collection<GrantedAuthority> authorities) {
Assert.notEmpty(attributes, "attributes cannot be empty"); Assert.notEmpty(attributes, "attributes cannot be empty");
this.attributes = Collections.unmodifiableMap(attributes); this.attributes = Collections.unmodifiableMap(attributes);
this.authorities = (authorities != null) ? Collections.unmodifiableCollection(authorities) this.authorities = (authorities != null) ? Collections.unmodifiableCollection(authorities)
: AuthorityUtils.NO_AUTHORITIES; : AuthorityUtils.NO_AUTHORITIES;
this.name = (name != null) ? name : (String) this.attributes.get("sub"); // Ensure name is never null - use 'sub' attribute as fallback, then empty string
// This satisfies AuthenticatedPrincipal.getName() contract which never returns
// null
String resolvedName = (name != null) ? name : (String) this.attributes.get("sub");
this.name = (resolvedName != null) ? resolvedName : "";
} }
/** /**

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

@ -22,6 +22,8 @@ import java.time.Instant;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -52,11 +54,12 @@ public class OAuth2AccessToken extends AbstractOAuth2Token {
* Constructs an {@code OAuth2AccessToken} using the provided parameters. * Constructs an {@code OAuth2AccessToken} using the provided parameters.
* @param tokenType the token type * @param tokenType the token type
* @param tokenValue the token value * @param tokenValue the token value
* @param issuedAt the time at which the token was issued * @param issuedAt the time at which the token was issued, may be {@code null}
* @param expiresAt the expiration time on or after which the token MUST NOT be * @param expiresAt the expiration time on or after which the token MUST NOT be
* accepted * accepted, may be {@code null}
*/ */
public OAuth2AccessToken(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt) { public OAuth2AccessToken(TokenType tokenType, String tokenValue, @Nullable Instant issuedAt,
@Nullable Instant expiresAt) {
this(tokenType, tokenValue, issuedAt, expiresAt, Collections.emptySet()); this(tokenType, tokenValue, issuedAt, expiresAt, Collections.emptySet());
} }
@ -64,13 +67,13 @@ public class OAuth2AccessToken extends AbstractOAuth2Token {
* Constructs an {@code OAuth2AccessToken} using the provided parameters. * Constructs an {@code OAuth2AccessToken} using the provided parameters.
* @param tokenType the token type * @param tokenType the token type
* @param tokenValue the token value * @param tokenValue the token value
* @param issuedAt the time at which the token was issued * @param issuedAt the time at which the token was issued, may be {@code null}
* @param expiresAt the expiration time on or after which the token MUST NOT be * @param expiresAt the expiration time on or after which the token MUST NOT be
* accepted * accepted, may be {@code null}
* @param scopes the scope(s) associated to the token * @param scopes the scope(s) associated to the token
*/ */
public OAuth2AccessToken(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt, public OAuth2AccessToken(TokenType tokenType, String tokenValue, @Nullable Instant issuedAt,
Set<String> scopes) { @Nullable Instant expiresAt, Set<String> scopes) {
super(tokenValue, issuedAt, expiresAt); super(tokenValue, issuedAt, expiresAt);
Assert.notNull(tokenType, "tokenType cannot be null"); Assert.notNull(tokenType, "tokenType cannot be null");
this.tokenType = tokenType; this.tokenType = tokenType;

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

@ -19,7 +19,8 @@ package org.springframework.security.oauth2.core;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import org.springframework.lang.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.core.AuthenticatedPrincipal; import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
@ -38,9 +39,8 @@ public interface OAuth2AuthenticatedPrincipal extends AuthenticatedPrincipal {
* @param <A> the type of the attribute * @param <A> the type of the attribute
* @return the attribute or {@code null} otherwise * @return the attribute or {@code null} otherwise
*/ */
@Nullable
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default <A> A getAttribute(String name) { default <A> @Nullable A getAttribute(String name) {
return (A) getAttributes().get(name); return (A) getAttributes().get(name);
} }

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

@ -18,6 +18,8 @@ package org.springframework.security.oauth2.core;
import java.io.Serial; import java.io.Serial;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -77,22 +79,25 @@ public class OAuth2AuthenticationException extends AuthenticationException {
/** /**
* Constructs an {@code OAuth2AuthenticationException} using the provided parameters. * Constructs an {@code OAuth2AuthenticationException} using the provided parameters.
* @param error the {@link OAuth2Error OAuth 2.0 Error} * @param error the {@link OAuth2Error OAuth 2.0 Error}
* @param message the detail message * @param message the detail message, may be {@code null}
*/ */
public OAuth2AuthenticationException(OAuth2Error error, String message) { public OAuth2AuthenticationException(OAuth2Error error, @Nullable String message) {
this(error, message, null); this(error, message, null);
} }
/** /**
* Constructs an {@code OAuth2AuthenticationException} using the provided parameters. * Constructs an {@code OAuth2AuthenticationException} using the provided parameters.
* @param error the {@link OAuth2Error OAuth 2.0 Error} * @param error the {@link OAuth2Error OAuth 2.0 Error}
* @param message the detail message * @param message the detail message, may be {@code null}
* @param cause the root cause * @param cause the root cause, may be {@code null}
*/ */
public OAuth2AuthenticationException(OAuth2Error error, String message, Throwable cause) { public OAuth2AuthenticationException(OAuth2Error error, @Nullable String message, @Nullable Throwable cause) {
super(message, cause); super(message);
Assert.notNull(error, "error cannot be null"); Assert.notNull(error, "error cannot be null");
this.error = error; this.error = error;
if (cause != null) {
initCause(cause);
}
} }
/** /**

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

@ -18,6 +18,8 @@ package org.springframework.security.oauth2.core;
import java.io.Serializable; import java.io.Serializable;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -41,9 +43,9 @@ public class OAuth2Error implements Serializable {
private final String errorCode; private final String errorCode;
private final String description; private final @Nullable String description;
private final String uri; private final @Nullable String uri;
/** /**
* Constructs an {@code OAuth2Error} using the provided parameters. * Constructs an {@code OAuth2Error} using the provided parameters.
@ -56,10 +58,10 @@ public class OAuth2Error implements Serializable {
/** /**
* Constructs an {@code OAuth2Error} using the provided parameters. * Constructs an {@code OAuth2Error} using the provided parameters.
* @param errorCode the error code * @param errorCode the error code
* @param description the error description * @param description the error description, may be {@code null}
* @param uri the error uri * @param uri the error uri, may be {@code null}
*/ */
public OAuth2Error(String errorCode, String description, String uri) { public OAuth2Error(String errorCode, @Nullable String description, @Nullable String uri) {
Assert.hasText(errorCode, "errorCode cannot be empty"); Assert.hasText(errorCode, "errorCode cannot be empty");
this.errorCode = errorCode; this.errorCode = errorCode;
this.description = description; this.description = description;
@ -76,17 +78,17 @@ public class OAuth2Error implements Serializable {
/** /**
* Returns the error description. * Returns the error description.
* @return the error description * @return the error description, or {@code null} if not available
*/ */
public final String getDescription() { public final @Nullable String getDescription() {
return this.description; return this.description;
} }
/** /**
* Returns the error uri. * Returns the error uri.
* @return the error uri * @return the error uri, or {@code null} if not available
*/ */
public final String getUri() { public final @Nullable String getUri() {
return this.uri; return this.uri;
} }

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

@ -19,6 +19,8 @@ package org.springframework.security.oauth2.core;
import java.io.Serial; import java.io.Serial;
import java.time.Instant; import java.time.Instant;
import org.jspecify.annotations.Nullable;
/** /**
* An implementation of an {@link AbstractOAuth2Token} representing an OAuth 2.0 Refresh * An implementation of an {@link AbstractOAuth2Token} representing an OAuth 2.0 Refresh
* Token. * Token.
@ -43,20 +45,20 @@ public class OAuth2RefreshToken extends AbstractOAuth2Token {
/** /**
* Constructs an {@code OAuth2RefreshToken} using the provided parameters. * Constructs an {@code OAuth2RefreshToken} using the provided parameters.
* @param tokenValue the token value * @param tokenValue the token value
* @param issuedAt the time at which the token was issued * @param issuedAt the time at which the token was issued, may be {@code null}
*/ */
public OAuth2RefreshToken(String tokenValue, Instant issuedAt) { public OAuth2RefreshToken(String tokenValue, @Nullable Instant issuedAt) {
this(tokenValue, issuedAt, null); this(tokenValue, issuedAt, null);
} }
/** /**
* Constructs an {@code OAuth2RefreshToken} using the provided parameters. * Constructs an {@code OAuth2RefreshToken} using the provided parameters.
* @param tokenValue the token value * @param tokenValue the token value
* @param issuedAt the time at which the token was issued * @param issuedAt the time at which the token was issued, may be {@code null}
* @param expiresAt the time at which the token expires * @param expiresAt the time at which the token expires, may be {@code null}
* @since 5.5 * @since 5.5
*/ */
public OAuth2RefreshToken(String tokenValue, Instant issuedAt, Instant expiresAt) { public OAuth2RefreshToken(String tokenValue, @Nullable Instant issuedAt, @Nullable Instant expiresAt) {
super(tokenValue, issuedAt, expiresAt); super(tokenValue, issuedAt, expiresAt);
} }

8
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2Token.java

@ -18,7 +18,7 @@ package org.springframework.security.oauth2.core;
import java.time.Instant; import java.time.Instant;
import org.springframework.lang.Nullable; import org.jspecify.annotations.Nullable;
/** /**
* Core interface representing an OAuth 2.0 Token. * Core interface representing an OAuth 2.0 Token.
@ -39,8 +39,7 @@ public interface OAuth2Token {
* Returns the time at which the token was issued. * Returns the time at which the token was issued.
* @return the time the token was issued or {@code null} * @return the time the token was issued or {@code null}
*/ */
@Nullable default @Nullable Instant getIssuedAt() {
default Instant getIssuedAt() {
return null; return null;
} }
@ -48,8 +47,7 @@ public interface OAuth2Token {
* Returns the expiration time on or after which the token MUST NOT be accepted. * Returns the expiration time on or after which the token MUST NOT be accepted.
* @return the token expiration time or {@code null} * @return the token expiration time or {@code null}
*/ */
@Nullable default @Nullable Instant getExpiresAt() {
default Instant getExpiresAt() {
return null; return null;
} }

91
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenIntrospectionClaimAccessor.java

@ -20,7 +20,7 @@ import java.net.URL;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
import org.springframework.lang.Nullable; import org.jspecify.annotations.Nullable;
/** /**
* A {@link ClaimAccessor} for the &quot;claims&quot; that may be contained in the * A {@link ClaimAccessor} for the &quot;claims&quot; that may be contained in the
@ -45,105 +45,106 @@ public interface OAuth2TokenIntrospectionClaimAccessor extends ClaimAccessor {
/** /**
* Returns a human-readable identifier {@code (username)} for the resource owner that * Returns a human-readable identifier {@code (username)} for the resource owner that
* authorized the token * authorized the token, or {@code null} if it does not exist.
* @return a human-readable identifier for the resource owner that authorized the * @return a human-readable identifier for the resource owner that authorized the
* token * token, or {@code null} if it does not exist
*/ */
@Nullable default @Nullable String getUsername() {
default String getUsername() {
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.USERNAME); return getClaimAsString(OAuth2TokenIntrospectionClaimNames.USERNAME);
} }
/** /**
* Returns the client identifier {@code (client_id)} for the token * Returns the client identifier {@code (client_id)} for the token, or {@code null} if
* @return the client identifier for the token * it does not exist.
* @return the client identifier for the token, or {@code null} if it does not exist
*/ */
@Nullable default @Nullable String getClientId() {
default String getClientId() {
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.CLIENT_ID); return getClaimAsString(OAuth2TokenIntrospectionClaimNames.CLIENT_ID);
} }
/** /**
* Returns the scopes {@code (scope)} associated with the token * Returns the scopes {@code (scope)} associated with the token, or {@code null} if it
* @return the scopes associated with the token * does not exist.
* @return the scopes associated with the token, or {@code null} if it does not exist
*/ */
@Nullable default @Nullable List<String> getScopes() {
default List<String> getScopes() {
return getClaimAsStringList(OAuth2TokenIntrospectionClaimNames.SCOPE); return getClaimAsStringList(OAuth2TokenIntrospectionClaimNames.SCOPE);
} }
/** /**
* Returns the type of the token {@code (token_type)}, for example {@code bearer}. * Returns the type of the token {@code (token_type)}, for example {@code bearer}, or
* @return the type of the token, for example {@code bearer}. * {@code null} if it does not exist.
* @return the type of the token, for example {@code bearer}, or {@code null} if it
* does not exist
*/ */
@Nullable default @Nullable String getTokenType() {
default String getTokenType() {
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.TOKEN_TYPE); return getClaimAsString(OAuth2TokenIntrospectionClaimNames.TOKEN_TYPE);
} }
/** /**
* Returns a timestamp {@code (exp)} indicating when the token expires * Returns a timestamp {@code (exp)} indicating when the token expires, or
* @return a timestamp indicating when the token expires * {@code null} if it does not exist.
* @return a timestamp indicating when the token expires, or {@code null} if it does
* not exist
*/ */
@Nullable default @Nullable Instant getExpiresAt() {
default Instant getExpiresAt() {
return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.EXP); return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.EXP);
} }
/** /**
* Returns a timestamp {@code (iat)} indicating when the token was issued * Returns a timestamp {@code (iat)} indicating when the token was issued, or
* @return a timestamp indicating when the token was issued * {@code null} if it does not exist.
* @return a timestamp indicating when the token was issued, or {@code null} if it
* does not exist
*/ */
@Nullable default @Nullable Instant getIssuedAt() {
default Instant getIssuedAt() {
return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.IAT); return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.IAT);
} }
/** /**
* Returns a timestamp {@code (nbf)} indicating when the token is not to be used * Returns a timestamp {@code (nbf)} indicating when the token is not to be used
* before * before, or {@code null} if it does not exist.
* @return a timestamp indicating when the token is not to be used before * @return a timestamp indicating when the token is not to be used before, or
* {@code null} if it does not exist
*/ */
@Nullable default @Nullable Instant getNotBefore() {
default Instant getNotBefore() {
return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.NBF); return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.NBF);
} }
/** /**
* Returns usually a machine-readable identifier {@code (sub)} of the resource owner * Returns usually a machine-readable identifier {@code (sub)} of the resource owner
* who authorized the token * who authorized the token, or {@code null} if it does not exist.
* @return usually a machine-readable identifier of the resource owner who authorized * @return usually a machine-readable identifier of the resource owner who authorized
* the token * the token, or {@code null} if it does not exist
*/ */
@Nullable default @Nullable String getSubject() {
default String getSubject() {
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.SUB); return getClaimAsString(OAuth2TokenIntrospectionClaimNames.SUB);
} }
/** /**
* Returns the intended audience {@code (aud)} for the token * Returns the intended audience {@code (aud)} for the token, or {@code null} if it
* @return the intended audience for the token * does not exist.
* @return the intended audience for the token, or {@code null} if it does not exist
*/ */
@Nullable default @Nullable List<String> getAudience() {
default List<String> getAudience() {
return getClaimAsStringList(OAuth2TokenIntrospectionClaimNames.AUD); return getClaimAsStringList(OAuth2TokenIntrospectionClaimNames.AUD);
} }
/** /**
* Returns the issuer {@code (iss)} of the token * Returns the issuer {@code (iss)} of the token, or {@code null} if it does not
* @return the issuer of the token * exist.
* @return the issuer of the token, or {@code null} if it does not exist
*/ */
@Nullable default @Nullable URL getIssuer() {
default URL getIssuer() {
return getClaimAsURL(OAuth2TokenIntrospectionClaimNames.ISS); return getClaimAsURL(OAuth2TokenIntrospectionClaimNames.ISS);
} }
/** /**
* Returns the identifier {@code (jti)} for the token * Returns the identifier {@code (jti)} for the token, or {@code null} if it does not
* @return the identifier for the token * exist.
* @return the identifier for the token, or {@code null} if it does not exist
*/ */
@Nullable default @Nullable String getId() {
default String getId() {
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.JTI); return getClaimAsString(OAuth2TokenIntrospectionClaimNames.JTI);
} }

23
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/authorization/package-info.java

@ -0,0 +1,23 @@
/*
* Copyright 2004-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support classes that provide OAuth 2.0 authorization managers.
*/
@NullMarked
package org.springframework.security.oauth2.core.authorization;
import org.jspecify.annotations.NullMarked;

4
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ClaimConversionService.java

@ -16,6 +16,8 @@
package org.springframework.security.oauth2.core.converter; package org.springframework.security.oauth2.core.converter;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.GenericConversionService;
@ -32,7 +34,7 @@ import org.springframework.security.oauth2.core.ClaimAccessor;
*/ */
public final class ClaimConversionService extends GenericConversionService { public final class ClaimConversionService extends GenericConversionService {
private static volatile ClaimConversionService sharedInstance; private static volatile @Nullable ClaimConversionService sharedInstance;
private ClaimConversionService() { private ClaimConversionService() {
addConverters(this); addConverters(this);

4
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToBooleanConverter.java

@ -19,6 +19,8 @@ package org.springframework.security.oauth2.core.converter;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.converter.GenericConverter;
@ -34,7 +36,7 @@ final class ObjectToBooleanConverter implements GenericConverter {
} }
@Override @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) { if (source == null) {
return null; return null;
} }

4
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToInstantConverter.java

@ -21,6 +21,8 @@ import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.converter.GenericConverter;
@ -36,7 +38,7 @@ final class ObjectToInstantConverter implements GenericConverter {
} }
@Override @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) { if (source == null) {
return null; return null;
} }

4
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToListStringConverter.java

@ -23,6 +23,8 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -49,7 +51,7 @@ final class ObjectToListStringConverter implements ConditionalGenericConverter {
} }
@Override @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) { if (source == null) {
return null; return null;
} }

7
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToMapStringObjectConverter.java

@ -21,6 +21,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.converter.ConditionalGenericConverter;
@ -37,12 +39,13 @@ final class ObjectToMapStringObjectConverter implements ConditionalGenericConver
@Override @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
TypeDescriptor mapKeyTypeDescriptor = targetType.getMapKeyTypeDescriptor();
return targetType.getElementTypeDescriptor() == null return targetType.getElementTypeDescriptor() == null
|| targetType.getMapKeyTypeDescriptor().getType().equals(String.class); || (mapKeyTypeDescriptor != null && mapKeyTypeDescriptor.getType().equals(String.class));
} }
@Override @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) { if (source == null) {
return null; return null;
} }

4
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToStringConverter.java

@ -19,6 +19,8 @@ package org.springframework.security.oauth2.core.converter;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.converter.GenericConverter;
@ -34,7 +36,7 @@ final class ObjectToStringConverter implements GenericConverter {
} }
@Override @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return (source != null) ? source.toString() : null; return (source != null) ? source.toString() : null;
} }

4
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToURLConverter.java

@ -21,6 +21,8 @@ import java.net.URL;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.converter.GenericConverter;
@ -36,7 +38,7 @@ final class ObjectToURLConverter implements GenericConverter {
} }
@Override @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) { if (source == null) {
return null; return null;
} }

23
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/package-info.java

@ -0,0 +1,23 @@
/*
* Copyright 2004-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support classes that provide claim type converters for OAuth 2.0 and OpenID Connect.
*/
@NullMarked
package org.springframework.security.oauth2.core.converter;
import org.jspecify.annotations.NullMarked;

11
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/DefaultMapOAuth2AccessTokenResponseConverter.java

@ -23,6 +23,8 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -44,6 +46,9 @@ public final class DefaultMapOAuth2AccessTokenResponseConverter
@Override @Override
public OAuth2AccessTokenResponse convert(Map<String, Object> source) { public OAuth2AccessTokenResponse convert(Map<String, Object> source) {
String accessToken = getParameterValue(source, OAuth2ParameterNames.ACCESS_TOKEN); String accessToken = getParameterValue(source, OAuth2ParameterNames.ACCESS_TOKEN);
if (accessToken == null) {
throw new IllegalArgumentException("Missing required parameter: " + OAuth2ParameterNames.ACCESS_TOKEN);
}
OAuth2AccessToken.TokenType accessTokenType = getAccessTokenType(source); OAuth2AccessToken.TokenType accessTokenType = getAccessTokenType(source);
long expiresIn = getExpiresIn(source); long expiresIn = getExpiresIn(source);
Set<String> scopes = getScopes(source); Set<String> scopes = getScopes(source);
@ -65,7 +70,8 @@ public final class DefaultMapOAuth2AccessTokenResponseConverter
// @formatter:on // @formatter:on
} }
private static OAuth2AccessToken.TokenType getAccessTokenType(Map<String, Object> tokenResponseParameters) { private static OAuth2AccessToken.@Nullable TokenType getAccessTokenType(
Map<String, Object> tokenResponseParameters) {
if (OAuth2AccessToken.TokenType.BEARER.getValue() if (OAuth2AccessToken.TokenType.BEARER.getValue()
.equalsIgnoreCase(getParameterValue(tokenResponseParameters, OAuth2ParameterNames.TOKEN_TYPE))) { .equalsIgnoreCase(getParameterValue(tokenResponseParameters, OAuth2ParameterNames.TOKEN_TYPE))) {
return OAuth2AccessToken.TokenType.BEARER; return OAuth2AccessToken.TokenType.BEARER;
@ -89,7 +95,8 @@ public final class DefaultMapOAuth2AccessTokenResponseConverter
return Collections.emptySet(); return Collections.emptySet();
} }
private static String getParameterValue(Map<String, Object> tokenResponseParameters, String parameterName) { private static @Nullable String getParameterValue(Map<String, Object> tokenResponseParameters,
String parameterName) {
Object obj = tokenResponseParameters.get(parameterName); Object obj = tokenResponseParameters.get(parameterName);
return (obj != null) ? obj.toString() : null; return (obj != null) ? obj.toString() : null;
} }

50
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java

@ -21,9 +21,11 @@ import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.lang.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -39,11 +41,11 @@ import org.springframework.util.StringUtils;
*/ */
public final class OAuth2AccessTokenResponse { public final class OAuth2AccessTokenResponse {
private OAuth2AccessToken accessToken; private @Nullable OAuth2AccessToken accessToken;
private OAuth2RefreshToken refreshToken; private @Nullable OAuth2RefreshToken refreshToken;
private Map<String, Object> additionalParameters; private @Nullable Map<String, Object> additionalParameters;
private OAuth2AccessTokenResponse() { private OAuth2AccessTokenResponse() {
} }
@ -53,12 +55,14 @@ public final class OAuth2AccessTokenResponse {
* @return the {@link OAuth2AccessToken} * @return the {@link OAuth2AccessToken}
*/ */
public OAuth2AccessToken getAccessToken() { public OAuth2AccessToken getAccessToken() {
Assert.notNull(this.accessToken, "accessToken cannot be null");
return this.accessToken; return this.accessToken;
} }
/** /**
* Returns the {@link OAuth2RefreshToken Refresh Token}. * Returns the {@link OAuth2RefreshToken Refresh Token}.
* @return the {@link OAuth2RefreshToken} * @return the {@link OAuth2RefreshToken}, or {@code null} if not present in the
* response
* @since 5.1 * @since 5.1
*/ */
public @Nullable OAuth2RefreshToken getRefreshToken() { public @Nullable OAuth2RefreshToken getRefreshToken() {
@ -71,6 +75,7 @@ public final class OAuth2AccessTokenResponse {
* empty. * empty.
*/ */
public Map<String, Object> getAdditionalParameters() { public Map<String, Object> getAdditionalParameters() {
Assert.notNull(this.additionalParameters, "additionalParameters cannot be null");
return this.additionalParameters; return this.additionalParameters;
} }
@ -99,19 +104,19 @@ public final class OAuth2AccessTokenResponse {
private String tokenValue; private String tokenValue;
private OAuth2AccessToken.TokenType tokenType; private OAuth2AccessToken.@Nullable TokenType tokenType;
private Instant issuedAt; private @Nullable Instant issuedAt;
private Instant expiresAt; private @Nullable Instant expiresAt;
private long expiresIn; private long expiresIn;
private Set<String> scopes; private @Nullable Set<String> scopes;
private String refreshToken; private @Nullable String refreshToken;
private Map<String, Object> additionalParameters; private @Nullable Map<String, Object> additionalParameters;
private Builder(OAuth2AccessTokenResponse response) { private Builder(OAuth2AccessTokenResponse response) {
OAuth2AccessToken accessToken = response.getAccessToken(); OAuth2AccessToken accessToken = response.getAccessToken();
@ -131,10 +136,10 @@ public final class OAuth2AccessTokenResponse {
/** /**
* Sets the {@link OAuth2AccessToken.TokenType token type}. * Sets the {@link OAuth2AccessToken.TokenType token type}.
* @param tokenType the type of token issued * @param tokenType the type of token issued, may be {@code null}
* @return the {@link Builder} * @return the {@link Builder}
*/ */
public Builder tokenType(OAuth2AccessToken.TokenType tokenType) { public Builder tokenType(OAuth2AccessToken.@Nullable TokenType tokenType) {
this.tokenType = tokenType; this.tokenType = tokenType;
return this; return this;
} }
@ -152,30 +157,32 @@ public final class OAuth2AccessTokenResponse {
/** /**
* Sets the scope(s) associated to the access token. * Sets the scope(s) associated to the access token.
* @param scopes the scope(s) associated to the access token. * @param scopes the scope(s) associated to the access token, may be {@code null}
* @return the {@link Builder} * @return the {@link Builder}
*/ */
public Builder scopes(Set<String> scopes) { public Builder scopes(@Nullable Set<String> scopes) {
this.scopes = scopes; this.scopes = scopes;
return this; return this;
} }
/** /**
* Sets the refresh token associated to the access token. * Sets the refresh token associated to the access token.
* @param refreshToken the refresh token associated to the access token. * @param refreshToken the refresh token associated to the access token, may be
* {@code null}
* @return the {@link Builder} * @return the {@link Builder}
*/ */
public Builder refreshToken(String refreshToken) { public Builder refreshToken(@Nullable String refreshToken) {
this.refreshToken = refreshToken; this.refreshToken = refreshToken;
return this; return this;
} }
/** /**
* Sets the additional parameters returned in the response. * Sets the additional parameters returned in the response.
* @param additionalParameters the additional parameters returned in the response * @param additionalParameters the additional parameters returned in the response,
* may be {@code null}
* @return the {@link Builder} * @return the {@link Builder}
*/ */
public Builder additionalParameters(Map<String, Object> additionalParameters) { public Builder additionalParameters(@Nullable Map<String, Object> additionalParameters) {
this.additionalParameters = additionalParameters; this.additionalParameters = additionalParameters;
return this; return this;
} }
@ -185,11 +192,14 @@ public final class OAuth2AccessTokenResponse {
* @return a {@link OAuth2AccessTokenResponse} * @return a {@link OAuth2AccessTokenResponse}
*/ */
public OAuth2AccessTokenResponse build() { public OAuth2AccessTokenResponse build() {
Assert.notNull(this.tokenType, "tokenType cannot be null");
Instant issuedAt = getIssuedAt(); Instant issuedAt = getIssuedAt();
Instant expiresAt = getExpiresAt(); Instant expiresAt = getExpiresAt();
// Convert nullable scopes to non-null for constructor
Set<String> scopesToUse = (this.scopes != null) ? this.scopes : Collections.emptySet();
OAuth2AccessTokenResponse accessTokenResponse = new OAuth2AccessTokenResponse(); OAuth2AccessTokenResponse accessTokenResponse = new OAuth2AccessTokenResponse();
accessTokenResponse.accessToken = new OAuth2AccessToken(this.tokenType, this.tokenValue, issuedAt, accessTokenResponse.accessToken = new OAuth2AccessToken(this.tokenType, this.tokenValue, issuedAt,
expiresAt, this.scopes); expiresAt, scopesToUse);
if (StringUtils.hasText(this.refreshToken)) { if (StringUtils.hasText(this.refreshToken)) {
accessTokenResponse.refreshToken = new OAuth2RefreshToken(this.refreshToken, issuedAt); accessTokenResponse.refreshToken = new OAuth2RefreshToken(this.refreshToken, issuedAt);
} }

59
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java

@ -30,6 +30,8 @@ import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -65,11 +67,11 @@ public class OAuth2AuthorizationRequest implements Serializable {
private final String clientId; private final String clientId;
private final String redirectUri; private final @Nullable String redirectUri;
private final Set<String> scopes; private final Set<String> scopes;
private final String state; private final @Nullable String state;
private final Map<String, Object> additionalParameters; private final Map<String, Object> additionalParameters;
@ -80,6 +82,8 @@ public class OAuth2AuthorizationRequest implements Serializable {
protected OAuth2AuthorizationRequest(AbstractBuilder<?, ?> builder) { protected OAuth2AuthorizationRequest(AbstractBuilder<?, ?> builder) {
Assert.hasText(builder.authorizationUri, "authorizationUri cannot be empty"); Assert.hasText(builder.authorizationUri, "authorizationUri cannot be empty");
Assert.hasText(builder.clientId, "clientId cannot be empty"); Assert.hasText(builder.clientId, "clientId cannot be empty");
Assert.notNull(builder.authorizationUri, "authorizationUri cannot be null");
Assert.notNull(builder.clientId, "clientId cannot be null");
this.authorizationUri = builder.authorizationUri; this.authorizationUri = builder.authorizationUri;
this.authorizationGrantType = builder.authorizationGrantType; this.authorizationGrantType = builder.authorizationGrantType;
this.responseType = builder.responseType; this.responseType = builder.responseType;
@ -89,8 +93,9 @@ public class OAuth2AuthorizationRequest implements Serializable {
CollectionUtils.isEmpty(builder.scopes) ? Collections.emptySet() : new LinkedHashSet<>(builder.scopes)); CollectionUtils.isEmpty(builder.scopes) ? Collections.emptySet() : new LinkedHashSet<>(builder.scopes));
this.state = builder.state; this.state = builder.state;
this.additionalParameters = Collections.unmodifiableMap(builder.additionalParameters); this.additionalParameters = Collections.unmodifiableMap(builder.additionalParameters);
this.authorizationRequestUri = StringUtils.hasText(builder.authorizationRequestUri) String builderUri = builder.authorizationRequestUri;
? builder.authorizationRequestUri : builder.buildAuthorizationRequestUri(); this.authorizationRequestUri = StringUtils.hasText(builderUri) ? builderUri
: builder.buildAuthorizationRequestUri();
this.attributes = Collections.unmodifiableMap(builder.attributes); this.attributes = Collections.unmodifiableMap(builder.attributes);
} }
@ -127,10 +132,10 @@ public class OAuth2AuthorizationRequest implements Serializable {
} }
/** /**
* Returns the uri for the redirection endpoint. * Returns the uri for the redirection endpoint, or {@code null} if not present.
* @return the uri for the redirection endpoint * @return the uri for the redirection endpoint, or {@code null}
*/ */
public String getRedirectUri() { public @Nullable String getRedirectUri() {
return this.redirectUri; return this.redirectUri;
} }
@ -143,10 +148,10 @@ public class OAuth2AuthorizationRequest implements Serializable {
} }
/** /**
* Returns the state. * Returns the state, or {@code null} if not present.
* @return the state * @return the state, or {@code null}
*/ */
public String getState() { public @Nullable String getState() {
return this.state; return this.state;
} }
@ -177,7 +182,7 @@ public class OAuth2AuthorizationRequest implements Serializable {
* @since 5.2 * @since 5.2
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T getAttribute(String name) { public <T> @Nullable T getAttribute(String name) {
return (T) this.getAttributes().get(name); return (T) this.getAttributes().get(name);
} }
@ -277,19 +282,19 @@ public class OAuth2AuthorizationRequest implements Serializable {
*/ */
protected abstract static class AbstractBuilder<T extends OAuth2AuthorizationRequest, B extends AbstractBuilder<T, B>> { protected abstract static class AbstractBuilder<T extends OAuth2AuthorizationRequest, B extends AbstractBuilder<T, B>> {
private String authorizationUri; private @Nullable String authorizationUri;
private final AuthorizationGrantType authorizationGrantType = AuthorizationGrantType.AUTHORIZATION_CODE; private final AuthorizationGrantType authorizationGrantType = AuthorizationGrantType.AUTHORIZATION_CODE;
private final OAuth2AuthorizationResponseType responseType = OAuth2AuthorizationResponseType.CODE; private final OAuth2AuthorizationResponseType responseType = OAuth2AuthorizationResponseType.CODE;
private String clientId; private @Nullable String clientId;
private String redirectUri; private @Nullable String redirectUri;
private Set<String> scopes; private @Nullable Set<String> scopes;
private String state; private @Nullable String state;
private Map<String, Object> additionalParameters = new LinkedHashMap<>(); private Map<String, Object> additionalParameters = new LinkedHashMap<>();
@ -298,7 +303,7 @@ public class OAuth2AuthorizationRequest implements Serializable {
private Map<String, Object> attributes = new LinkedHashMap<>(); private Map<String, Object> attributes = new LinkedHashMap<>();
private String authorizationRequestUri; private @Nullable String authorizationRequestUri;
private Function<UriBuilder, URI> authorizationRequestUriFunction = (builder) -> builder.build(); private Function<UriBuilder, URI> authorizationRequestUriFunction = (builder) -> builder.build();
@ -341,20 +346,20 @@ public class OAuth2AuthorizationRequest implements Serializable {
/** /**
* Sets the uri for the redirection endpoint. * Sets the uri for the redirection endpoint.
* @param redirectUri the uri for the redirection endpoint * @param redirectUri the uri for the redirection endpoint, may be {@code null}
* @return the {@link AbstractBuilder} * @return the {@link AbstractBuilder}
*/ */
public B redirectUri(String redirectUri) { public B redirectUri(@Nullable String redirectUri) {
this.redirectUri = redirectUri; this.redirectUri = redirectUri;
return getThis(); return getThis();
} }
/** /**
* Sets the scope(s). * Sets the scope(s).
* @param scope the scope(s) * @param scope the scope(s), may be {@code null}
* @return the {@link AbstractBuilder} * @return the {@link AbstractBuilder}
*/ */
public B scope(String... scope) { public B scope(@Nullable String... scope) {
if (scope != null && scope.length > 0) { if (scope != null && scope.length > 0) {
return scopes(new LinkedHashSet<>(Arrays.asList(scope))); return scopes(new LinkedHashSet<>(Arrays.asList(scope)));
} }
@ -363,20 +368,20 @@ public class OAuth2AuthorizationRequest implements Serializable {
/** /**
* Sets the scope(s). * Sets the scope(s).
* @param scopes the scope(s) * @param scopes the scope(s), may be {@code null}
* @return the {@link AbstractBuilder} * @return the {@link AbstractBuilder}
*/ */
public B scopes(Set<String> scopes) { public B scopes(@Nullable Set<String> scopes) {
this.scopes = scopes; this.scopes = scopes;
return getThis(); return getThis();
} }
/** /**
* Sets the state. * Sets the state.
* @param state the state * @param state the state, may be {@code null}
* @return the {@link AbstractBuilder} * @return the {@link AbstractBuilder}
*/ */
public B state(String state) { public B state(@Nullable String state) {
this.state = state; this.state = state;
return getThis(); return getThis();
} }
@ -502,7 +507,9 @@ public class OAuth2AuthorizationRequest implements Serializable {
queryParams.set(key, encodeQueryParam(String.valueOf(v))); queryParams.set(key, encodeQueryParam(String.valueOf(v)));
} }
}); });
UriBuilder uriBuilder = this.uriBuilderFactory.uriString(this.authorizationUri).queryParams(queryParams); String uri = this.authorizationUri;
Assert.notNull(uri, "authorizationUri cannot be null");
UriBuilder uriBuilder = this.uriBuilderFactory.uriString(uri).queryParams(queryParams);
return this.authorizationRequestUriFunction.apply(uriBuilder).toString(); return this.authorizationRequestUriFunction.apply(uriBuilder).toString();
} }

39
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationResponse.java

@ -19,6 +19,8 @@ package org.springframework.security.oauth2.core.endpoint;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -39,13 +41,13 @@ public final class OAuth2AuthorizationResponse implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 620L; private static final long serialVersionUID = 620L;
private String redirectUri; private @Nullable String redirectUri;
private String state; private @Nullable String state;
private String code; private @Nullable String code;
private OAuth2Error error; private @Nullable OAuth2Error error;
private OAuth2AuthorizationResponse() { private OAuth2AuthorizationResponse() {
} }
@ -55,22 +57,24 @@ public final class OAuth2AuthorizationResponse implements Serializable {
* @return the uri where the response was redirected to * @return the uri where the response was redirected to
*/ */
public String getRedirectUri() { public String getRedirectUri() {
Assert.notNull(this.redirectUri, "redirectUri cannot be null");
return this.redirectUri; return this.redirectUri;
} }
/** /**
* Returns the state. * Returns the state, or {@code null} if not present.
* @return the state * @return the state, or {@code null}
*/ */
public String getState() { public @Nullable String getState() {
return this.state; return this.state;
} }
/** /**
* Returns the authorization code. * Returns the authorization code, or {@code null} if the response is an error
* @return the authorization code * response.
* @return the authorization code, or {@code null}
*/ */
public String getCode() { public @Nullable String getCode() {
return this.code; return this.code;
} }
@ -80,7 +84,7 @@ public final class OAuth2AuthorizationResponse implements Serializable {
* @return the {@link OAuth2Error} if the Authorization Request failed, otherwise * @return the {@link OAuth2Error} if the Authorization Request failed, otherwise
* {@code null} * {@code null}
*/ */
public OAuth2Error getError() { public @Nullable OAuth2Error getError() {
return this.error; return this.error;
} }
@ -127,17 +131,17 @@ public final class OAuth2AuthorizationResponse implements Serializable {
*/ */
public static final class Builder { public static final class Builder {
private String redirectUri; private @Nullable String redirectUri;
private String state; private @Nullable String state;
private String code; private @Nullable String code;
private String errorCode; private @Nullable String errorCode;
private String errorDescription; private @Nullable String errorDescription;
private String errorUri; private @Nullable String errorUri;
private Builder() { private Builder() {
} }
@ -218,6 +222,7 @@ public final class OAuth2AuthorizationResponse implements Serializable {
authorizationResponse.code = this.code; authorizationResponse.code = this.code;
} }
else { else {
Assert.hasText(this.errorCode, "errorCode cannot be empty when code is not present");
authorizationResponse.error = new OAuth2Error(this.errorCode, this.errorDescription, this.errorUri); authorizationResponse.error = new OAuth2Error(this.errorCode, this.errorDescription, this.errorUri);
} }
return authorizationResponse; return authorizationResponse;

33
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2DeviceAuthorizationResponse.java

@ -21,6 +21,8 @@ import java.time.temporal.ChronoUnit;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.OAuth2DeviceCode; import org.springframework.security.oauth2.core.OAuth2DeviceCode;
import org.springframework.security.oauth2.core.OAuth2UserCode; import org.springframework.security.oauth2.core.OAuth2UserCode;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -38,17 +40,17 @@ import org.springframework.util.CollectionUtils;
*/ */
public final class OAuth2DeviceAuthorizationResponse { public final class OAuth2DeviceAuthorizationResponse {
private OAuth2DeviceCode deviceCode; private @Nullable OAuth2DeviceCode deviceCode;
private OAuth2UserCode userCode; private @Nullable OAuth2UserCode userCode;
private String verificationUri; private @Nullable String verificationUri;
private String verificationUriComplete; private @Nullable String verificationUriComplete;
private long interval; private long interval;
private Map<String, Object> additionalParameters; private @Nullable Map<String, Object> additionalParameters;
private OAuth2DeviceAuthorizationResponse() { private OAuth2DeviceAuthorizationResponse() {
} }
@ -58,6 +60,7 @@ public final class OAuth2DeviceAuthorizationResponse {
* @return the {@link OAuth2DeviceCode} * @return the {@link OAuth2DeviceCode}
*/ */
public OAuth2DeviceCode getDeviceCode() { public OAuth2DeviceCode getDeviceCode() {
Assert.notNull(this.deviceCode, "deviceCode cannot be null");
return this.deviceCode; return this.deviceCode;
} }
@ -66,6 +69,7 @@ public final class OAuth2DeviceAuthorizationResponse {
* @return the {@link OAuth2UserCode} * @return the {@link OAuth2UserCode}
*/ */
public OAuth2UserCode getUserCode() { public OAuth2UserCode getUserCode() {
Assert.notNull(this.userCode, "userCode cannot be null");
return this.userCode; return this.userCode;
} }
@ -74,14 +78,16 @@ public final class OAuth2DeviceAuthorizationResponse {
* @return the end-user verification URI * @return the end-user verification URI
*/ */
public String getVerificationUri() { public String getVerificationUri() {
Assert.notNull(this.verificationUri, "verificationUri cannot be null");
return this.verificationUri; return this.verificationUri;
} }
/** /**
* Returns the end-user verification URI that includes the user code. * Returns the end-user verification URI that includes the user code, or {@code null}
* @return the end-user verification URI that includes the user code * if not present.
* @return the end-user verification URI that includes the user code, or {@code null}
*/ */
public String getVerificationUriComplete() { public @Nullable String getVerificationUriComplete() {
return this.verificationUriComplete; return this.verificationUriComplete;
} }
@ -100,6 +106,7 @@ public final class OAuth2DeviceAuthorizationResponse {
* empty. * empty.
*/ */
public Map<String, Object> getAdditionalParameters() { public Map<String, Object> getAdditionalParameters() {
Assert.notNull(this.additionalParameters, "additionalParameters cannot be null");
return this.additionalParameters; return this.additionalParameters;
} }
@ -138,15 +145,15 @@ public final class OAuth2DeviceAuthorizationResponse {
private final String userCode; private final String userCode;
private String verificationUri; private @Nullable String verificationUri;
private String verificationUriComplete; private @Nullable String verificationUriComplete;
private long expiresIn; private long expiresIn;
private long interval; private long interval;
private Map<String, Object> additionalParameters; private @Nullable Map<String, Object> additionalParameters;
private Builder(OAuth2DeviceCode deviceCode, OAuth2UserCode userCode) { private Builder(OAuth2DeviceCode deviceCode, OAuth2UserCode userCode) {
this.deviceCode = deviceCode.getTokenValue(); this.deviceCode = deviceCode.getTokenValue();
@ -172,10 +179,10 @@ public final class OAuth2DeviceAuthorizationResponse {
/** /**
* Sets the end-user verification URI that includes the user code. * Sets the end-user verification URI that includes the user code.
* @param verificationUriComplete the end-user verification URI that includes the * @param verificationUriComplete the end-user verification URI that includes the
* user code * user code, may be {@code null}
* @return the {@link Builder} * @return the {@link Builder}
*/ */
public Builder verificationUriComplete(String verificationUriComplete) { public Builder verificationUriComplete(@Nullable String verificationUriComplete) {
this.verificationUriComplete = verificationUriComplete; this.verificationUriComplete = verificationUriComplete;
return this; return this;
} }

3
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/package-info.java

@ -18,4 +18,7 @@
* Support classes that model the OAuth 2.0 Request and Response messages from the * Support classes that model the OAuth 2.0 Request and Response messages from the
* Authorization Endpoint and Token Endpoint. * Authorization Endpoint and Token Endpoint.
*/ */
@NullMarked
package org.springframework.security.oauth2.core.endpoint; package org.springframework.security.oauth2.core.endpoint;
import org.jspecify.annotations.NullMarked;

4
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/HttpMessageConverters.java

@ -16,6 +16,8 @@
package org.springframework.security.oauth2.core.http.converter; package org.springframework.security.oauth2.core.http.converter;
import org.jspecify.annotations.Nullable;
import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter;
@ -54,7 +56,7 @@ final class HttpMessageConverters {
} }
@SuppressWarnings("removal") @SuppressWarnings("removal")
static GenericHttpMessageConverter<Object> getJsonMessageConverter() { static @Nullable GenericHttpMessageConverter<Object> getJsonMessageConverter() {
if (jacksonPresent) { if (jacksonPresent) {
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter()); return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
} }

6
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java

@ -52,8 +52,7 @@ public class OAuth2AccessTokenResponseHttpMessageConverter
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() {
}; };
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters private final GenericHttpMessageConverter<Object> jsonMessageConverter;
.getJsonMessageConverter();
private Converter<Map<String, Object>, OAuth2AccessTokenResponse> accessTokenResponseConverter = new DefaultMapOAuth2AccessTokenResponseConverter(); private Converter<Map<String, Object>, OAuth2AccessTokenResponse> accessTokenResponseConverter = new DefaultMapOAuth2AccessTokenResponseConverter();
@ -61,6 +60,9 @@ public class OAuth2AccessTokenResponseHttpMessageConverter
public OAuth2AccessTokenResponseHttpMessageConverter() { public OAuth2AccessTokenResponseHttpMessageConverter() {
super(DEFAULT_CHARSET, MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); super(DEFAULT_CHARSET, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
GenericHttpMessageConverter<Object> converter = HttpMessageConverters.getJsonMessageConverter();
Assert.notNull(converter, "Unable to locate a supported JSON message converter");
this.jsonMessageConverter = converter;
} }
@Override @Override

23
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2DeviceAuthorizationResponseHttpMessageConverter.java

@ -25,6 +25,8 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpInputMessage;
@ -56,13 +58,18 @@ public class OAuth2DeviceAuthorizationResponseHttpMessageConverter
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() {
}; };
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters private final GenericHttpMessageConverter<Object> jsonMessageConverter;
.getJsonMessageConverter();
private Converter<Map<String, Object>, OAuth2DeviceAuthorizationResponse> deviceAuthorizationResponseConverter = new DefaultMapOAuth2DeviceAuthorizationResponseConverter(); private Converter<Map<String, Object>, OAuth2DeviceAuthorizationResponse> deviceAuthorizationResponseConverter = new DefaultMapOAuth2DeviceAuthorizationResponseConverter();
private Converter<OAuth2DeviceAuthorizationResponse, Map<String, Object>> deviceAuthorizationResponseParametersConverter = new DefaultOAuth2DeviceAuthorizationResponseMapConverter(); private Converter<OAuth2DeviceAuthorizationResponse, Map<String, Object>> deviceAuthorizationResponseParametersConverter = new DefaultOAuth2DeviceAuthorizationResponseMapConverter();
public OAuth2DeviceAuthorizationResponseHttpMessageConverter() {
GenericHttpMessageConverter<Object> converter = HttpMessageConverters.getJsonMessageConverter();
Assert.notNull(converter, "Unable to locate a supported JSON message converter");
this.jsonMessageConverter = converter;
}
@Override @Override
protected boolean supports(Class<?> clazz) { protected boolean supports(Class<?> clazz) {
return OAuth2DeviceAuthorizationResponse.class.isAssignableFrom(clazz); return OAuth2DeviceAuthorizationResponse.class.isAssignableFrom(clazz);
@ -139,8 +146,18 @@ public class OAuth2DeviceAuthorizationResponseHttpMessageConverter
@Override @Override
public OAuth2DeviceAuthorizationResponse convert(Map<String, Object> parameters) { public OAuth2DeviceAuthorizationResponse convert(Map<String, Object> parameters) {
String deviceCode = getParameterValue(parameters, OAuth2ParameterNames.DEVICE_CODE); String deviceCode = getParameterValue(parameters, OAuth2ParameterNames.DEVICE_CODE);
if (deviceCode == null) {
throw new IllegalArgumentException("Missing required parameter: " + OAuth2ParameterNames.DEVICE_CODE);
}
String userCode = getParameterValue(parameters, OAuth2ParameterNames.USER_CODE); String userCode = getParameterValue(parameters, OAuth2ParameterNames.USER_CODE);
if (userCode == null) {
throw new IllegalArgumentException("Missing required parameter: " + OAuth2ParameterNames.USER_CODE);
}
String verificationUri = getParameterValue(parameters, OAuth2ParameterNames.VERIFICATION_URI); String verificationUri = getParameterValue(parameters, OAuth2ParameterNames.VERIFICATION_URI);
if (verificationUri == null) {
throw new IllegalArgumentException(
"Missing required parameter: " + OAuth2ParameterNames.VERIFICATION_URI);
}
String verificationUriComplete = getParameterValue(parameters, String verificationUriComplete = getParameterValue(parameters,
OAuth2ParameterNames.VERIFICATION_URI_COMPLETE); OAuth2ParameterNames.VERIFICATION_URI_COMPLETE);
long expiresIn = getParameterValue(parameters, OAuth2ParameterNames.EXPIRES_IN, 0L); long expiresIn = getParameterValue(parameters, OAuth2ParameterNames.EXPIRES_IN, 0L);
@ -162,7 +179,7 @@ public class OAuth2DeviceAuthorizationResponseHttpMessageConverter
// @formatter:on // @formatter:on
} }
private static String getParameterValue(Map<String, Object> parameters, String parameterName) { private static @Nullable String getParameterValue(Map<String, Object> parameters, String parameterName) {
Object obj = parameters.get(parameterName); Object obj = parameters.get(parameterName);
return (obj != null) ? obj.toString() : null; return (obj != null) ? obj.toString() : null;
} }

7
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java

@ -52,8 +52,7 @@ public class OAuth2ErrorHttpMessageConverter extends AbstractHttpMessageConverte
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() {
}; };
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters private final GenericHttpMessageConverter<Object> jsonMessageConverter;
.getJsonMessageConverter();
protected Converter<Map<String, String>, OAuth2Error> errorConverter = new OAuth2ErrorConverter(); protected Converter<Map<String, String>, OAuth2Error> errorConverter = new OAuth2ErrorConverter();
@ -61,6 +60,9 @@ public class OAuth2ErrorHttpMessageConverter extends AbstractHttpMessageConverte
public OAuth2ErrorHttpMessageConverter() { public OAuth2ErrorHttpMessageConverter() {
super(DEFAULT_CHARSET, MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); super(DEFAULT_CHARSET, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
GenericHttpMessageConverter<Object> converter = HttpMessageConverters.getJsonMessageConverter();
Assert.notNull(converter, "Unable to locate a supported JSON message converter");
this.jsonMessageConverter = converter;
} }
@Override @Override
@ -133,6 +135,7 @@ public class OAuth2ErrorHttpMessageConverter extends AbstractHttpMessageConverte
@Override @Override
public OAuth2Error convert(Map<String, String> parameters) { public OAuth2Error convert(Map<String, String> parameters) {
String errorCode = parameters.get(OAuth2ParameterNames.ERROR); String errorCode = parameters.get(OAuth2ParameterNames.ERROR);
Assert.hasText(errorCode, "errorCode cannot be empty");
String errorDescription = parameters.get(OAuth2ParameterNames.ERROR_DESCRIPTION); String errorDescription = parameters.get(OAuth2ParameterNames.ERROR_DESCRIPTION);
String errorUri = parameters.get(OAuth2ParameterNames.ERROR_URI); String errorUri = parameters.get(OAuth2ParameterNames.ERROR_URI);
return new OAuth2Error(errorCode, errorDescription, errorUri); return new OAuth2Error(errorCode, errorDescription, errorUri);

23
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/package-info.java

@ -0,0 +1,23 @@
/*
* Copyright 2004-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* HTTP message converters for OAuth 2.0 and OpenID Connect protocol messages.
*/
@NullMarked
package org.springframework.security.oauth2.core.http.converter;
import org.jspecify.annotations.NullMarked;

23
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/package-info.java

@ -0,0 +1,23 @@
/*
* Copyright 2004-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support classes that provide HTTP message conversion for OAuth 2.0 and OpenID Connect.
*/
@NullMarked
package org.springframework.security.oauth2.core.http;
import org.jspecify.annotations.NullMarked;

41
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/AddressStandardClaim.java

@ -16,6 +16,8 @@
package org.springframework.security.oauth2.core.oidc; package org.springframework.security.oauth2.core.oidc;
import org.jspecify.annotations.Nullable;
/** /**
* The Address Claim represents a physical mailing address defined by the OpenID Connect * The Address Claim represents a physical mailing address defined by the OpenID Connect
* Core 1.0 specification that can be returned either in the UserInfo Response or the ID * Core 1.0 specification that can be returned either in the UserInfo Response or the ID
@ -34,40 +36,43 @@ package org.springframework.security.oauth2.core.oidc;
public interface AddressStandardClaim { public interface AddressStandardClaim {
/** /**
* Returns the full mailing address, formatted for display. * Returns the full mailing address, formatted for display, or {@code null} if it does
* @return the full mailing address * not exist.
* @return the full mailing address, or {@code null} if it does not exist
*/ */
String getFormatted(); @Nullable String getFormatted();
/** /**
* Returns the full street address, which may include house number, street name, P.O. * Returns the full street address, which may include house number, street name, P.O.
* Box, etc. * Box, etc., or {@code null} if it does not exist.
* @return the full street address * @return the full street address, or {@code null} if it does not exist
*/ */
String getStreetAddress(); @Nullable String getStreetAddress();
/** /**
* Returns the city or locality. * Returns the city or locality, or {@code null} if it does not exist.
* @return the city or locality * @return the city or locality, or {@code null} if it does not exist
*/ */
String getLocality(); @Nullable String getLocality();
/** /**
* Returns the state, province, prefecture, or region. * Returns the state, province, prefecture, or region, or {@code null} if it does not
* @return the state, province, prefecture, or region * exist.
* @return the state, province, prefecture, or region, or {@code null} if it does not
* exist
*/ */
String getRegion(); @Nullable String getRegion();
/** /**
* Returns the zip code or postal code. * Returns the zip code or postal code, or {@code null} if it does not exist.
* @return the zip code or postal code * @return the zip code or postal code, or {@code null} if it does not exist
*/ */
String getPostalCode(); @Nullable String getPostalCode();
/** /**
* Returns the country. * Returns the country, or {@code null} if it does not exist.
* @return the country * @return the country, or {@code null} if it does not exist
*/ */
String getCountry(); @Nullable String getCountry();
} }

62
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/DefaultAddressStandardClaim.java

@ -18,6 +18,8 @@ package org.springframework.security.oauth2.core.oidc;
import java.util.Map; import java.util.Map;
import org.jspecify.annotations.Nullable;
/** /**
* The default implementation of an {@link AddressStandardClaim Address Claim}. * The default implementation of an {@link AddressStandardClaim Address Claim}.
* *
@ -27,48 +29,48 @@ import java.util.Map;
*/ */
public final class DefaultAddressStandardClaim implements AddressStandardClaim { public final class DefaultAddressStandardClaim implements AddressStandardClaim {
private String formatted; private @Nullable String formatted;
private String streetAddress; private @Nullable String streetAddress;
private String locality; private @Nullable String locality;
private String region; private @Nullable String region;
private String postalCode; private @Nullable String postalCode;
private String country; private @Nullable String country;
private DefaultAddressStandardClaim() { private DefaultAddressStandardClaim() {
} }
@Override @Override
public String getFormatted() { public @Nullable String getFormatted() {
return this.formatted; return this.formatted;
} }
@Override @Override
public String getStreetAddress() { public @Nullable String getStreetAddress() {
return this.streetAddress; return this.streetAddress;
} }
@Override @Override
public String getLocality() { public @Nullable String getLocality() {
return this.locality; return this.locality;
} }
@Override @Override
public String getRegion() { public @Nullable String getRegion() {
return this.region; return this.region;
} }
@Override @Override
public String getPostalCode() { public @Nullable String getPostalCode() {
return this.postalCode; return this.postalCode;
} }
@Override @Override
public String getCountry() { public @Nullable String getCountry() {
return this.country; return this.country;
} }
@ -131,17 +133,17 @@ public final class DefaultAddressStandardClaim implements AddressStandardClaim {
private static final String COUNTRY_FIELD_NAME = "country"; private static final String COUNTRY_FIELD_NAME = "country";
private String formatted; private @Nullable String formatted;
private String streetAddress; private @Nullable String streetAddress;
private String locality; private @Nullable String locality;
private String region; private @Nullable String region;
private String postalCode; private @Nullable String postalCode;
private String country; private @Nullable String country;
/** /**
* Default constructor. * Default constructor.
@ -165,10 +167,10 @@ public final class DefaultAddressStandardClaim implements AddressStandardClaim {
/** /**
* Sets the full mailing address, formatted for display. * Sets the full mailing address, formatted for display.
* @param formatted the full mailing address * @param formatted the full mailing address, may be {@code null}
* @return the {@link Builder} * @return the {@link Builder}
*/ */
public Builder formatted(String formatted) { public Builder formatted(@Nullable String formatted) {
this.formatted = formatted; this.formatted = formatted;
return this; return this;
} }
@ -176,50 +178,50 @@ public final class DefaultAddressStandardClaim implements AddressStandardClaim {
/** /**
* Sets the full street address, which may include house number, street name, P.O. * Sets the full street address, which may include house number, street name, P.O.
* Box, etc. * Box, etc.
* @param streetAddress the full street address * @param streetAddress the full street address, may be {@code null}
* @return the {@link Builder} * @return the {@link Builder}
*/ */
public Builder streetAddress(String streetAddress) { public Builder streetAddress(@Nullable String streetAddress) {
this.streetAddress = streetAddress; this.streetAddress = streetAddress;
return this; return this;
} }
/** /**
* Sets the city or locality. * Sets the city or locality.
* @param locality the city or locality * @param locality the city or locality, may be {@code null}
* @return the {@link Builder} * @return the {@link Builder}
*/ */
public Builder locality(String locality) { public Builder locality(@Nullable String locality) {
this.locality = locality; this.locality = locality;
return this; return this;
} }
/** /**
* Sets the state, province, prefecture, or region. * Sets the state, province, prefecture, or region.
* @param region the state, province, prefecture, or region * @param region the state, province, prefecture, or region, may be {@code null}
* @return the {@link Builder} * @return the {@link Builder}
*/ */
public Builder region(String region) { public Builder region(@Nullable String region) {
this.region = region; this.region = region;
return this; return this;
} }
/** /**
* Sets the zip code or postal code. * Sets the zip code or postal code.
* @param postalCode the zip code or postal code * @param postalCode the zip code or postal code, may be {@code null}
* @return the {@link Builder} * @return the {@link Builder}
*/ */
public Builder postalCode(String postalCode) { public Builder postalCode(@Nullable String postalCode) {
this.postalCode = postalCode; this.postalCode = postalCode;
return this; return this;
} }
/** /**
* Sets the country. * Sets the country.
* @param country the country * @param country the country, may be {@code null}
* @return the {@link Builder} * @return the {@link Builder}
*/ */
public Builder country(String country) { public Builder country(@Nullable String country) {
this.country = country; this.country = country;
return this; return this;
} }

90
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/IdTokenClaimAccessor.java

@ -20,6 +20,8 @@ import java.net.URL;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.ClaimAccessor;
/** /**
@ -43,101 +45,117 @@ import org.springframework.security.oauth2.core.ClaimAccessor;
public interface IdTokenClaimAccessor extends StandardClaimAccessor { public interface IdTokenClaimAccessor extends StandardClaimAccessor {
/** /**
* Returns the Issuer identifier {@code (iss)}. * Returns the Issuer identifier {@code (iss)}, or {@code null} if it does not exist.
* @return the Issuer identifier * @return the Issuer identifier, or {@code null} if it does not exist
*/ */
default URL getIssuer() { default @Nullable URL getIssuer() {
return this.getClaimAsURL(IdTokenClaimNames.ISS); return this.getClaimAsURL(IdTokenClaimNames.ISS);
} }
/** /**
* Returns the Subject identifier {@code (sub)}. * Returns the Subject identifier {@code (sub)}, or {@code null} if it does not exist.
* @return the Subject identifier * @return the Subject identifier, or {@code null} if it does not exist
*/ */
@Override @Override
default String getSubject() { default @Nullable String getSubject() {
return this.getClaimAsString(IdTokenClaimNames.SUB); return this.getClaimAsString(IdTokenClaimNames.SUB);
} }
/** /**
* Returns the Audience(s) {@code (aud)} that this ID Token is intended for. * Returns the Audience(s) {@code (aud)} that this ID Token is intended for, or
* @return the Audience(s) that this ID Token is intended for * {@code null} if it does not exist.
* @return the Audience(s) that this ID Token is intended for, or {@code null} if it
* does not exist
*/ */
default List<String> getAudience() { default @Nullable List<String> getAudience() {
return this.getClaimAsStringList(IdTokenClaimNames.AUD); return this.getClaimAsStringList(IdTokenClaimNames.AUD);
} }
/** /**
* Returns the Expiration time {@code (exp)} on or after which the ID Token MUST NOT * Returns the Expiration time {@code (exp)} on or after which the ID Token MUST NOT
* be accepted. * be accepted, or {@code null} if it does not exist.
* @return the Expiration time on or after which the ID Token MUST NOT be accepted * @return the Expiration time on or after which the ID Token MUST NOT be accepted, or
* {@code null} if it does not exist
*/ */
default Instant getExpiresAt() { default @Nullable Instant getExpiresAt() {
return this.getClaimAsInstant(IdTokenClaimNames.EXP); return this.getClaimAsInstant(IdTokenClaimNames.EXP);
} }
/** /**
* Returns the time at which the ID Token was issued {@code (iat)}. * Returns the time at which the ID Token was issued {@code (iat)}, or {@code null} if
* @return the time at which the ID Token was issued * it does not exist.
* @return the time at which the ID Token was issued, or {@code null} if it does not
* exist
*/ */
default Instant getIssuedAt() { default @Nullable Instant getIssuedAt() {
return this.getClaimAsInstant(IdTokenClaimNames.IAT); return this.getClaimAsInstant(IdTokenClaimNames.IAT);
} }
/** /**
* Returns the time when the End-User authentication occurred {@code (auth_time)}. * Returns the time when the End-User authentication occurred {@code (auth_time)}, or
* @return the time when the End-User authentication occurred * {@code null} if it does not exist.
* @return the time when the End-User authentication occurred, or {@code null} if it
* does not exist
*/ */
default Instant getAuthenticatedAt() { default @Nullable Instant getAuthenticatedAt() {
return this.getClaimAsInstant(IdTokenClaimNames.AUTH_TIME); return this.getClaimAsInstant(IdTokenClaimNames.AUTH_TIME);
} }
/** /**
* Returns a {@code String} value {@code (nonce)} used to associate a Client session * Returns a {@code String} value {@code (nonce)} used to associate a Client session
* with an ID Token, and to mitigate replay attacks. * with an ID Token, and to mitigate replay attacks, or {@code null} if it does not
* @return the nonce used to associate a Client session with an ID Token * exist.
* @return the nonce used to associate a Client session with an ID Token, or
* {@code null} if it does not exist
*/ */
default String getNonce() { default @Nullable String getNonce() {
return this.getClaimAsString(IdTokenClaimNames.NONCE); return this.getClaimAsString(IdTokenClaimNames.NONCE);
} }
/** /**
* Returns the Authentication Context Class Reference {@code (acr)}. * Returns the Authentication Context Class Reference {@code (acr)}, or {@code null}
* @return the Authentication Context Class Reference * if it does not exist.
* @return the Authentication Context Class Reference, or {@code null} if it does not
* exist
*/ */
default String getAuthenticationContextClass() { default @Nullable String getAuthenticationContextClass() {
return this.getClaimAsString(IdTokenClaimNames.ACR); return this.getClaimAsString(IdTokenClaimNames.ACR);
} }
/** /**
* Returns the Authentication Methods References {@code (amr)}. * Returns the Authentication Methods References {@code (amr)}, or {@code null} if it
* @return the Authentication Methods References * does not exist.
* @return the Authentication Methods References, or {@code null} if it does not exist
*/ */
default List<String> getAuthenticationMethods() { default @Nullable List<String> getAuthenticationMethods() {
return this.getClaimAsStringList(IdTokenClaimNames.AMR); return this.getClaimAsStringList(IdTokenClaimNames.AMR);
} }
/** /**
* Returns the Authorized party {@code (azp)} to which the ID Token was issued. * Returns the Authorized party {@code (azp)} to which the ID Token was issued, or
* @return the Authorized party to which the ID Token was issued * {@code null} if it does not exist.
* @return the Authorized party to which the ID Token was issued, or {@code null} if
* it does not exist
*/ */
default String getAuthorizedParty() { default @Nullable String getAuthorizedParty() {
return this.getClaimAsString(IdTokenClaimNames.AZP); return this.getClaimAsString(IdTokenClaimNames.AZP);
} }
/** /**
* Returns the Access Token hash value {@code (at_hash)}. * Returns the Access Token hash value {@code (at_hash)}, or {@code null} if it does
* @return the Access Token hash value * not exist.
* @return the Access Token hash value, or {@code null} if it does not exist
*/ */
default String getAccessTokenHash() { default @Nullable String getAccessTokenHash() {
return this.getClaimAsString(IdTokenClaimNames.AT_HASH); return this.getClaimAsString(IdTokenClaimNames.AT_HASH);
} }
/** /**
* Returns the Authorization Code hash value {@code (c_hash)}. * Returns the Authorization Code hash value {@code (c_hash)}, or {@code null} if it
* @return the Authorization Code hash value * does not exist.
* @return the Authorization Code hash value, or {@code null} if it does not exist
*/ */
default String getAuthorizationCodeHash() { default @Nullable String getAuthorizationCodeHash() {
return this.getClaimAsString(IdTokenClaimNames.C_HASH); return this.getClaimAsString(IdTokenClaimNames.C_HASH);
} }

18
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/OidcIdToken.java

@ -25,6 +25,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.AbstractOAuth2Token; import org.springframework.security.oauth2.core.AbstractOAuth2Token;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -57,12 +59,14 @@ public class OidcIdToken extends AbstractOAuth2Token implements IdTokenClaimAcce
/** /**
* Constructs a {@code OidcIdToken} using the provided parameters. * Constructs a {@code OidcIdToken} using the provided parameters.
* @param tokenValue the ID Token value * @param tokenValue the ID Token value
* @param issuedAt the time at which the ID Token was issued {@code (iat)} * @param issuedAt the time at which the ID Token was issued {@code (iat)}, may be
* {@code null}
* @param expiresAt the expiration time {@code (exp)} on or after which the ID Token * @param expiresAt the expiration time {@code (exp)} on or after which the ID Token
* MUST NOT be accepted * MUST NOT be accepted, may be {@code null}
* @param claims the claims about the authentication of the End-User * @param claims the claims about the authentication of the End-User
*/ */
public OidcIdToken(String tokenValue, Instant issuedAt, Instant expiresAt, Map<String, Object> claims) { public OidcIdToken(String tokenValue, @Nullable Instant issuedAt, @Nullable Instant expiresAt,
Map<String, Object> claims) {
super(tokenValue, issuedAt, expiresAt); super(tokenValue, issuedAt, expiresAt);
Assert.notEmpty(claims, "claims cannot be empty"); Assert.notEmpty(claims, "claims cannot be empty");
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims)); this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
@ -246,12 +250,14 @@ public class OidcIdToken extends AbstractOAuth2Token implements IdTokenClaimAcce
* @return The constructed {@link OidcIdToken} * @return The constructed {@link OidcIdToken}
*/ */
public OidcIdToken build() { public OidcIdToken build() {
Instant iat = toInstant(this.claims.get(IdTokenClaimNames.IAT)); Object iatObj = this.claims.get(IdTokenClaimNames.IAT);
Instant exp = toInstant(this.claims.get(IdTokenClaimNames.EXP)); Object expObj = this.claims.get(IdTokenClaimNames.EXP);
Instant iat = toInstant(iatObj);
Instant exp = toInstant(expObj);
return new OidcIdToken(this.tokenValue, iat, exp, this.claims); return new OidcIdToken(this.tokenValue, iat, exp, this.claims);
} }
private Instant toInstant(Object timestamp) { private @Nullable Instant toInstant(@Nullable Object timestamp) {
if (timestamp != null) { if (timestamp != null) {
Assert.isInstanceOf(Instant.class, timestamp, "timestamps must be of type Instant"); Assert.isInstanceOf(Instant.class, timestamp, "timestamps must be of type Instant");
} }

132
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/StandardClaimAccessor.java

@ -19,6 +19,8 @@ package org.springframework.security.oauth2.core.oidc;
import java.time.Instant; import java.time.Instant;
import java.util.Map; import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -41,152 +43,166 @@ import org.springframework.util.CollectionUtils;
public interface StandardClaimAccessor extends ClaimAccessor { public interface StandardClaimAccessor extends ClaimAccessor {
/** /**
* Returns the Subject identifier {@code (sub)}. * Returns the Subject identifier {@code (sub)}, or {@code null} if it does not exist.
* @return the Subject identifier * @return the Subject identifier, or {@code null} if it does not exist
*/ */
default String getSubject() { default @Nullable String getSubject() {
return this.getClaimAsString(StandardClaimNames.SUB); return this.getClaimAsString(StandardClaimNames.SUB);
} }
/** /**
* Returns the user's full name {@code (name)} in displayable form. * Returns the user's full name {@code (name)} in displayable form, or {@code null} if
* @return the user's full name * it does not exist.
* @return the user's full name, or {@code null} if it does not exist
*/ */
default String getFullName() { default @Nullable String getFullName() {
return this.getClaimAsString(StandardClaimNames.NAME); return this.getClaimAsString(StandardClaimNames.NAME);
} }
/** /**
* Returns the user's given name(s) or first name(s) {@code (given_name)}. * Returns the user's given name(s) or first name(s) {@code (given_name)}, or
* @return the user's given name(s) * {@code null} if it does not exist.
* @return the user's given name(s), or {@code null} if it does not exist
*/ */
default String getGivenName() { default @Nullable String getGivenName() {
return this.getClaimAsString(StandardClaimNames.GIVEN_NAME); return this.getClaimAsString(StandardClaimNames.GIVEN_NAME);
} }
/** /**
* Returns the user's surname(s) or last name(s) {@code (family_name)}. * Returns the user's surname(s) or last name(s) {@code (family_name)}, or
* @return the user's family names(s) * {@code null} if it does not exist.
* @return the user's family names(s), or {@code null} if it does not exist
*/ */
default String getFamilyName() { default @Nullable String getFamilyName() {
return this.getClaimAsString(StandardClaimNames.FAMILY_NAME); return this.getClaimAsString(StandardClaimNames.FAMILY_NAME);
} }
/** /**
* Returns the user's middle name(s) {@code (middle_name)}. * Returns the user's middle name(s) {@code (middle_name)}, or {@code null} if it does
* @return the user's middle name(s) * not exist.
* @return the user's middle name(s), or {@code null} if it does not exist
*/ */
default String getMiddleName() { default @Nullable String getMiddleName() {
return this.getClaimAsString(StandardClaimNames.MIDDLE_NAME); return this.getClaimAsString(StandardClaimNames.MIDDLE_NAME);
} }
/** /**
* Returns the user's nick name {@code (nickname)} that may or may not be the same as * Returns the user's nick name {@code (nickname)} that may or may not be the same as
* the {@code (given_name)}. * the {@code (given_name)}, or {@code null} if it does not exist.
* @return the user's nick name * @return the user's nick name, or {@code null} if it does not exist
*/ */
default String getNickName() { default @Nullable String getNickName() {
return this.getClaimAsString(StandardClaimNames.NICKNAME); return this.getClaimAsString(StandardClaimNames.NICKNAME);
} }
/** /**
* Returns the preferred username {@code (preferred_username)} that the user wishes to * Returns the preferred username {@code (preferred_username)} that the user wishes to
* be referred to. * be referred to, or {@code null} if it does not exist.
* @return the user's preferred user name * @return the user's preferred user name, or {@code null} if it does not exist
*/ */
default String getPreferredUsername() { default @Nullable String getPreferredUsername() {
return this.getClaimAsString(StandardClaimNames.PREFERRED_USERNAME); return this.getClaimAsString(StandardClaimNames.PREFERRED_USERNAME);
} }
/** /**
* Returns the URL of the user's profile page {@code (profile)}. * Returns the URL of the user's profile page {@code (profile)}, or {@code null} if it
* @return the URL of the user's profile page * does not exist.
* @return the URL of the user's profile page, or {@code null} if it does not exist
*/ */
default String getProfile() { default @Nullable String getProfile() {
return this.getClaimAsString(StandardClaimNames.PROFILE); return this.getClaimAsString(StandardClaimNames.PROFILE);
} }
/** /**
* Returns the URL of the user's profile picture {@code (picture)}. * Returns the URL of the user's profile picture {@code (picture)}, or {@code null} if
* @return the URL of the user's profile picture * it does not exist.
* @return the URL of the user's profile picture, or {@code null} if it does not exist
*/ */
default String getPicture() { default @Nullable String getPicture() {
return this.getClaimAsString(StandardClaimNames.PICTURE); return this.getClaimAsString(StandardClaimNames.PICTURE);
} }
/** /**
* Returns the URL of the user's web page or blog {@code (website)}. * Returns the URL of the user's web page or blog {@code (website)}, or {@code null}
* @return the URL of the user's web page or blog * if it does not exist.
* @return the URL of the user's web page or blog, or {@code null} if it does not
* exist
*/ */
default String getWebsite() { default @Nullable String getWebsite() {
return this.getClaimAsString(StandardClaimNames.WEBSITE); return this.getClaimAsString(StandardClaimNames.WEBSITE);
} }
/** /**
* Returns the user's preferred e-mail address {@code (email)}. * Returns the user's preferred e-mail address {@code (email)}, or {@code null} if it
* @return the user's preferred e-mail address * does not exist.
* @return the user's preferred e-mail address, or {@code null} if it does not exist
*/ */
default String getEmail() { default @Nullable String getEmail() {
return this.getClaimAsString(StandardClaimNames.EMAIL); return this.getClaimAsString(StandardClaimNames.EMAIL);
} }
/** /**
* Returns {@code true} if the user's e-mail address has been verified * Returns {@code true} if the user's e-mail address has been verified
* {@code (email_verified)}, otherwise {@code false}. * {@code (email_verified)}, otherwise {@code false}, or {@code null} if it does not
* exist.
* @return {@code true} if the user's e-mail address has been verified, otherwise * @return {@code true} if the user's e-mail address has been verified, otherwise
* {@code false} * {@code false}, or {@code null} if it does not exist
*/ */
default Boolean getEmailVerified() { default @Nullable Boolean getEmailVerified() {
return this.getClaimAsBoolean(StandardClaimNames.EMAIL_VERIFIED); return this.getClaimAsBoolean(StandardClaimNames.EMAIL_VERIFIED);
} }
/** /**
* Returns the user's gender {@code (gender)}. * Returns the user's gender {@code (gender)}, or {@code null} if it does not exist.
* @return the user's gender * @return the user's gender, or {@code null} if it does not exist
*/ */
default String getGender() { default @Nullable String getGender() {
return this.getClaimAsString(StandardClaimNames.GENDER); return this.getClaimAsString(StandardClaimNames.GENDER);
} }
/** /**
* Returns the user's birth date {@code (birthdate)}. * Returns the user's birth date {@code (birthdate)}, or {@code null} if it does not
* @return the user's birth date * exist.
* @return the user's birth date, or {@code null} if it does not exist
*/ */
default String getBirthdate() { default @Nullable String getBirthdate() {
return this.getClaimAsString(StandardClaimNames.BIRTHDATE); return this.getClaimAsString(StandardClaimNames.BIRTHDATE);
} }
/** /**
* Returns the user's time zone {@code (zoneinfo)}. * Returns the user's time zone {@code (zoneinfo)}, or {@code null} if it does not
* @return the user's time zone * exist.
* @return the user's time zone, or {@code null} if it does not exist
*/ */
default String getZoneInfo() { default @Nullable String getZoneInfo() {
return this.getClaimAsString(StandardClaimNames.ZONEINFO); return this.getClaimAsString(StandardClaimNames.ZONEINFO);
} }
/** /**
* Returns the user's locale {@code (locale)}. * Returns the user's locale {@code (locale)}, or {@code null} if it does not exist.
* @return the user's locale * @return the user's locale, or {@code null} if it does not exist
*/ */
default String getLocale() { default @Nullable String getLocale() {
return this.getClaimAsString(StandardClaimNames.LOCALE); return this.getClaimAsString(StandardClaimNames.LOCALE);
} }
/** /**
* Returns the user's preferred phone number {@code (phone_number)}. * Returns the user's preferred phone number {@code (phone_number)}, or {@code null}
* @return the user's preferred phone number * if it does not exist.
* @return the user's preferred phone number, or {@code null} if it does not exist
*/ */
default String getPhoneNumber() { default @Nullable String getPhoneNumber() {
return this.getClaimAsString(StandardClaimNames.PHONE_NUMBER); return this.getClaimAsString(StandardClaimNames.PHONE_NUMBER);
} }
/** /**
* Returns {@code true} if the user's phone number has been verified * Returns {@code true} if the user's phone number has been verified
* {@code (phone_number_verified)}, otherwise {@code false}. * {@code (phone_number_verified)}, otherwise {@code false}, or {@code null} if it
* does not exist.
* @return {@code true} if the user's phone number has been verified, otherwise * @return {@code true} if the user's phone number has been verified, otherwise
* {@code false} * {@code false}, or {@code null} if it does not exist
*/ */
default Boolean getPhoneNumberVerified() { default @Nullable Boolean getPhoneNumberVerified() {
return this.getClaimAsBoolean(StandardClaimNames.PHONE_NUMBER_VERIFIED); return this.getClaimAsBoolean(StandardClaimNames.PHONE_NUMBER_VERIFIED);
} }
@ -201,10 +217,12 @@ public interface StandardClaimAccessor extends ClaimAccessor {
} }
/** /**
* Returns the time the user's information was last updated {@code (updated_at)}. * Returns the time the user's information was last updated {@code (updated_at)}, or
* @return the time the user's information was last updated * {@code null} if it does not exist.
* @return the time the user's information was last updated, or {@code null} if it
* does not exist
*/ */
default Instant getUpdatedAt() { default @Nullable Instant getUpdatedAt() {
return this.getClaimAsInstant(StandardClaimNames.UPDATED_AT); return this.getClaimAsInstant(StandardClaimNames.UPDATED_AT);
} }

3
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/endpoint/package-info.java

@ -18,4 +18,7 @@
* Support classes that model the OpenID Connect Core 1.0 Request and Response messages * Support classes that model the OpenID Connect Core 1.0 Request and Response messages
* from the Authorization Endpoint and Token Endpoint. * from the Authorization Endpoint and Token Endpoint.
*/ */
@NullMarked
package org.springframework.security.oauth2.core.oidc.endpoint; package org.springframework.security.oauth2.core.oidc.endpoint;
import org.jspecify.annotations.NullMarked;

3
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/package-info.java

@ -17,4 +17,7 @@
/** /**
* Core classes and interfaces providing support for OpenID Connect Core 1.0. * Core classes and interfaces providing support for OpenID Connect Core 1.0.
*/ */
@NullMarked
package org.springframework.security.oauth2.core.oidc; package org.springframework.security.oauth2.core.oidc;
import org.jspecify.annotations.NullMarked;

26
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUser.java

@ -20,6 +20,8 @@ import java.io.Serial;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.OidcIdToken;
@ -48,52 +50,52 @@ public class DefaultOidcUser extends DefaultOAuth2User implements OidcUser {
private final OidcIdToken idToken; private final OidcIdToken idToken;
private final OidcUserInfo userInfo; private final @Nullable OidcUserInfo userInfo;
/** /**
* Constructs a {@code DefaultOidcUser} using the provided parameters. * Constructs a {@code DefaultOidcUser} using the provided parameters.
* @param authorities the authorities granted to the user * @param authorities the authorities granted to the user, may be {@code null}
* @param idToken the {@link OidcIdToken ID Token} containing claims about the user * @param idToken the {@link OidcIdToken ID Token} containing claims about the user
*/ */
public DefaultOidcUser(Collection<? extends GrantedAuthority> authorities, OidcIdToken idToken) { public DefaultOidcUser(@Nullable Collection<? extends GrantedAuthority> authorities, OidcIdToken idToken) {
this(authorities, idToken, IdTokenClaimNames.SUB); this(authorities, idToken, IdTokenClaimNames.SUB);
} }
/** /**
* Constructs a {@code DefaultOidcUser} using the provided parameters. * Constructs a {@code DefaultOidcUser} using the provided parameters.
* @param authorities the authorities granted to the user * @param authorities the authorities granted to the user, may be {@code null}
* @param idToken the {@link OidcIdToken ID Token} containing claims about the user * @param idToken the {@link OidcIdToken ID Token} containing claims about the user
* @param nameAttributeKey the key used to access the user's &quot;name&quot; from * @param nameAttributeKey the key used to access the user's &quot;name&quot; from
* {@link #getAttributes()} * {@link #getAttributes()}
*/ */
public DefaultOidcUser(Collection<? extends GrantedAuthority> authorities, OidcIdToken idToken, public DefaultOidcUser(@Nullable Collection<? extends GrantedAuthority> authorities, OidcIdToken idToken,
String nameAttributeKey) { String nameAttributeKey) {
this(authorities, idToken, null, nameAttributeKey); this(authorities, idToken, null, nameAttributeKey);
} }
/** /**
* Constructs a {@code DefaultOidcUser} using the provided parameters. * Constructs a {@code DefaultOidcUser} using the provided parameters.
* @param authorities the authorities granted to the user * @param authorities the authorities granted to the user, may be {@code null}
* @param idToken the {@link OidcIdToken ID Token} containing claims about the user * @param idToken the {@link OidcIdToken ID Token} containing claims about the user
* @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user, * @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user,
* may be {@code null} * may be {@code null}
*/ */
public DefaultOidcUser(Collection<? extends GrantedAuthority> authorities, OidcIdToken idToken, public DefaultOidcUser(@Nullable Collection<? extends GrantedAuthority> authorities, OidcIdToken idToken,
OidcUserInfo userInfo) { @Nullable OidcUserInfo userInfo) {
this(authorities, idToken, userInfo, IdTokenClaimNames.SUB); this(authorities, idToken, userInfo, IdTokenClaimNames.SUB);
} }
/** /**
* Constructs a {@code DefaultOidcUser} using the provided parameters. * Constructs a {@code DefaultOidcUser} using the provided parameters.
* @param authorities the authorities granted to the user * @param authorities the authorities granted to the user, may be {@code null}
* @param idToken the {@link OidcIdToken ID Token} containing claims about the user * @param idToken the {@link OidcIdToken ID Token} containing claims about the user
* @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user, * @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user,
* may be {@code null} * may be {@code null}
* @param nameAttributeKey the key used to access the user's &quot;name&quot; from * @param nameAttributeKey the key used to access the user's &quot;name&quot; from
* {@link #getAttributes()} * {@link #getAttributes()}
*/ */
public DefaultOidcUser(Collection<? extends GrantedAuthority> authorities, OidcIdToken idToken, public DefaultOidcUser(@Nullable Collection<? extends GrantedAuthority> authorities, OidcIdToken idToken,
OidcUserInfo userInfo, String nameAttributeKey) { @Nullable OidcUserInfo userInfo, String nameAttributeKey) {
super(authorities, OidcUserAuthority.collectClaims(idToken, userInfo), nameAttributeKey); super(authorities, OidcUserAuthority.collectClaims(idToken, userInfo), nameAttributeKey);
this.idToken = idToken; this.idToken = idToken;
this.userInfo = userInfo; this.userInfo = userInfo;
@ -110,7 +112,7 @@ public class DefaultOidcUser extends DefaultOAuth2User implements OidcUser {
} }
@Override @Override
public OidcUserInfo getUserInfo() { public @Nullable OidcUserInfo getUserInfo() {
return this.userInfo; return this.userInfo;
} }

9
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/OidcUser.java

@ -18,6 +18,8 @@ package org.springframework.security.oauth2.core.oidc.user;
import java.util.Map; import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.AuthenticatedPrincipal; import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimAccessor; import org.springframework.security.oauth2.core.oidc.IdTokenClaimAccessor;
@ -65,10 +67,11 @@ public interface OidcUser extends OAuth2User, IdTokenClaimAccessor {
Map<String, Object> getClaims(); Map<String, Object> getClaims();
/** /**
* Returns the {@link OidcUserInfo UserInfo} containing claims about the user. * Returns the {@link OidcUserInfo UserInfo} containing claims about the user, or
* @return the {@link OidcUserInfo} containing claims about the user. * {@code null} if not present.
* @return the {@link OidcUserInfo} containing claims about the user, or {@code null}
*/ */
OidcUserInfo getUserInfo(); @Nullable OidcUserInfo getUserInfo();
/** /**
* Returns the {@link OidcIdToken ID Token} containing claims about the user. * Returns the {@link OidcIdToken ID Token} containing claims about the user.

26
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/OidcUserAuthority.java

@ -19,8 +19,10 @@ package org.springframework.security.oauth2.core.oidc.user;
import java.io.Serial; import java.io.Serial;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import org.springframework.lang.Nullable;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.OidcIdToken;
@ -42,7 +44,7 @@ public class OidcUserAuthority extends OAuth2UserAuthority {
private final OidcIdToken idToken; private final OidcIdToken idToken;
private final OidcUserInfo userInfo; private final @Nullable OidcUserInfo userInfo;
/** /**
* Constructs a {@code OidcUserAuthority} using the provided parameters. * Constructs a {@code OidcUserAuthority} using the provided parameters.
@ -59,7 +61,7 @@ public class OidcUserAuthority extends OAuth2UserAuthority {
* @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user, * @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user,
* may be {@code null} * may be {@code null}
*/ */
public OidcUserAuthority(OidcIdToken idToken, OidcUserInfo userInfo) { public OidcUserAuthority(OidcIdToken idToken, @Nullable OidcUserInfo userInfo) {
this("OIDC_USER", idToken, userInfo); this("OIDC_USER", idToken, userInfo);
} }
@ -70,10 +72,11 @@ public class OidcUserAuthority extends OAuth2UserAuthority {
* @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user, * @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user,
* may be {@code null} * may be {@code null}
* @param userNameAttributeName the attribute name used to access the user's name from * @param userNameAttributeName the attribute name used to access the user's name from
* the attributes * the attributes, may be {@code null}
* @since 6.4 * @since 6.4
*/ */
public OidcUserAuthority(OidcIdToken idToken, OidcUserInfo userInfo, @Nullable String userNameAttributeName) { public OidcUserAuthority(OidcIdToken idToken, @Nullable OidcUserInfo userInfo,
@Nullable String userNameAttributeName) {
this("OIDC_USER", idToken, userInfo, userNameAttributeName); this("OIDC_USER", idToken, userInfo, userNameAttributeName);
} }
@ -84,7 +87,7 @@ public class OidcUserAuthority extends OAuth2UserAuthority {
* @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user, * @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user,
* may be {@code null} * may be {@code null}
*/ */
public OidcUserAuthority(String authority, OidcIdToken idToken, OidcUserInfo userInfo) { public OidcUserAuthority(String authority, OidcIdToken idToken, @Nullable OidcUserInfo userInfo) {
this(authority, idToken, userInfo, IdTokenClaimNames.SUB); this(authority, idToken, userInfo, IdTokenClaimNames.SUB);
} }
@ -95,10 +98,10 @@ public class OidcUserAuthority extends OAuth2UserAuthority {
* @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user, * @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user,
* may be {@code null} * may be {@code null}
* @param userNameAttributeName the attribute name used to access the user's name from * @param userNameAttributeName the attribute name used to access the user's name from
* the attributes * the attributes, may be {@code null}
* @since 6.4 * @since 6.4
*/ */
public OidcUserAuthority(String authority, OidcIdToken idToken, OidcUserInfo userInfo, public OidcUserAuthority(String authority, OidcIdToken idToken, @Nullable OidcUserInfo userInfo,
@Nullable String userNameAttributeName) { @Nullable String userNameAttributeName) {
super(authority, collectClaims(idToken, userInfo), userNameAttributeName); super(authority, collectClaims(idToken, userInfo), userNameAttributeName);
this.idToken = idToken; this.idToken = idToken;
@ -118,7 +121,7 @@ public class OidcUserAuthority extends OAuth2UserAuthority {
* {@code null}. * {@code null}.
* @return the {@link OidcUserInfo} containing claims about the user, or {@code null} * @return the {@link OidcUserInfo} containing claims about the user, or {@code null}
*/ */
public OidcUserInfo getUserInfo() { public @Nullable OidcUserInfo getUserInfo() {
return this.userInfo; return this.userInfo;
} }
@ -137,8 +140,7 @@ public class OidcUserAuthority extends OAuth2UserAuthority {
if (!this.getIdToken().equals(that.getIdToken())) { if (!this.getIdToken().equals(that.getIdToken())) {
return false; return false;
} }
return (this.getUserInfo() != null) ? this.getUserInfo().equals(that.getUserInfo()) return Objects.equals(this.getUserInfo(), that.getUserInfo());
: that.getUserInfo() == null;
} }
@Override @Override
@ -149,7 +151,7 @@ public class OidcUserAuthority extends OAuth2UserAuthority {
return result; return result;
} }
static Map<String, Object> collectClaims(OidcIdToken idToken, OidcUserInfo userInfo) { static Map<String, Object> collectClaims(OidcIdToken idToken, @Nullable OidcUserInfo userInfo) {
Assert.notNull(idToken, "idToken cannot be null"); Assert.notNull(idToken, "idToken cannot be null");
Map<String, Object> claims = new HashMap<>(); Map<String, Object> claims = new HashMap<>();
if (userInfo != null) { if (userInfo != null) {

3
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/package-info.java

@ -18,4 +18,7 @@
* Provides a model for an OpenID Connect Core 1.0 representation of a user * Provides a model for an OpenID Connect Core 1.0 representation of a user
* {@code Principal}. * {@code Principal}.
*/ */
@NullMarked
package org.springframework.security.oauth2.core.oidc.user; package org.springframework.security.oauth2.core.oidc.user;
import org.jspecify.annotations.NullMarked;

3
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/package-info.java

@ -18,4 +18,7 @@
* Core classes and interfaces providing support for the OAuth 2.0 Authorization * Core classes and interfaces providing support for the OAuth 2.0 Authorization
* Framework. * Framework.
*/ */
@NullMarked
package org.springframework.security.oauth2.core; package org.springframework.security.oauth2.core;
import org.jspecify.annotations.NullMarked;

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

@ -27,6 +27,8 @@ import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -59,17 +61,17 @@ public class DefaultOAuth2User implements OAuth2User, Serializable {
/** /**
* Constructs a {@code DefaultOAuth2User} using the provided parameters. * Constructs a {@code DefaultOAuth2User} using the provided parameters.
* @param authorities the authorities granted to the user * @param authorities the authorities granted to the user, may be {@code null}
* @param attributes the attributes about the user * @param attributes the attributes about the user
* @param nameAttributeKey the key used to access the user's &quot;name&quot; from * @param nameAttributeKey the key used to access the user's &quot;name&quot; from
* {@link #getAttributes()} * {@link #getAttributes()}
*/ */
public DefaultOAuth2User(Collection<? extends GrantedAuthority> authorities, Map<String, Object> attributes, public DefaultOAuth2User(@Nullable Collection<? extends GrantedAuthority> authorities,
String nameAttributeKey) { Map<String, Object> attributes, String nameAttributeKey) {
Assert.notEmpty(attributes, "attributes cannot be empty"); Assert.notEmpty(attributes, "attributes cannot be empty");
Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty"); Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty");
Assert.notNull(attributes.get(nameAttributeKey), Object nameAttributeValue = attributes.get(nameAttributeKey);
"Attribute value for '" + nameAttributeKey + "' cannot be null"); Assert.notNull(nameAttributeValue, "Attribute value for '" + nameAttributeKey + "' cannot be null");
this.authorities = (authorities != null) this.authorities = (authorities != null)
? Collections.unmodifiableSet(new LinkedHashSet<>(this.sortAuthorities(authorities))) ? Collections.unmodifiableSet(new LinkedHashSet<>(this.sortAuthorities(authorities)))
@ -80,7 +82,9 @@ public class DefaultOAuth2User implements OAuth2User, Serializable {
@Override @Override
public String getName() { public String getName() {
return this.getAttribute(this.nameAttributeKey).toString(); Object nameAttributeValue = this.getAttribute(this.nameAttributeKey);
Assert.notNull(nameAttributeValue, "Name attribute value cannot be null");
return nameAttributeValue.toString();
} }
@Override @Override

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

@ -22,7 +22,8 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.springframework.lang.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -41,7 +42,7 @@ public class OAuth2UserAuthority implements GrantedAuthority {
private final Map<String, Object> attributes; private final Map<String, Object> attributes;
private final String userNameAttributeName; private final @Nullable String userNameAttributeName;
/** /**
* Constructs a {@code OAuth2UserAuthority} using the provided parameters and defaults * Constructs a {@code OAuth2UserAuthority} using the provided parameters and defaults
@ -78,10 +79,11 @@ public class OAuth2UserAuthority implements GrantedAuthority {
* @param authority the authority granted to the user * @param authority the authority granted to the user
* @param attributes the attributes about the user * @param attributes the attributes about the user
* @param userNameAttributeName the attribute name used to access the user's name from * @param userNameAttributeName the attribute name used to access the user's name from
* the attributes * the attributes, may be {@code null}
* @since 6.4 * @since 6.4
*/ */
public OAuth2UserAuthority(String authority, Map<String, Object> attributes, String userNameAttributeName) { public OAuth2UserAuthority(String authority, Map<String, Object> attributes,
@Nullable String userNameAttributeName) {
Assert.hasText(authority, "authority cannot be empty"); Assert.hasText(authority, "authority cannot be empty");
Assert.notEmpty(attributes, "attributes cannot be empty"); Assert.notEmpty(attributes, "attributes cannot be empty");
this.authority = authority; this.authority = authority;
@ -104,11 +106,11 @@ public class OAuth2UserAuthority implements GrantedAuthority {
/** /**
* Returns the attribute name used to access the user's name from the attributes. * Returns the attribute name used to access the user's name from the attributes.
* @return the attribute name used to access the user's name from the attributes * @return the attribute name used to access the user's name from the attributes, or
* {@code null} if not available
* @since 6.4 * @since 6.4
*/ */
@Nullable public @Nullable String getUserNameAttributeName() {
public String getUserNameAttributeName() {
return this.userNameAttributeName; return this.userNameAttributeName;
} }
@ -137,8 +139,9 @@ public class OAuth2UserAuthority implements GrantedAuthority {
} }
} }
else { else {
Object thatValue = convertURLIfNecessary(thatAttributes.get(key)); Object thatValue = thatAttributes.get(key);
if (!value.equals(thatValue)) { Object convertedThatValue = convertURLIfNecessary(thatValue);
if (!value.equals(convertedThatValue)) {
return false; return false;
} }
} }
@ -165,9 +168,10 @@ public class OAuth2UserAuthority implements GrantedAuthority {
/** /**
* @return {@code URL} converted to a string since {@code URL} shouldn't be used for * @return {@code URL} converted to a string since {@code URL} shouldn't be used for
* equality/hashCode. For other instances the value is returned as is. * equality/hashCode. For other instances the value is returned as is (including
* null).
*/ */
private static Object convertURLIfNecessary(Object value) { private static @Nullable Object convertURLIfNecessary(@Nullable Object value) {
return (value instanceof URL) ? ((URL) value).toExternalForm() : value; return (value instanceof URL) ? ((URL) value).toExternalForm() : value;
} }

3
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/package-info.java

@ -17,4 +17,7 @@
/** /**
* Provides a model for an OAuth 2.0 representation of a user {@code Principal}. * Provides a model for an OAuth 2.0 representation of a user {@code Principal}.
*/ */
@NullMarked
package org.springframework.security.oauth2.core.user; package org.springframework.security.oauth2.core.user;
import org.jspecify.annotations.NullMarked;

23
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/web/package-info.java

@ -0,0 +1,23 @@
/*
* Copyright 2004-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Web support classes for OAuth 2.0 and OpenID Connect.
*/
@NullMarked
package org.springframework.security.oauth2.core.web;
import org.jspecify.annotations.NullMarked;

23
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/web/reactive/function/package-info.java

@ -0,0 +1,23 @@
/*
* Copyright 2004-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Reactive functional web support classes for OAuth 2.0 and OpenID Connect.
*/
@NullMarked
package org.springframework.security.oauth2.core.web.reactive.function;
import org.jspecify.annotations.NullMarked;

23
oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/web/reactive/package-info.java

@ -0,0 +1,23 @@
/*
* Copyright 2004-present 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Reactive web support classes for OAuth 2.0 and OpenID Connect.
*/
@NullMarked
package org.springframework.security.oauth2.core.web.reactive;
import org.jspecify.annotations.NullMarked;

4
oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationTests.java

@ -76,11 +76,11 @@ public class BearerTokenAuthenticationTests {
} }
@Test @Test
public void getNameWhenHasNoSubjectThenReturnsNull() { public void getNameWhenHasNoSubjectThenReturnsEmptyString() {
OAuth2AuthenticatedPrincipal principal = new DefaultOAuth2AuthenticatedPrincipal( OAuth2AuthenticatedPrincipal principal = new DefaultOAuth2AuthenticatedPrincipal(
Collections.singletonMap("claim", "value"), null); Collections.singletonMap("claim", "value"), null);
BearerTokenAuthentication authenticated = new BearerTokenAuthentication(principal, this.token, null); BearerTokenAuthentication authenticated = new BearerTokenAuthentication(principal, this.token, null);
assertThat(authenticated.getName()).isNull(); assertThat(authenticated.getName()).isEmpty();
} }
@Test @Test

Loading…
Cancel
Save