mirror of
https://github.com/spring-projects/spring-security.git
synced 2026-03-30 14:08:11 +00:00
Enable null-safety in spring-security-oauth2-resource-server
Closes gh-17822
This commit is contained in:
parent
1cb9db4f2d
commit
09ce639c4b
@ -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"))
|
||||
|
||||
@ -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) ||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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 : "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
}
|
||||
|
||||
@ -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())) {
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user