Enable null-safety in spring-security-oauth2-resource-server

Closes gh-17822
This commit is contained in:
Joe Grandja 2026-03-18 16:50:54 -04:00
parent 1cb9db4f2d
commit 09ce639c4b
44 changed files with 333 additions and 122 deletions

View File

@ -1,5 +1,9 @@
plugins {
id 'security-nullability'
id 'javadoc-warnings-error'
}
apply plugin: 'io.spring.convention.spring-module'
apply plugin: 'javadoc-warnings-error'
dependencies {
management platform(project(":spring-security-dependencies"))

View File

@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.resource;
import java.io.Serial;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpStatus;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.util.Assert;
@ -41,14 +43,15 @@ public final class BearerTokenError extends OAuth2Error {
private final HttpStatus httpStatus;
private final String scope;
private final @Nullable String scope;
/**
* Create a {@code BearerTokenError} using the provided parameters
* @param errorCode the error code
* @param httpStatus the HTTP status
*/
public BearerTokenError(String errorCode, HttpStatus httpStatus, String description, String errorUri) {
public BearerTokenError(String errorCode, HttpStatus httpStatus, @Nullable String description,
@Nullable String errorUri) {
this(errorCode, httpStatus, description, errorUri, null);
}
@ -60,8 +63,8 @@ public final class BearerTokenError extends OAuth2Error {
* @param errorUri the URI
* @param scope the scope
*/
public BearerTokenError(String errorCode, HttpStatus httpStatus, String description, String errorUri,
String scope) {
public BearerTokenError(String errorCode, HttpStatus httpStatus, @Nullable String description,
@Nullable String errorUri, @Nullable String scope) {
super(errorCode, description, errorUri);
Assert.notNull(httpStatus, "httpStatus cannot be null");
Assert.isTrue(isDescriptionValid(description),
@ -85,13 +88,13 @@ public final class BearerTokenError extends OAuth2Error {
/**
* Return the scope.
* @return the scope
* @return the scope, or {@code null} if not set
*/
public String getScope() {
public @Nullable String getScope() {
return this.scope;
}
private static boolean isDescriptionValid(String description) {
private static boolean isDescriptionValid(@Nullable String description) {
// @formatter:off
return description == null || description.chars().allMatch((c) ->
withinTheRangeOf(c, 0x20, 0x21) ||
@ -109,12 +112,12 @@ public final class BearerTokenError extends OAuth2Error {
// @formatter:on
}
private static boolean isErrorUriValid(String errorUri) {
private static boolean isErrorUriValid(@Nullable String errorUri) {
return errorUri == null || errorUri.chars()
.allMatch((c) -> c == 0x21 || withinTheRangeOf(c, 0x23, 0x5B) || withinTheRangeOf(c, 0x5D, 0x7E));
}
private static boolean isScopeValid(String scope) {
private static boolean isScopeValid(@Nullable String scope) {
// @formatter:off
return scope == null || scope.chars().allMatch((c) ->
withinTheRangeOf(c, 0x20, 0x21) ||

View File

@ -16,6 +16,8 @@
package org.springframework.security.oauth2.server.resource;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpStatus;
/**
@ -78,7 +80,7 @@ public final class BearerTokenErrors {
* @param scope the scope attribute to use in the error
* @return a {@link BearerTokenError}
*/
public static BearerTokenError insufficientScope(String message, String scope) {
public static BearerTokenError insufficientScope(String message, @Nullable String scope) {
try {
return new BearerTokenError(BearerTokenErrorCodes.INSUFFICIENT_SCOPE, HttpStatus.FORBIDDEN, message,
DEFAULT_URI, scope);

View File

@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.resource;
import java.io.Serial;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
/**
@ -52,10 +54,13 @@ public class InvalidBearerTokenException extends OAuth2AuthenticationException {
* {@link org.springframework.security.oauth2.core.OAuth2Error} instance as the
* {@code error_description}.
* @param description the description
* @param cause the causing exception
* @param cause the causing exception, or {@code null}
*/
public InvalidBearerTokenException(String description, Throwable cause) {
super(BearerTokenErrors.invalidToken(description), cause);
public InvalidBearerTokenException(String description, @Nullable Throwable cause) {
super(BearerTokenErrors.invalidToken(description));
if (cause != null) {
initCause(cause);
}
}
}

View File

@ -27,6 +27,8 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@ -271,7 +273,10 @@ public final class OAuth2ProtectedResourceMetadata
valuesConsumer.accept(values);
}
private static void validateURL(Object url, String errorMessage) {
private static void validateURL(@Nullable Object url, String errorMessage) {
if (url == null) {
return;
}
if (URL.class.isAssignableFrom(url.getClass())) {
return;
}

View File

@ -19,9 +19,13 @@ package org.springframework.security.oauth2.server.resource;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.util.Assert;
/**
* A {@link ClaimAccessor} for the claims a Resource Server describes about its
@ -42,7 +46,9 @@ public interface OAuth2ProtectedResourceMetadataClaimAccessor extends ClaimAcces
* @return the {@code URL} the protected resource asserts as its resource identifier
*/
default URL getResource() {
return getClaimAsURL(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE);
URL resource = getClaimAsURL(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE);
Assert.notNull(resource, "resource cannot be null");
return resource;
}
/**
@ -50,11 +56,14 @@ public interface OAuth2ProtectedResourceMetadataClaimAccessor extends ClaimAcces
* servers that can be used with this protected resource
* {@code (authorization_servers)}.
* @return a list of {@code issuer} identifier {@code URL}'s, for authorization
* servers that can be used with this protected resource
* servers that can be used with this protected resource, or an empty list if not set
*/
default List<URL> getAuthorizationServers() {
List<String> authorizationServers = getClaimAsStringList(
OAuth2ProtectedResourceMetadataClaimNames.AUTHORIZATION_SERVERS);
if (authorizationServers == null) {
return Collections.emptyList();
}
List<URL> urls = new ArrayList<>();
authorizationServers.forEach((authorizationServer) -> {
try {
@ -70,11 +79,11 @@ public interface OAuth2ProtectedResourceMetadataClaimAccessor extends ClaimAcces
/**
* Returns a list of {@code scope} values supported, that are used in authorization
* requests to request access to this protected resource {@code (scopes_supported)}.
* @return a list of {@code scope} values supported, that are used in authorization
* requests to request access to this protected resource
* @return a list of {@code scope} values supported, or an empty list if not set
*/
default List<String> getScopes() {
return getClaimAsStringList(OAuth2ProtectedResourceMetadataClaimNames.SCOPES_SUPPORTED);
List<String> scopes = getClaimAsStringList(OAuth2ProtectedResourceMetadataClaimNames.SCOPES_SUPPORTED);
return (scopes != null) ? scopes : Collections.emptyList();
}
/**
@ -82,18 +91,20 @@ public interface OAuth2ProtectedResourceMetadataClaimAccessor extends ClaimAcces
* the protected resource. Defined values are "header", "body" and "query".
* {@code (bearer_methods_supported)}.
* @return a list of the supported methods for sending an OAuth 2.0 bearer token to
* the protected resource
* the protected resource, or an empty list if not set
*/
default List<String> getBearerMethodsSupported() {
return getClaimAsStringList(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED);
List<String> methods = getClaimAsStringList(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED);
return (methods != null) ? methods : Collections.emptyList();
}
/**
* Returns the name of the protected resource intended for display to the end user
* {@code (resource_name)}.
* @return the name of the protected resource intended for display to the end user
* @return the name of the protected resource intended for display to the end user, or
* {@code null} if not set
*/
default String getResourceName() {
default @Nullable String getResourceName() {
return getClaimAsString(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE_NAME);
}

View File

@ -61,21 +61,20 @@ public abstract class AbstractOAuth2TokenAuthenticationToken<T extends OAuth2Tok
* Sub-class constructor.
*/
protected AbstractOAuth2TokenAuthenticationToken(T token) {
this(token, null);
}
/**
* Sub-class constructor.
* @param authorities the authorities assigned to the Access Token
* @param authorities the authorities assigned to the Access Token, or {@code null}
*/
protected AbstractOAuth2TokenAuthenticationToken(T token, Collection<? extends GrantedAuthority> authorities) {
protected AbstractOAuth2TokenAuthenticationToken(T token,
@Nullable Collection<? extends GrantedAuthority> authorities) {
this(token, token, token, authorities);
}
protected AbstractOAuth2TokenAuthenticationToken(T token, Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
@Nullable Collection<? extends GrantedAuthority> authorities) {
super(authorities);
Assert.notNull(token, "token cannot be null");

View File

@ -24,6 +24,7 @@ import java.util.Map;
import java.util.function.Function;
import com.nimbusds.jose.jwk.JWK;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
@ -72,13 +73,15 @@ public final class DPoPAuthenticationProvider implements AuthenticationProvider
public DPoPAuthenticationProvider(AuthenticationManager tokenAuthenticationManager) {
Assert.notNull(tokenAuthenticationManager, "tokenAuthenticationManager cannot be null");
this.tokenAuthenticationManager = tokenAuthenticationManager;
Function<DPoPProofContext, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = (
context) -> new DelegatingOAuth2TokenValidator<>(
// Use default validators
DPoPProofJwtDecoderFactory.DEFAULT_JWT_VALIDATOR_FACTORY.apply(context),
// Add custom validators
new AthClaimValidator(context.getAccessToken()),
new JwkThumbprintValidator(context.getAccessToken()));
Function<DPoPProofContext, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = (context) -> {
OAuth2AccessTokenClaims accessToken = context.getAccessToken();
Assert.notNull(accessToken, "accessToken cannot be null");
return new DelegatingOAuth2TokenValidator<>(
// Use default validators
DPoPProofJwtDecoderFactory.DEFAULT_JWT_VALIDATOR_FACTORY.apply(context),
// Add custom validators
new AthClaimValidator(accessToken), new JwkThumbprintValidator(accessToken));
};
DPoPProofJwtDecoderFactory dPoPProofJwtDecoderFactory = new DPoPProofJwtDecoderFactory();
dPoPProofJwtDecoderFactory.setJwtValidatorFactory(jwtValidatorFactory);
this.dPoPProofVerifierFactory = dPoPProofJwtDecoderFactory;
@ -260,12 +263,12 @@ public final class DPoPAuthenticationProvider implements AuthenticationProvider
}
@Override
public Instant getIssuedAt() {
public @Nullable Instant getIssuedAt() {
return this.accessToken.getIssuedAt();
}
@Override
public Instant getExpiresAt() {
public @Nullable Instant getExpiresAt() {
return this.accessToken.getExpiresAt();
}

View File

@ -114,7 +114,8 @@ public class JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthen
@Override
public String getName() {
return getClaimAsString(this.principalClaimName);
String name = this.getClaimAsString(this.principalClaimName);
return (name != null) ? name : "";
}
}

View File

@ -101,10 +101,12 @@ public final class JwtAuthenticationProvider implements AuthenticationProvider {
}
catch (BadJwtException failed) {
this.logger.debug("Failed to authenticate since the JWT was invalid");
throw new InvalidBearerTokenException(failed.getMessage(), failed);
throw new InvalidBearerTokenException((failed.getMessage() != null) ? failed.getMessage() : "Invalid token",
failed);
}
catch (JwtException failed) {
throw new AuthenticationServiceException(failed.getMessage(), failed);
throw new AuthenticationServiceException(
(failed.getMessage() != null) ? failed.getMessage() : "Invalid token", failed);
}
}

View File

@ -43,7 +43,7 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
private static final long serialVersionUID = 620L;
private final String name;
private final @Nullable String name;
/**
* Constructs a {@code JwtAuthenticationToken} using the provided parameters.
@ -92,8 +92,8 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
public JwtAuthenticationToken(Jwt jwt, Object principal, Collection<? extends GrantedAuthority> authorities) {
super(jwt, principal, jwt, authorities);
this.setAuthenticated(true);
if (principal instanceof AuthenticatedPrincipal) {
this.name = ((AuthenticatedPrincipal) principal).getName();
if (principal instanceof AuthenticatedPrincipal authenticatedPrincipal) {
this.name = authenticatedPrincipal.getName();
}
else {
this.name = jwt.getSubject();
@ -106,11 +106,12 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
}
/**
* The principal name which is, by default, the {@link Jwt}'s subject
* The principal name which is, by default, the {@link Jwt}'s subject. Returns empty
* string if the subject claim is absent.
*/
@Override
public String getName() {
return this.name;
return (this.name != null) ? this.name : "";
}
@Override
@ -126,7 +127,7 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
*/
public static class Builder<B extends Builder<B>> extends AbstractOAuth2TokenAuthenticationBuilder<Jwt, B> {
private String name;
private @Nullable String name;
protected Builder(JwtAuthenticationToken token) {
super(token);
@ -168,10 +169,10 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
/**
* The name to use.
* @param name the name to use
* @param name the name to use, or {@code null} if the principal has no name
* @return the {@link Builder} for further configurations
*/
public B name(String name) {
public B name(@Nullable String name) {
this.name = name;
return (B) this;
}

View File

@ -23,6 +23,7 @@ import java.util.Collections;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.log.LogMessage;
@ -105,7 +106,7 @@ public final class JwtGrantedAuthoritiesConverter implements Converter<Jwt, Coll
this.authoritiesClaimNames = Collections.singletonList(authoritiesClaimName);
}
private String getAuthoritiesClaimName(Jwt jwt) {
private @Nullable String getAuthoritiesClaimName(Jwt jwt) {
for (String claimName : this.authoritiesClaimNames) {
if (jwt.hasClaim(claimName)) {
return claimName;

View File

@ -29,7 +29,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.log.LogMessage;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.core.Authentication;
@ -169,7 +168,7 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
private static class JwtClaimIssuerConverter implements Converter<BearerTokenAuthenticationToken, String> {
@Override
public String convert(@NonNull BearerTokenAuthenticationToken authentication) {
public String convert(BearerTokenAuthenticationToken authentication) {
String token = authentication.getToken();
try {
String issuer = JWTParser.parse(token).getJWTClaimsSet().getIssuer();
@ -178,7 +177,8 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
}
}
catch (Exception cause) {
AuthenticationException ex = new InvalidBearerTokenException(cause.getMessage(), cause);
AuthenticationException ex = new InvalidBearerTokenException(
(cause.getMessage() != null) ? cause.getMessage() : "Invalid token", cause);
ex.setAuthenticationRequest(authentication);
throw ex;
}
@ -202,6 +202,9 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
}
@Override
@SuppressWarnings("NullAway") // Interface does not declare @Nullable; this
// implementation returns null when issuer not
// trusted
public AuthenticationManager resolve(String issuer) {
if (this.trustedIssuer.test(issuer)) {
AuthenticationManager authenticationManager = this.authenticationManagers.computeIfAbsent(issuer,

View File

@ -31,7 +31,6 @@ import reactor.core.scheduler.Schedulers;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.log.LogMessage;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
@ -172,7 +171,7 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver
private static class JwtClaimIssuerConverter implements Converter<BearerTokenAuthenticationToken, Mono<String>> {
@Override
public Mono<String> convert(@NonNull BearerTokenAuthenticationToken token) {
public Mono<String> convert(BearerTokenAuthenticationToken token) {
try {
String issuer = JWTParser.parse(token.getToken()).getJWTClaimsSet().getIssuer();
if (issuer == null) {
@ -184,7 +183,8 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver
}
catch (Exception cause) {
return Mono.error(() -> {
AuthenticationException ex = new InvalidBearerTokenException(cause.getMessage(), cause);
AuthenticationException ex = new InvalidBearerTokenException(
(cause.getMessage() != null) ? cause.getMessage() : "Invalid token", cause);
ex.setAuthenticationRequest(token);
return ex;
});

View File

@ -76,7 +76,7 @@ public final class JwtReactiveAuthenticationManager implements ReactiveAuthentic
private AuthenticationException onError(JwtException ex) {
if (ex instanceof BadJwtException) {
return new InvalidBearerTokenException(ex.getMessage(), ex);
return new InvalidBearerTokenException((ex.getMessage() != null) ? ex.getMessage() : "Invalid token", ex);
}
return new AuthenticationServiceException(ex.getMessage(), ex);
}

View File

@ -22,6 +22,7 @@ import java.util.HashSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
@ -104,7 +105,7 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
* @throws AuthenticationException if authentication failed for some reason
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof BearerTokenAuthenticationToken bearer)) {
return null;
}
@ -129,7 +130,8 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
}
catch (BadOpaqueTokenException failed) {
this.logger.debug("Failed to authenticate since token was invalid");
throw new InvalidBearerTokenException(failed.getMessage(), failed);
throw new InvalidBearerTokenException((failed.getMessage() != null) ? failed.getMessage() : "Invalid token",
failed);
}
catch (OAuth2IntrospectionException failed) {
throw new AuthenticationServiceException(failed.getMessage(), failed);

View File

@ -105,7 +105,7 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
private AuthenticationException onError(OAuth2IntrospectionException ex) {
if (ex instanceof BadOpaqueTokenException) {
return new InvalidBearerTokenException(ex.getMessage(), ex);
return new InvalidBearerTokenException((ex.getMessage() != null) ? ex.getMessage() : "Invalid token", ex);
}
return new AuthenticationServiceException(ex.getMessage(), ex);
}

View File

@ -48,7 +48,8 @@ public final class ReactiveJwtAuthenticationConverter implements Converter<Jwt,
.collectList()
.map((authorities) -> {
String principalName = jwt.getClaimAsString(this.principalClaimName);
return new JwtAuthenticationToken(jwt, authorities, principalName);
return new JwtAuthenticationToken(jwt, authorities,
(principalName != null) ? principalName : "");
});
// @formatter:on
}

View File

@ -18,4 +18,7 @@
* OAuth 2.0 Resource Server {@code Authentication}s and supporting classes and
* interfaces.
*/
@NullMarked
package org.springframework.security.oauth2.server.resource.authentication;
import org.jspecify.annotations.NullMarked;

View File

@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.resource.introspection;
import java.io.Serial;
import org.jspecify.annotations.Nullable;
/**
* An exception similar to
* {@link org.springframework.security.authentication.BadCredentialsException} that
@ -35,7 +37,7 @@ public class BadOpaqueTokenException extends OAuth2IntrospectionException {
super(message);
}
public BadOpaqueTokenException(String message, Throwable cause) {
public BadOpaqueTokenException(String message, @Nullable Throwable cause) {
super(message, cause);
}

View File

@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.resource.introspection;
import java.io.Serial;
import org.jspecify.annotations.Nullable;
/**
* Base exception for all OAuth 2.0 Introspection related errors
*
@ -33,7 +35,7 @@ public class OAuth2IntrospectionException extends RuntimeException {
super(message);
}
public OAuth2IntrospectionException(String message, Throwable cause) {
public OAuth2IntrospectionException(String message, @Nullable Throwable cause) {
super(message, cause);
}

View File

@ -31,6 +31,7 @@ import java.util.function.Consumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.converter.Converter;
@ -108,7 +109,7 @@ public final class RestClientOpaqueTokenIntrospector implements OpaqueTokenIntro
return spec.retrieve().toEntity(STRING_OBJECT_MAP);
}
catch (Exception ex) {
throw new OAuth2IntrospectionException(ex.getMessage(), ex);
throw new OAuth2IntrospectionException((ex.getMessage() != null) ? ex.getMessage() : "Invalid token", ex);
}
}
@ -208,7 +209,8 @@ public final class RestClientOpaqueTokenIntrospector implements OpaqueTokenIntro
*/
private OAuth2IntrospectionAuthenticatedPrincipal defaultAuthenticationConverter(
OAuth2TokenIntrospectionClaimAccessor accessor) {
Collection<GrantedAuthority> authorities = authorities(accessor.getScopes());
List<String> scopes = accessor.getScopes();
Collection<GrantedAuthority> authorities = authorities((scopes != null) ? scopes : Collections.emptyList());
return new OAuth2IntrospectionAuthenticatedPrincipal(accessor.getClaims(), authorities);
}
@ -250,7 +252,7 @@ public final class RestClientOpaqueTokenIntrospector implements OpaqueTokenIntro
private interface ArrayListFromStringClaimAccessor extends OAuth2TokenIntrospectionClaimAccessor {
@Override
default List<String> getScopes() {
default @Nullable List<String> getScopes() {
Object value = getClaims().get(OAuth2TokenIntrospectionClaimNames.SCOPE);
if (value instanceof ArrayListFromString list) {
return list;
@ -270,9 +272,9 @@ public final class RestClientOpaqueTokenIntrospector implements OpaqueTokenIntro
private final String introspectionUri;
private String clientId;
private @Nullable String clientId;
private String clientSecret;
private @Nullable String clientSecret;
private final List<Consumer<RestClientOpaqueTokenIntrospector>> postProcessors = new ArrayList<>();
@ -322,9 +324,13 @@ public final class RestClientOpaqueTokenIntrospector implements OpaqueTokenIntro
* @return the {@link RestClientOpaqueTokenIntrospector}
*/
public RestClientOpaqueTokenIntrospector build() {
RestClient restClient = RestClient.builder()
.defaultHeaders((headers) -> headers.setBasicAuth(this.clientId, this.clientSecret))
.build();
RestClient.Builder builder = RestClient.builder();
if (this.clientId != null && this.clientSecret != null) {
String clientId = this.clientId;
String clientSecret = this.clientSecret;
builder.defaultHeaders((headers) -> headers.setBasicAuth(clientId, clientSecret));
}
RestClient restClient = builder.build();
RestClientOpaqueTokenIntrospector introspector = new RestClientOpaqueTokenIntrospector(
this.introspectionUri, restClient);
this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector));

View File

@ -32,6 +32,7 @@ import java.util.function.Consumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.converter.Converter;
@ -157,7 +158,7 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
return this.restOperations.exchange(requestEntity, STRING_OBJECT_MAP);
}
catch (Exception ex) {
throw new OAuth2IntrospectionException(ex.getMessage(), ex);
throw new OAuth2IntrospectionException((ex.getMessage() != null) ? ex.getMessage() : "Invalid token", ex);
}
}
@ -259,7 +260,8 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
*/
private OAuth2IntrospectionAuthenticatedPrincipal defaultAuthenticationConverter(
OAuth2TokenIntrospectionClaimAccessor accessor) {
Collection<GrantedAuthority> authorities = authorities(accessor.getScopes());
List<String> scopes = accessor.getScopes();
Collection<GrantedAuthority> authorities = authorities((scopes != null) ? scopes : Collections.emptyList());
return new OAuth2IntrospectionAuthenticatedPrincipal(accessor.getClaims(), authorities);
}
@ -302,7 +304,7 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private interface ArrayListFromStringClaimAccessor extends OAuth2TokenIntrospectionClaimAccessor {
@Override
default List<String> getScopes() {
default @Nullable List<String> getScopes() {
Object value = getClaims().get(OAuth2TokenIntrospectionClaimNames.SCOPE);
if (value instanceof ArrayListFromString list) {
return list;
@ -322,9 +324,9 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private final String introspectionUri;
private String clientId;
private @Nullable String clientId;
private String clientSecret;
private @Nullable String clientSecret;
private final List<Consumer<SpringOpaqueTokenIntrospector>> postProcessors = new ArrayList<>();
@ -379,7 +381,10 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
*/
public SpringOpaqueTokenIntrospector build() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(this.clientId, this.clientSecret));
if (this.clientId != null && this.clientSecret != null) {
restTemplate.getInterceptors()
.add(new BasicAuthenticationInterceptor(this.clientId, this.clientSecret));
}
SpringOpaqueTokenIntrospector introspector = new SpringOpaqueTokenIntrospector(this.introspectionUri,
restTemplate);
this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector));

View File

@ -30,6 +30,7 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
@ -188,7 +189,7 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
}
private OAuth2IntrospectionException onError(Throwable ex) {
return new OAuth2IntrospectionException(ex.getMessage(), ex);
return new OAuth2IntrospectionException((ex.getMessage() != null) ? ex.getMessage() : "Invalid token", ex);
}
/**
@ -212,7 +213,8 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
private Mono<OAuth2IntrospectionAuthenticatedPrincipal> defaultAuthenticationConverter(
OAuth2TokenIntrospectionClaimAccessor accessor) {
Collection<GrantedAuthority> authorities = authorities(accessor.getScopes());
List<String> scopes = accessor.getScopes();
Collection<GrantedAuthority> authorities = authorities((scopes != null) ? scopes : Collections.emptyList());
return Mono.just(new OAuth2IntrospectionAuthenticatedPrincipal(accessor.getClaims(), authorities));
}
@ -255,7 +257,7 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
private interface ArrayListFromStringClaimAccessor extends OAuth2TokenIntrospectionClaimAccessor {
@Override
default List<String> getScopes() {
default @Nullable List<String> getScopes() {
Object value = getClaims().get(OAuth2TokenIntrospectionClaimNames.SCOPE);
if (value instanceof ArrayListFromString list) {
return list;
@ -275,9 +277,9 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
private final String introspectionUri;
private String clientId;
private @Nullable String clientId;
private String clientSecret;
private @Nullable String clientSecret;
private final List<Consumer<SpringReactiveOpaqueTokenIntrospector>> postProcessors = new ArrayList<>();
@ -332,9 +334,13 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
* @since 6.5
*/
public SpringReactiveOpaqueTokenIntrospector build() {
WebClient webClient = WebClient.builder()
.defaultHeaders((h) -> h.setBasicAuth(this.clientId, this.clientSecret))
.build();
WebClient.Builder builder = WebClient.builder();
if (this.clientId != null && this.clientSecret != null) {
String clientId = this.clientId;
String clientSecret = this.clientSecret;
builder.defaultHeaders((h) -> h.setBasicAuth(clientId, clientSecret));
}
WebClient webClient = builder.build();
SpringReactiveOpaqueTokenIntrospector introspector = new SpringReactiveOpaqueTokenIntrospector(
this.introspectionUri, webClient);
this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector));

View File

@ -17,4 +17,7 @@
/**
* OAuth 2.0 Introspection supporting classes and interfaces.
*/
@NullMarked
package org.springframework.security.oauth2.server.resource.introspection;
import org.jspecify.annotations.NullMarked;

View File

@ -17,4 +17,7 @@
/**
* OAuth 2.0 Resource Server core classes and interfaces providing support.
*/
@NullMarked
package org.springframework.security.oauth2.server.resource;
import org.jspecify.annotations.NullMarked;

View File

@ -22,6 +22,7 @@ import java.util.function.Function;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@ -51,7 +52,7 @@ import org.springframework.web.util.UriComponentsBuilder;
*/
public final class BearerTokenAuthenticationEntryPoint implements AuthenticationEntryPoint {
private String realmName;
private @Nullable String realmName;
private Function<HttpServletRequest, String> resourceMetadataParameterResolver = BearerTokenAuthenticationEntryPoint::getResourceMetadataParameter;
@ -95,9 +96,9 @@ public final class BearerTokenAuthenticationEntryPoint implements Authentication
/**
* Set the default realm name to use in the bearer token error response
* @param realmName
* @param realmName the realm name, or {@code null}
*/
public void setRealmName(String realmName) {
public void setRealmName(@Nullable String realmName) {
this.realmName = realmName;
}

View File

@ -17,6 +17,7 @@
package org.springframework.security.oauth2.server.resource.web;
import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
@ -41,6 +42,6 @@ public interface BearerTokenResolver {
* @return the Bearer Token value or {@code null} if none found
* @throws OAuth2AuthenticationException if the found token is invalid
*/
String resolve(HttpServletRequest request);
@Nullable String resolve(HttpServletRequest request);
}

View File

@ -20,6 +20,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@ -51,7 +52,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;
@Override
public String resolve(final HttpServletRequest request) {
public @Nullable String resolve(final HttpServletRequest request) {
// @formatter:off
return resolveToken(
resolveFromAuthorizationHeader(request),
@ -61,7 +62,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
// @formatter:on
}
private static String resolveToken(String... accessTokens) {
private static @Nullable String resolveToken(@Nullable String... accessTokens) {
if (accessTokens == null || accessTokens.length == 0) {
return null;
}
@ -87,7 +88,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
return accessToken;
}
private String resolveFromAuthorizationHeader(HttpServletRequest request) {
private @Nullable String resolveFromAuthorizationHeader(HttpServletRequest request) {
String authorization = request.getHeader(this.bearerTokenHeaderName);
if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
return null;
@ -102,7 +103,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
return matcher.group("token");
}
private String resolveAccessTokenFromQueryString(HttpServletRequest request) {
private @Nullable String resolveAccessTokenFromQueryString(HttpServletRequest request) {
if (!this.allowUriQueryParameter || !HttpMethod.GET.name().equals(request.getMethod())) {
return null;
}
@ -110,7 +111,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
return resolveToken(request.getParameterValues(ACCESS_TOKEN_PARAMETER_NAME));
}
private String resolveAccessTokenFromBody(HttpServletRequest request) {
private @Nullable String resolveAccessTokenFromBody(HttpServletRequest request) {
if (!this.allowFormEncodedBodyParameter
|| !MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())
|| HttpMethod.GET.name().equals(request.getMethod())) {

View File

@ -24,6 +24,7 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
@ -60,7 +61,7 @@ public final class OAuth2ProtectedResourceMetadataFilter extends OncePerRequestF
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() {
};
private static final GenericHttpMessageConverter<Object> JSON_MESSAGE_CONVERTER = HttpMessageConverters
private static final @Nullable GenericHttpMessageConverter<Object> JSON_MESSAGE_CONVERTER = HttpMessageConverters
.getJsonMessageConverter();
/**
@ -108,8 +109,10 @@ public final class OAuth2ProtectedResourceMetadataFilter extends OncePerRequestF
OAuth2ProtectedResourceMetadata protectedResourceMetadata = builder.build();
try {
GenericHttpMessageConverter<Object> converter = JSON_MESSAGE_CONVERTER;
Assert.notNull(converter, "No JSON message converter available");
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
JSON_MESSAGE_CONVERTER.write(protectedResourceMetadata.getClaims(), STRING_OBJECT_MAP.getType(),
converter.write(protectedResourceMetadata.getClaims(), STRING_OBJECT_MAP.getType(),
MediaType.APPLICATION_JSON, httpResponse);
}
catch (Exception ex) {
@ -161,7 +164,7 @@ public final class OAuth2ProtectedResourceMetadataFilter extends OncePerRequestF
}
@SuppressWarnings("removal")
private static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
private static @Nullable GenericHttpMessageConverter<Object> getJsonMessageConverter() {
if (jacksonPresent) {
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
}

View File

@ -21,6 +21,7 @@ import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@ -46,7 +47,7 @@ import org.springframework.security.web.access.AccessDeniedHandler;
*/
public final class BearerTokenAccessDeniedHandler implements AccessDeniedHandler {
private String realmName;
private @Nullable String realmName;
/**
* Collect error details from the provided parameters and format according to RFC
@ -78,7 +79,7 @@ public final class BearerTokenAccessDeniedHandler implements AccessDeniedHandler
* Set the default realm name to use in the bearer token error response
* @param realmName
*/
public void setRealmName(String realmName) {
public void setRealmName(@Nullable String realmName) {
this.realmName = realmName;
}

View File

@ -17,4 +17,7 @@
/**
* OAuth 2.0 Resource Server access denial classes and interfaces.
*/
@NullMarked
package org.springframework.security.oauth2.server.resource.web.access;
import org.jspecify.annotations.NullMarked;

View File

@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
@ -51,7 +52,7 @@ public class BearerTokenServerAccessDeniedHandler implements ServerAccessDeniedH
private static final Collection<String> WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES = Arrays.asList("scope", "scp");
private String realmName;
private @Nullable String realmName;
@Override
public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
@ -72,7 +73,7 @@ public class BearerTokenServerAccessDeniedHandler implements ServerAccessDeniedH
* Set the default realm name to use in the bearer token error response
* @param realmName
*/
public final void setRealmName(String realmName) {
public final void setRealmName(@Nullable String realmName) {
this.realmName = realmName;
}

View File

@ -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.
*/
/**
* OAuth 2.0 Resource Server WebFlux access denied handlers.
*/
@NullMarked
package org.springframework.security.oauth2.server.resource.web.access.server;
import org.jspecify.annotations.NullMarked;

View File

@ -17,6 +17,7 @@
package org.springframework.security.oauth2.server.resource.web.authentication;
import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.core.Authentication;
@ -43,7 +44,7 @@ public final class BearerTokenAuthenticationConverter implements AuthenticationC
private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
@Override
public Authentication convert(HttpServletRequest request) {
public @Nullable Authentication convert(HttpServletRequest request) {
String token = this.bearerTokenResolver.resolve(request);
if (StringUtils.hasText(token)) {
BearerTokenAuthenticationToken authenticationToken = new BearerTokenAuthenticationToken(token);

View File

@ -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.
*/
/**
* OAuth 2.0 Resource Server web authentication converters and filters.
*/
@NullMarked
package org.springframework.security.oauth2.server.resource.web.authentication;
import org.jspecify.annotations.NullMarked;

View File

@ -17,4 +17,7 @@
/**
* OAuth 2.0 Resource Server {@code Filter}'s and supporting classes and interfaces.
*/
@NullMarked
package org.springframework.security.oauth2.server.resource.web;
import org.jspecify.annotations.NullMarked;

View File

@ -20,8 +20,8 @@ import reactor.core.publisher.Mono;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
@ -63,17 +63,18 @@ public final class ServerBearerExchangeFilterFunction implements ExchangeFilterF
private Mono<OAuth2Token> oauth2Token() {
// @formatter:off
return currentAuthentication()
.filter((authentication) -> authentication.getCredentials() instanceof OAuth2Token)
.map(Authentication::getCredentials)
.cast(OAuth2Token.class);
.filter((authentication) -> authentication.getCredentials() != null
&& authentication.getCredentials() instanceof OAuth2Token)
.map((authentication) -> {
Object credentials = authentication.getCredentials();
Assert.notNull(credentials, "credentials cannot be null");
return (OAuth2Token) credentials;
});
// @formatter:on
}
private Mono<Authentication> currentAuthentication() {
// @formatter:off
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication);
// @formatter:on
return ReactiveSecurityContextHolder.getContext().flatMap((ctx) -> Mono.justOrEmpty(ctx.getAuthentication()));
}
private ClientRequest bearer(ClientRequest request, OAuth2Token token) {

View File

@ -18,11 +18,13 @@ package org.springframework.security.oauth2.server.resource.web.reactive.functio
import java.util.Map;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
@ -71,9 +73,13 @@ public final class ServletBearerExchangeFilterFunction implements ExchangeFilter
return Mono.deferContextual(Mono::just)
.cast(Context.class)
.flatMap(this::currentAuthentication)
.filter((authentication) -> authentication.getCredentials() instanceof OAuth2Token)
.map(Authentication::getCredentials)
.cast(OAuth2Token.class);
.filter((authentication) -> authentication.getCredentials() != null
&& authentication.getCredentials() instanceof OAuth2Token)
.map((authentication) -> {
Object credentials = authentication.getCredentials();
Assert.notNull(credentials, "credentials cannot be null");
return (OAuth2Token) credentials;
});
// @formatter:on
}
@ -81,7 +87,7 @@ public final class ServletBearerExchangeFilterFunction implements ExchangeFilter
return Mono.justOrEmpty(getAttribute(ctx, Authentication.class));
}
private <T> T getAttribute(Context ctx, Class<T> clazz) {
private <T> @Nullable T getAttribute(Context ctx, Class<T> clazz) {
// NOTE: SecurityReactorContextConfiguration.SecurityReactorContextSubscriber adds
// this key
if (!ctx.hasKey(SECURITY_REACTOR_CONTEXT_ATTRIBUTES_KEY)) {

View File

@ -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.
*/
/**
* OAuth 2.0 Resource Server WebClient exchange filter functions.
*/
@NullMarked
package org.springframework.security.oauth2.server.resource.web.reactive.function.client;
import org.jspecify.annotations.NullMarked;

View File

@ -19,6 +19,7 @@ package org.springframework.security.oauth2.server.resource.web.server;
import java.util.LinkedHashMap;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
@ -49,9 +50,9 @@ import org.springframework.web.server.ServerWebExchange;
*/
public final class BearerTokenServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
private String realmName;
private @Nullable String realmName;
public void setRealmName(String realmName) {
public void setRealmName(@Nullable String realmName) {
this.realmName = realmName;
}

View File

@ -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.
*/
/**
* OAuth 2.0 Resource Server WebFlux authentication converters.
*/
@NullMarked
package org.springframework.security.oauth2.server.resource.web.server.authentication;
import org.jspecify.annotations.NullMarked;

View File

@ -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.
*/
/**
* OAuth 2.0 Resource Server WebFlux support classes.
*/
@NullMarked
package org.springframework.security.oauth2.server.resource.web.server;
import org.jspecify.annotations.NullMarked;

View File

@ -47,10 +47,10 @@ public class JwtAuthenticationTokenTests {
}
@Test
public void getNameWhenJwtHasNoSubjectThenReturnsNull() {
public void getNameWhenJwtHasNoSubjectThenReturnsEmptyString() {
Jwt jwt = builder().claim("claim", "value").build();
JwtAuthenticationToken token = new JwtAuthenticationToken(jwt);
assertThat(token.getName()).isNull();
assertThat(token.getName()).isEmpty();
}
@Test
@ -108,12 +108,12 @@ public class JwtAuthenticationTokenTests {
}
@Test
public void getNameWhenConstructedWithNoSubjectThenReturnsNull() {
public void getNameWhenConstructedWithNoSubjectThenReturnsEmptyString() {
Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("test");
Jwt jwt = builder().claim("claim", "value").build();
assertThat(new JwtAuthenticationToken(jwt, authorities, (String) null).getName()).isNull();
assertThat(new JwtAuthenticationToken(jwt, authorities).getName()).isNull();
assertThat(new JwtAuthenticationToken(jwt).getName()).isNull();
assertThat(new JwtAuthenticationToken(jwt, authorities, (String) null).getName()).isEmpty();
assertThat(new JwtAuthenticationToken(jwt, authorities).getName()).isEmpty();
assertThat(new JwtAuthenticationToken(jwt).getName()).isEmpty();
}
@Test