Enable null-safety in spring-security-oauth2-resource-server
Closes gh-17822
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
|
plugins {
|
||||||
|
id 'security-nullability'
|
||||||
|
id 'javadoc-warnings-error'
|
||||||
|
}
|
||||||
|
|
||||||
apply plugin: 'io.spring.convention.spring-module'
|
apply plugin: 'io.spring.convention.spring-module'
|
||||||
apply plugin: 'javadoc-warnings-error'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
management platform(project(":spring-security-dependencies"))
|
management platform(project(":spring-security-dependencies"))
|
||||||
|
|||||||
+12
-9
@@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.resource;
|
|||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -41,14 +43,15 @@ public final class BearerTokenError extends OAuth2Error {
|
|||||||
|
|
||||||
private final HttpStatus httpStatus;
|
private final HttpStatus httpStatus;
|
||||||
|
|
||||||
private final String scope;
|
private final @Nullable String scope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@code BearerTokenError} using the provided parameters
|
* Create a {@code BearerTokenError} using the provided parameters
|
||||||
* @param errorCode the error code
|
* @param errorCode the error code
|
||||||
* @param httpStatus the HTTP status
|
* @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);
|
this(errorCode, httpStatus, description, errorUri, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,8 +63,8 @@ public final class BearerTokenError extends OAuth2Error {
|
|||||||
* @param errorUri the URI
|
* @param errorUri the URI
|
||||||
* @param scope the scope
|
* @param scope the scope
|
||||||
*/
|
*/
|
||||||
public BearerTokenError(String errorCode, HttpStatus httpStatus, String description, String errorUri,
|
public BearerTokenError(String errorCode, HttpStatus httpStatus, @Nullable String description,
|
||||||
String scope) {
|
@Nullable String errorUri, @Nullable String scope) {
|
||||||
super(errorCode, description, errorUri);
|
super(errorCode, description, errorUri);
|
||||||
Assert.notNull(httpStatus, "httpStatus cannot be null");
|
Assert.notNull(httpStatus, "httpStatus cannot be null");
|
||||||
Assert.isTrue(isDescriptionValid(description),
|
Assert.isTrue(isDescriptionValid(description),
|
||||||
@@ -85,13 +88,13 @@ public final class BearerTokenError extends OAuth2Error {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the scope.
|
* 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;
|
return this.scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isDescriptionValid(String description) {
|
private static boolean isDescriptionValid(@Nullable String description) {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return description == null || description.chars().allMatch((c) ->
|
return description == null || description.chars().allMatch((c) ->
|
||||||
withinTheRangeOf(c, 0x20, 0x21) ||
|
withinTheRangeOf(c, 0x20, 0x21) ||
|
||||||
@@ -109,12 +112,12 @@ public final class BearerTokenError extends OAuth2Error {
|
|||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isErrorUriValid(String errorUri) {
|
private static boolean isErrorUriValid(@Nullable String errorUri) {
|
||||||
return errorUri == null || errorUri.chars()
|
return errorUri == null || errorUri.chars()
|
||||||
.allMatch((c) -> c == 0x21 || withinTheRangeOf(c, 0x23, 0x5B) || withinTheRangeOf(c, 0x5D, 0x7E));
|
.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
|
// @formatter:off
|
||||||
return scope == null || scope.chars().allMatch((c) ->
|
return scope == null || scope.chars().allMatch((c) ->
|
||||||
withinTheRangeOf(c, 0x20, 0x21) ||
|
withinTheRangeOf(c, 0x20, 0x21) ||
|
||||||
|
|||||||
+3
-1
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package org.springframework.security.oauth2.server.resource;
|
package org.springframework.security.oauth2.server.resource;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,7 +80,7 @@ public final class BearerTokenErrors {
|
|||||||
* @param scope the scope attribute to use in the error
|
* @param scope the scope attribute to use in the error
|
||||||
* @return a {@link BearerTokenError}
|
* @return a {@link BearerTokenError}
|
||||||
*/
|
*/
|
||||||
public static BearerTokenError insufficientScope(String message, String scope) {
|
public static BearerTokenError insufficientScope(String message, @Nullable String scope) {
|
||||||
try {
|
try {
|
||||||
return new BearerTokenError(BearerTokenErrorCodes.INSUFFICIENT_SCOPE, HttpStatus.FORBIDDEN, message,
|
return new BearerTokenError(BearerTokenErrorCodes.INSUFFICIENT_SCOPE, HttpStatus.FORBIDDEN, message,
|
||||||
DEFAULT_URI, scope);
|
DEFAULT_URI, scope);
|
||||||
|
|||||||
+8
-3
@@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.resource;
|
|||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
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
|
* {@link org.springframework.security.oauth2.core.OAuth2Error} instance as the
|
||||||
* {@code error_description}.
|
* {@code error_description}.
|
||||||
* @param description the description
|
* @param description the description
|
||||||
* @param cause the causing exception
|
* @param cause the causing exception, or {@code null}
|
||||||
*/
|
*/
|
||||||
public InvalidBearerTokenException(String description, Throwable cause) {
|
public InvalidBearerTokenException(String description, @Nullable Throwable cause) {
|
||||||
super(BearerTokenErrors.invalidToken(description), cause);
|
super(BearerTokenErrors.invalidToken(description));
|
||||||
|
if (cause != null) {
|
||||||
|
initCause(cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -27,6 +27,8 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -271,7 +273,10 @@ public final class OAuth2ProtectedResourceMetadata
|
|||||||
valuesConsumer.accept(values);
|
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())) {
|
if (URL.class.isAssignableFrom(url.getClass())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-9
@@ -19,9 +19,13 @@ package org.springframework.security.oauth2.server.resource;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link ClaimAccessor} for the claims a Resource Server describes about its
|
* 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
|
* @return the {@code URL} the protected resource asserts as its resource identifier
|
||||||
*/
|
*/
|
||||||
default URL getResource() {
|
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
|
* servers that can be used with this protected resource
|
||||||
* {@code (authorization_servers)}.
|
* {@code (authorization_servers)}.
|
||||||
* @return a list of {@code issuer} identifier {@code URL}'s, for authorization
|
* @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() {
|
default List<URL> getAuthorizationServers() {
|
||||||
List<String> authorizationServers = getClaimAsStringList(
|
List<String> authorizationServers = getClaimAsStringList(
|
||||||
OAuth2ProtectedResourceMetadataClaimNames.AUTHORIZATION_SERVERS);
|
OAuth2ProtectedResourceMetadataClaimNames.AUTHORIZATION_SERVERS);
|
||||||
|
if (authorizationServers == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
List<URL> urls = new ArrayList<>();
|
List<URL> urls = new ArrayList<>();
|
||||||
authorizationServers.forEach((authorizationServer) -> {
|
authorizationServers.forEach((authorizationServer) -> {
|
||||||
try {
|
try {
|
||||||
@@ -70,11 +79,11 @@ public interface OAuth2ProtectedResourceMetadataClaimAccessor extends ClaimAcces
|
|||||||
/**
|
/**
|
||||||
* Returns a list of {@code scope} values supported, that are used in authorization
|
* Returns a list of {@code scope} values supported, that are used in authorization
|
||||||
* requests to request access to this protected resource {@code (scopes_supported)}.
|
* requests to request access to this protected resource {@code (scopes_supported)}.
|
||||||
* @return a list of {@code scope} values supported, that are used in authorization
|
* @return a list of {@code scope} values supported, or an empty list if not set
|
||||||
* requests to request access to this protected resource
|
|
||||||
*/
|
*/
|
||||||
default List<String> getScopes() {
|
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".
|
* the protected resource. Defined values are "header", "body" and "query".
|
||||||
* {@code (bearer_methods_supported)}.
|
* {@code (bearer_methods_supported)}.
|
||||||
* @return a list of the supported methods for sending an OAuth 2.0 bearer token to
|
* @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() {
|
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
|
* Returns the name of the protected resource intended for display to the end user
|
||||||
* {@code (resource_name)}.
|
* {@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);
|
return getClaimAsString(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-5
@@ -61,21 +61,20 @@ public abstract class AbstractOAuth2TokenAuthenticationToken<T extends OAuth2Tok
|
|||||||
* Sub-class constructor.
|
* Sub-class constructor.
|
||||||
*/
|
*/
|
||||||
protected AbstractOAuth2TokenAuthenticationToken(T token) {
|
protected AbstractOAuth2TokenAuthenticationToken(T token) {
|
||||||
|
|
||||||
this(token, null);
|
this(token, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sub-class constructor.
|
* 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);
|
this(token, token, token, authorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AbstractOAuth2TokenAuthenticationToken(T token, Object principal, Object credentials,
|
protected AbstractOAuth2TokenAuthenticationToken(T token, Object principal, Object credentials,
|
||||||
Collection<? extends GrantedAuthority> authorities) {
|
@Nullable Collection<? extends GrantedAuthority> authorities) {
|
||||||
|
|
||||||
super(authorities);
|
super(authorities);
|
||||||
Assert.notNull(token, "token cannot be null");
|
Assert.notNull(token, "token cannot be null");
|
||||||
|
|||||||
+12
-9
@@ -24,6 +24,7 @@ import java.util.Map;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.nimbusds.jose.jwk.JWK;
|
import com.nimbusds.jose.jwk.JWK;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
@@ -72,13 +73,15 @@ public final class DPoPAuthenticationProvider implements AuthenticationProvider
|
|||||||
public DPoPAuthenticationProvider(AuthenticationManager tokenAuthenticationManager) {
|
public DPoPAuthenticationProvider(AuthenticationManager tokenAuthenticationManager) {
|
||||||
Assert.notNull(tokenAuthenticationManager, "tokenAuthenticationManager cannot be null");
|
Assert.notNull(tokenAuthenticationManager, "tokenAuthenticationManager cannot be null");
|
||||||
this.tokenAuthenticationManager = tokenAuthenticationManager;
|
this.tokenAuthenticationManager = tokenAuthenticationManager;
|
||||||
Function<DPoPProofContext, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = (
|
Function<DPoPProofContext, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = (context) -> {
|
||||||
context) -> new DelegatingOAuth2TokenValidator<>(
|
OAuth2AccessTokenClaims accessToken = context.getAccessToken();
|
||||||
// Use default validators
|
Assert.notNull(accessToken, "accessToken cannot be null");
|
||||||
DPoPProofJwtDecoderFactory.DEFAULT_JWT_VALIDATOR_FACTORY.apply(context),
|
return new DelegatingOAuth2TokenValidator<>(
|
||||||
// Add custom validators
|
// Use default validators
|
||||||
new AthClaimValidator(context.getAccessToken()),
|
DPoPProofJwtDecoderFactory.DEFAULT_JWT_VALIDATOR_FACTORY.apply(context),
|
||||||
new JwkThumbprintValidator(context.getAccessToken()));
|
// Add custom validators
|
||||||
|
new AthClaimValidator(accessToken), new JwkThumbprintValidator(accessToken));
|
||||||
|
};
|
||||||
DPoPProofJwtDecoderFactory dPoPProofJwtDecoderFactory = new DPoPProofJwtDecoderFactory();
|
DPoPProofJwtDecoderFactory dPoPProofJwtDecoderFactory = new DPoPProofJwtDecoderFactory();
|
||||||
dPoPProofJwtDecoderFactory.setJwtValidatorFactory(jwtValidatorFactory);
|
dPoPProofJwtDecoderFactory.setJwtValidatorFactory(jwtValidatorFactory);
|
||||||
this.dPoPProofVerifierFactory = dPoPProofJwtDecoderFactory;
|
this.dPoPProofVerifierFactory = dPoPProofJwtDecoderFactory;
|
||||||
@@ -260,12 +263,12 @@ public final class DPoPAuthenticationProvider implements AuthenticationProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Instant getIssuedAt() {
|
public @Nullable Instant getIssuedAt() {
|
||||||
return this.accessToken.getIssuedAt();
|
return this.accessToken.getIssuedAt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Instant getExpiresAt() {
|
public @Nullable Instant getExpiresAt() {
|
||||||
return this.accessToken.getExpiresAt();
|
return this.accessToken.getExpiresAt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -114,7 +114,8 @@ public class JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return getClaimAsString(this.principalClaimName);
|
String name = this.getClaimAsString(this.principalClaimName);
|
||||||
|
return (name != null) ? name : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -101,10 +101,12 @@ public final class JwtAuthenticationProvider implements AuthenticationProvider {
|
|||||||
}
|
}
|
||||||
catch (BadJwtException failed) {
|
catch (BadJwtException failed) {
|
||||||
this.logger.debug("Failed to authenticate since the JWT was invalid");
|
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) {
|
catch (JwtException failed) {
|
||||||
throw new AuthenticationServiceException(failed.getMessage(), failed);
|
throw new AuthenticationServiceException(
|
||||||
|
(failed.getMessage() != null) ? failed.getMessage() : "Invalid token", failed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-8
@@ -43,7 +43,7 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
|
|||||||
|
|
||||||
private static final long serialVersionUID = 620L;
|
private static final long serialVersionUID = 620L;
|
||||||
|
|
||||||
private final String name;
|
private final @Nullable String name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code JwtAuthenticationToken} using the provided parameters.
|
* 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) {
|
public JwtAuthenticationToken(Jwt jwt, Object principal, Collection<? extends GrantedAuthority> authorities) {
|
||||||
super(jwt, principal, jwt, authorities);
|
super(jwt, principal, jwt, authorities);
|
||||||
this.setAuthenticated(true);
|
this.setAuthenticated(true);
|
||||||
if (principal instanceof AuthenticatedPrincipal) {
|
if (principal instanceof AuthenticatedPrincipal authenticatedPrincipal) {
|
||||||
this.name = ((AuthenticatedPrincipal) principal).getName();
|
this.name = authenticatedPrincipal.getName();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.name = jwt.getSubject();
|
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
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return this.name;
|
return (this.name != null) ? this.name : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -126,7 +127,7 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
|
|||||||
*/
|
*/
|
||||||
public static class Builder<B extends Builder<B>> extends AbstractOAuth2TokenAuthenticationBuilder<Jwt, B> {
|
public static class Builder<B extends Builder<B>> extends AbstractOAuth2TokenAuthenticationBuilder<Jwt, B> {
|
||||||
|
|
||||||
private String name;
|
private @Nullable String name;
|
||||||
|
|
||||||
protected Builder(JwtAuthenticationToken token) {
|
protected Builder(JwtAuthenticationToken token) {
|
||||||
super(token);
|
super(token);
|
||||||
@@ -168,10 +169,10 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The name to use.
|
* 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
|
* @return the {@link Builder} for further configurations
|
||||||
*/
|
*/
|
||||||
public B name(String name) {
|
public B name(@Nullable String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
return (B) this;
|
return (B) this;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -23,6 +23,7 @@ import java.util.Collections;
|
|||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.core.log.LogMessage;
|
import org.springframework.core.log.LogMessage;
|
||||||
@@ -105,7 +106,7 @@ public final class JwtGrantedAuthoritiesConverter implements Converter<Jwt, Coll
|
|||||||
this.authoritiesClaimNames = Collections.singletonList(authoritiesClaimName);
|
this.authoritiesClaimNames = Collections.singletonList(authoritiesClaimName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAuthoritiesClaimName(Jwt jwt) {
|
private @Nullable String getAuthoritiesClaimName(Jwt jwt) {
|
||||||
for (String claimName : this.authoritiesClaimNames) {
|
for (String claimName : this.authoritiesClaimNames) {
|
||||||
if (jwt.hasClaim(claimName)) {
|
if (jwt.hasClaim(claimName)) {
|
||||||
return claimName;
|
return claimName;
|
||||||
|
|||||||
+6
-3
@@ -29,7 +29,6 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.core.log.LogMessage;
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -169,7 +168,7 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
|
|||||||
private static class JwtClaimIssuerConverter implements Converter<BearerTokenAuthenticationToken, String> {
|
private static class JwtClaimIssuerConverter implements Converter<BearerTokenAuthenticationToken, String> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String convert(@NonNull BearerTokenAuthenticationToken authentication) {
|
public String convert(BearerTokenAuthenticationToken authentication) {
|
||||||
String token = authentication.getToken();
|
String token = authentication.getToken();
|
||||||
try {
|
try {
|
||||||
String issuer = JWTParser.parse(token).getJWTClaimsSet().getIssuer();
|
String issuer = JWTParser.parse(token).getJWTClaimsSet().getIssuer();
|
||||||
@@ -178,7 +177,8 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception cause) {
|
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);
|
ex.setAuthenticationRequest(authentication);
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
@@ -202,6 +202,9 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("NullAway") // Interface does not declare @Nullable; this
|
||||||
|
// implementation returns null when issuer not
|
||||||
|
// trusted
|
||||||
public AuthenticationManager resolve(String issuer) {
|
public AuthenticationManager resolve(String issuer) {
|
||||||
if (this.trustedIssuer.test(issuer)) {
|
if (this.trustedIssuer.test(issuer)) {
|
||||||
AuthenticationManager authenticationManager = this.authenticationManagers.computeIfAbsent(issuer,
|
AuthenticationManager authenticationManager = this.authenticationManagers.computeIfAbsent(issuer,
|
||||||
|
|||||||
+3
-3
@@ -31,7 +31,6 @@ import reactor.core.scheduler.Schedulers;
|
|||||||
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.core.log.LogMessage;
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||||
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
||||||
@@ -172,7 +171,7 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver
|
|||||||
private static class JwtClaimIssuerConverter implements Converter<BearerTokenAuthenticationToken, Mono<String>> {
|
private static class JwtClaimIssuerConverter implements Converter<BearerTokenAuthenticationToken, Mono<String>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<String> convert(@NonNull BearerTokenAuthenticationToken token) {
|
public Mono<String> convert(BearerTokenAuthenticationToken token) {
|
||||||
try {
|
try {
|
||||||
String issuer = JWTParser.parse(token.getToken()).getJWTClaimsSet().getIssuer();
|
String issuer = JWTParser.parse(token.getToken()).getJWTClaimsSet().getIssuer();
|
||||||
if (issuer == null) {
|
if (issuer == null) {
|
||||||
@@ -184,7 +183,8 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver
|
|||||||
}
|
}
|
||||||
catch (Exception cause) {
|
catch (Exception cause) {
|
||||||
return Mono.error(() -> {
|
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);
|
ex.setAuthenticationRequest(token);
|
||||||
return ex;
|
return ex;
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ public final class JwtReactiveAuthenticationManager implements ReactiveAuthentic
|
|||||||
|
|
||||||
private AuthenticationException onError(JwtException ex) {
|
private AuthenticationException onError(JwtException ex) {
|
||||||
if (ex instanceof BadJwtException) {
|
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);
|
return new AuthenticationServiceException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -22,6 +22,7 @@ import java.util.HashSet;
|
|||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
@@ -104,7 +105,7 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
|||||||
* @throws AuthenticationException if authentication failed for some reason
|
* @throws AuthenticationException if authentication failed for some reason
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
if (!(authentication instanceof BearerTokenAuthenticationToken bearer)) {
|
if (!(authentication instanceof BearerTokenAuthenticationToken bearer)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -129,7 +130,8 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
|||||||
}
|
}
|
||||||
catch (BadOpaqueTokenException failed) {
|
catch (BadOpaqueTokenException failed) {
|
||||||
this.logger.debug("Failed to authenticate since token was invalid");
|
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) {
|
catch (OAuth2IntrospectionException failed) {
|
||||||
throw new AuthenticationServiceException(failed.getMessage(), failed);
|
throw new AuthenticationServiceException(failed.getMessage(), failed);
|
||||||
|
|||||||
+1
-1
@@ -105,7 +105,7 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|||||||
|
|
||||||
private AuthenticationException onError(OAuth2IntrospectionException ex) {
|
private AuthenticationException onError(OAuth2IntrospectionException ex) {
|
||||||
if (ex instanceof BadOpaqueTokenException) {
|
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);
|
return new AuthenticationServiceException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -48,7 +48,8 @@ public final class ReactiveJwtAuthenticationConverter implements Converter<Jwt,
|
|||||||
.collectList()
|
.collectList()
|
||||||
.map((authorities) -> {
|
.map((authorities) -> {
|
||||||
String principalName = jwt.getClaimAsString(this.principalClaimName);
|
String principalName = jwt.getClaimAsString(this.principalClaimName);
|
||||||
return new JwtAuthenticationToken(jwt, authorities, principalName);
|
return new JwtAuthenticationToken(jwt, authorities,
|
||||||
|
(principalName != null) ? principalName : "");
|
||||||
});
|
});
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -18,4 +18,7 @@
|
|||||||
* OAuth 2.0 Resource Server {@code Authentication}s and supporting classes and
|
* OAuth 2.0 Resource Server {@code Authentication}s and supporting classes and
|
||||||
* interfaces.
|
* interfaces.
|
||||||
*/
|
*/
|
||||||
|
@NullMarked
|
||||||
package org.springframework.security.oauth2.server.resource.authentication;
|
package org.springframework.security.oauth2.server.resource.authentication;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|||||||
+3
-1
@@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.resource.introspection;
|
|||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An exception similar to
|
* An exception similar to
|
||||||
* {@link org.springframework.security.authentication.BadCredentialsException} that
|
* {@link org.springframework.security.authentication.BadCredentialsException} that
|
||||||
@@ -35,7 +37,7 @@ public class BadOpaqueTokenException extends OAuth2IntrospectionException {
|
|||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BadOpaqueTokenException(String message, Throwable cause) {
|
public BadOpaqueTokenException(String message, @Nullable Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.resource.introspection;
|
|||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base exception for all OAuth 2.0 Introspection related errors
|
* Base exception for all OAuth 2.0 Introspection related errors
|
||||||
*
|
*
|
||||||
@@ -33,7 +35,7 @@ public class OAuth2IntrospectionException extends RuntimeException {
|
|||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OAuth2IntrospectionException(String message, Throwable cause) {
|
public OAuth2IntrospectionException(String message, @Nullable Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-8
@@ -31,6 +31,7 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
@@ -108,7 +109,7 @@ public final class RestClientOpaqueTokenIntrospector implements OpaqueTokenIntro
|
|||||||
return spec.retrieve().toEntity(STRING_OBJECT_MAP);
|
return spec.retrieve().toEntity(STRING_OBJECT_MAP);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
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(
|
private OAuth2IntrospectionAuthenticatedPrincipal defaultAuthenticationConverter(
|
||||||
OAuth2TokenIntrospectionClaimAccessor accessor) {
|
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);
|
return new OAuth2IntrospectionAuthenticatedPrincipal(accessor.getClaims(), authorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +252,7 @@ public final class RestClientOpaqueTokenIntrospector implements OpaqueTokenIntro
|
|||||||
private interface ArrayListFromStringClaimAccessor extends OAuth2TokenIntrospectionClaimAccessor {
|
private interface ArrayListFromStringClaimAccessor extends OAuth2TokenIntrospectionClaimAccessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default List<String> getScopes() {
|
default @Nullable List<String> getScopes() {
|
||||||
Object value = getClaims().get(OAuth2TokenIntrospectionClaimNames.SCOPE);
|
Object value = getClaims().get(OAuth2TokenIntrospectionClaimNames.SCOPE);
|
||||||
if (value instanceof ArrayListFromString list) {
|
if (value instanceof ArrayListFromString list) {
|
||||||
return list;
|
return list;
|
||||||
@@ -270,9 +272,9 @@ public final class RestClientOpaqueTokenIntrospector implements OpaqueTokenIntro
|
|||||||
|
|
||||||
private final String introspectionUri;
|
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<>();
|
private final List<Consumer<RestClientOpaqueTokenIntrospector>> postProcessors = new ArrayList<>();
|
||||||
|
|
||||||
@@ -322,9 +324,13 @@ public final class RestClientOpaqueTokenIntrospector implements OpaqueTokenIntro
|
|||||||
* @return the {@link RestClientOpaqueTokenIntrospector}
|
* @return the {@link RestClientOpaqueTokenIntrospector}
|
||||||
*/
|
*/
|
||||||
public RestClientOpaqueTokenIntrospector build() {
|
public RestClientOpaqueTokenIntrospector build() {
|
||||||
RestClient restClient = RestClient.builder()
|
RestClient.Builder builder = RestClient.builder();
|
||||||
.defaultHeaders((headers) -> headers.setBasicAuth(this.clientId, this.clientSecret))
|
if (this.clientId != null && this.clientSecret != null) {
|
||||||
.build();
|
String clientId = this.clientId;
|
||||||
|
String clientSecret = this.clientSecret;
|
||||||
|
builder.defaultHeaders((headers) -> headers.setBasicAuth(clientId, clientSecret));
|
||||||
|
}
|
||||||
|
RestClient restClient = builder.build();
|
||||||
RestClientOpaqueTokenIntrospector introspector = new RestClientOpaqueTokenIntrospector(
|
RestClientOpaqueTokenIntrospector introspector = new RestClientOpaqueTokenIntrospector(
|
||||||
this.introspectionUri, restClient);
|
this.introspectionUri, restClient);
|
||||||
this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector));
|
this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector));
|
||||||
|
|||||||
+11
-6
@@ -32,6 +32,7 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
@@ -157,7 +158,7 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
|||||||
return this.restOperations.exchange(requestEntity, STRING_OBJECT_MAP);
|
return this.restOperations.exchange(requestEntity, STRING_OBJECT_MAP);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
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(
|
private OAuth2IntrospectionAuthenticatedPrincipal defaultAuthenticationConverter(
|
||||||
OAuth2TokenIntrospectionClaimAccessor accessor) {
|
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);
|
return new OAuth2IntrospectionAuthenticatedPrincipal(accessor.getClaims(), authorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +304,7 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
|||||||
private interface ArrayListFromStringClaimAccessor extends OAuth2TokenIntrospectionClaimAccessor {
|
private interface ArrayListFromStringClaimAccessor extends OAuth2TokenIntrospectionClaimAccessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default List<String> getScopes() {
|
default @Nullable List<String> getScopes() {
|
||||||
Object value = getClaims().get(OAuth2TokenIntrospectionClaimNames.SCOPE);
|
Object value = getClaims().get(OAuth2TokenIntrospectionClaimNames.SCOPE);
|
||||||
if (value instanceof ArrayListFromString list) {
|
if (value instanceof ArrayListFromString list) {
|
||||||
return list;
|
return list;
|
||||||
@@ -322,9 +324,9 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
|||||||
|
|
||||||
private final String introspectionUri;
|
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<>();
|
private final List<Consumer<SpringOpaqueTokenIntrospector>> postProcessors = new ArrayList<>();
|
||||||
|
|
||||||
@@ -379,7 +381,10 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
|||||||
*/
|
*/
|
||||||
public SpringOpaqueTokenIntrospector build() {
|
public SpringOpaqueTokenIntrospector build() {
|
||||||
RestTemplate restTemplate = new RestTemplate();
|
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,
|
SpringOpaqueTokenIntrospector introspector = new SpringOpaqueTokenIntrospector(this.introspectionUri,
|
||||||
restTemplate);
|
restTemplate);
|
||||||
this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector));
|
this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector));
|
||||||
|
|||||||
+14
-8
@@ -30,6 +30,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
@@ -188,7 +189,7 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
|
|||||||
}
|
}
|
||||||
|
|
||||||
private OAuth2IntrospectionException onError(Throwable ex) {
|
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(
|
private Mono<OAuth2IntrospectionAuthenticatedPrincipal> defaultAuthenticationConverter(
|
||||||
OAuth2TokenIntrospectionClaimAccessor accessor) {
|
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));
|
return Mono.just(new OAuth2IntrospectionAuthenticatedPrincipal(accessor.getClaims(), authorities));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +257,7 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
|
|||||||
private interface ArrayListFromStringClaimAccessor extends OAuth2TokenIntrospectionClaimAccessor {
|
private interface ArrayListFromStringClaimAccessor extends OAuth2TokenIntrospectionClaimAccessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default List<String> getScopes() {
|
default @Nullable List<String> getScopes() {
|
||||||
Object value = getClaims().get(OAuth2TokenIntrospectionClaimNames.SCOPE);
|
Object value = getClaims().get(OAuth2TokenIntrospectionClaimNames.SCOPE);
|
||||||
if (value instanceof ArrayListFromString list) {
|
if (value instanceof ArrayListFromString list) {
|
||||||
return list;
|
return list;
|
||||||
@@ -275,9 +277,9 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
|
|||||||
|
|
||||||
private final String introspectionUri;
|
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<>();
|
private final List<Consumer<SpringReactiveOpaqueTokenIntrospector>> postProcessors = new ArrayList<>();
|
||||||
|
|
||||||
@@ -332,9 +334,13 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
|
|||||||
* @since 6.5
|
* @since 6.5
|
||||||
*/
|
*/
|
||||||
public SpringReactiveOpaqueTokenIntrospector build() {
|
public SpringReactiveOpaqueTokenIntrospector build() {
|
||||||
WebClient webClient = WebClient.builder()
|
WebClient.Builder builder = WebClient.builder();
|
||||||
.defaultHeaders((h) -> h.setBasicAuth(this.clientId, this.clientSecret))
|
if (this.clientId != null && this.clientSecret != null) {
|
||||||
.build();
|
String clientId = this.clientId;
|
||||||
|
String clientSecret = this.clientSecret;
|
||||||
|
builder.defaultHeaders((h) -> h.setBasicAuth(clientId, clientSecret));
|
||||||
|
}
|
||||||
|
WebClient webClient = builder.build();
|
||||||
SpringReactiveOpaqueTokenIntrospector introspector = new SpringReactiveOpaqueTokenIntrospector(
|
SpringReactiveOpaqueTokenIntrospector introspector = new SpringReactiveOpaqueTokenIntrospector(
|
||||||
this.introspectionUri, webClient);
|
this.introspectionUri, webClient);
|
||||||
this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector));
|
this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector));
|
||||||
|
|||||||
+3
@@ -17,4 +17,7 @@
|
|||||||
/**
|
/**
|
||||||
* OAuth 2.0 Introspection supporting classes and interfaces.
|
* OAuth 2.0 Introspection supporting classes and interfaces.
|
||||||
*/
|
*/
|
||||||
|
@NullMarked
|
||||||
package org.springframework.security.oauth2.server.resource.introspection;
|
package org.springframework.security.oauth2.server.resource.introspection;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|||||||
+3
@@ -17,4 +17,7 @@
|
|||||||
/**
|
/**
|
||||||
* OAuth 2.0 Resource Server core classes and interfaces providing support.
|
* OAuth 2.0 Resource Server core classes and interfaces providing support.
|
||||||
*/
|
*/
|
||||||
|
@NullMarked
|
||||||
package org.springframework.security.oauth2.server.resource;
|
package org.springframework.security.oauth2.server.resource;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|||||||
+4
-3
@@ -22,6 +22,7 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -51,7 +52,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|||||||
*/
|
*/
|
||||||
public final class BearerTokenAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
public final class BearerTokenAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
|
|
||||||
private String realmName;
|
private @Nullable String realmName;
|
||||||
|
|
||||||
private Function<HttpServletRequest, String> resourceMetadataParameterResolver = BearerTokenAuthenticationEntryPoint::getResourceMetadataParameter;
|
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
|
* 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;
|
this.realmName = realmName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.security.oauth2.server.resource.web;
|
package org.springframework.security.oauth2.server.resource.web;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
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
|
* @return the Bearer Token value or {@code null} if none found
|
||||||
* @throws OAuth2AuthenticationException if the found token is invalid
|
* @throws OAuth2AuthenticationException if the found token is invalid
|
||||||
*/
|
*/
|
||||||
String resolve(HttpServletRequest request);
|
@Nullable String resolve(HttpServletRequest request);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -20,6 +20,7 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
@@ -51,7 +52,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
|
|||||||
private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;
|
private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String resolve(final HttpServletRequest request) {
|
public @Nullable String resolve(final HttpServletRequest request) {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return resolveToken(
|
return resolveToken(
|
||||||
resolveFromAuthorizationHeader(request),
|
resolveFromAuthorizationHeader(request),
|
||||||
@@ -61,7 +62,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
|
|||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String resolveToken(String... accessTokens) {
|
private static @Nullable String resolveToken(@Nullable String... accessTokens) {
|
||||||
if (accessTokens == null || accessTokens.length == 0) {
|
if (accessTokens == null || accessTokens.length == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -87,7 +88,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
|
|||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveFromAuthorizationHeader(HttpServletRequest request) {
|
private @Nullable String resolveFromAuthorizationHeader(HttpServletRequest request) {
|
||||||
String authorization = request.getHeader(this.bearerTokenHeaderName);
|
String authorization = request.getHeader(this.bearerTokenHeaderName);
|
||||||
if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
|
if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
|
||||||
return null;
|
return null;
|
||||||
@@ -102,7 +103,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
|
|||||||
return matcher.group("token");
|
return matcher.group("token");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveAccessTokenFromQueryString(HttpServletRequest request) {
|
private @Nullable String resolveAccessTokenFromQueryString(HttpServletRequest request) {
|
||||||
if (!this.allowUriQueryParameter || !HttpMethod.GET.name().equals(request.getMethod())) {
|
if (!this.allowUriQueryParameter || !HttpMethod.GET.name().equals(request.getMethod())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -110,7 +111,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
|
|||||||
return resolveToken(request.getParameterValues(ACCESS_TOKEN_PARAMETER_NAME));
|
return resolveToken(request.getParameterValues(ACCESS_TOKEN_PARAMETER_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveAccessTokenFromBody(HttpServletRequest request) {
|
private @Nullable String resolveAccessTokenFromBody(HttpServletRequest request) {
|
||||||
if (!this.allowFormEncodedBodyParameter
|
if (!this.allowFormEncodedBodyParameter
|
||||||
|| !MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())
|
|| !MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())
|
||||||
|| HttpMethod.GET.name().equals(request.getMethod())) {
|
|| HttpMethod.GET.name().equals(request.getMethod())) {
|
||||||
|
|||||||
+6
-3
@@ -24,6 +24,7 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
import org.springframework.http.HttpMethod;
|
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 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();
|
.getJsonMessageConverter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,8 +109,10 @@ public final class OAuth2ProtectedResourceMetadataFilter extends OncePerRequestF
|
|||||||
OAuth2ProtectedResourceMetadata protectedResourceMetadata = builder.build();
|
OAuth2ProtectedResourceMetadata protectedResourceMetadata = builder.build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
GenericHttpMessageConverter<Object> converter = JSON_MESSAGE_CONVERTER;
|
||||||
|
Assert.notNull(converter, "No JSON message converter available");
|
||||||
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
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);
|
MediaType.APPLICATION_JSON, httpResponse);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
@@ -161,7 +164,7 @@ public final class OAuth2ProtectedResourceMetadataFilter extends OncePerRequestF
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("removal")
|
@SuppressWarnings("removal")
|
||||||
private static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
|
private static @Nullable GenericHttpMessageConverter<Object> getJsonMessageConverter() {
|
||||||
if (jacksonPresent) {
|
if (jacksonPresent) {
|
||||||
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
|
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -21,6 +21,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -46,7 +47,7 @@ import org.springframework.security.web.access.AccessDeniedHandler;
|
|||||||
*/
|
*/
|
||||||
public final class BearerTokenAccessDeniedHandler implements 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
|
* 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
|
* Set the default realm name to use in the bearer token error response
|
||||||
* @param realmName
|
* @param realmName
|
||||||
*/
|
*/
|
||||||
public void setRealmName(String realmName) {
|
public void setRealmName(@Nullable String realmName) {
|
||||||
this.realmName = realmName;
|
this.realmName = realmName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
@@ -17,4 +17,7 @@
|
|||||||
/**
|
/**
|
||||||
* OAuth 2.0 Resource Server access denial classes and interfaces.
|
* OAuth 2.0 Resource Server access denial classes and interfaces.
|
||||||
*/
|
*/
|
||||||
|
@NullMarked
|
||||||
package org.springframework.security.oauth2.server.resource.web.access;
|
package org.springframework.security.oauth2.server.resource.web.access;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|||||||
+3
-2
@@ -21,6 +21,7 @@ import java.util.Collection;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
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 static final Collection<String> WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES = Arrays.asList("scope", "scp");
|
||||||
|
|
||||||
private String realmName;
|
private @Nullable String realmName;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
|
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
|
* Set the default realm name to use in the bearer token error response
|
||||||
* @param realmName
|
* @param realmName
|
||||||
*/
|
*/
|
||||||
public final void setRealmName(String realmName) {
|
public final void setRealmName(@Nullable String realmName) {
|
||||||
this.realmName = realmName;
|
this.realmName = realmName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
@@ -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;
|
||||||
+2
-1
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.security.oauth2.server.resource.web.authentication;
|
package org.springframework.security.oauth2.server.resource.web.authentication;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -43,7 +44,7 @@ public final class BearerTokenAuthenticationConverter implements AuthenticationC
|
|||||||
private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
|
private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication convert(HttpServletRequest request) {
|
public @Nullable Authentication convert(HttpServletRequest request) {
|
||||||
String token = this.bearerTokenResolver.resolve(request);
|
String token = this.bearerTokenResolver.resolve(request);
|
||||||
if (StringUtils.hasText(token)) {
|
if (StringUtils.hasText(token)) {
|
||||||
BearerTokenAuthenticationToken authenticationToken = new BearerTokenAuthenticationToken(token);
|
BearerTokenAuthenticationToken authenticationToken = new BearerTokenAuthenticationToken(token);
|
||||||
|
|||||||
+23
@@ -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;
|
||||||
+3
@@ -17,4 +17,7 @@
|
|||||||
/**
|
/**
|
||||||
* OAuth 2.0 Resource Server {@code Filter}'s and supporting classes and interfaces.
|
* OAuth 2.0 Resource Server {@code Filter}'s and supporting classes and interfaces.
|
||||||
*/
|
*/
|
||||||
|
@NullMarked
|
||||||
package org.springframework.security.oauth2.server.resource.web;
|
package org.springframework.security.oauth2.server.resource.web;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|||||||
+9
-8
@@ -20,8 +20,8 @@ import reactor.core.publisher.Mono;
|
|||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
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.ClientRequest;
|
||||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||||
@@ -63,17 +63,18 @@ public final class ServerBearerExchangeFilterFunction implements ExchangeFilterF
|
|||||||
private Mono<OAuth2Token> oauth2Token() {
|
private Mono<OAuth2Token> oauth2Token() {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return currentAuthentication()
|
return currentAuthentication()
|
||||||
.filter((authentication) -> authentication.getCredentials() instanceof OAuth2Token)
|
.filter((authentication) -> authentication.getCredentials() != null
|
||||||
.map(Authentication::getCredentials)
|
&& authentication.getCredentials() instanceof OAuth2Token)
|
||||||
.cast(OAuth2Token.class);
|
.map((authentication) -> {
|
||||||
|
Object credentials = authentication.getCredentials();
|
||||||
|
Assert.notNull(credentials, "credentials cannot be null");
|
||||||
|
return (OAuth2Token) credentials;
|
||||||
|
});
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<Authentication> currentAuthentication() {
|
private Mono<Authentication> currentAuthentication() {
|
||||||
// @formatter:off
|
return ReactiveSecurityContextHolder.getContext().flatMap((ctx) -> Mono.justOrEmpty(ctx.getAuthentication()));
|
||||||
return ReactiveSecurityContextHolder.getContext()
|
|
||||||
.map(SecurityContext::getAuthentication);
|
|
||||||
// @formatter:on
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientRequest bearer(ClientRequest request, OAuth2Token token) {
|
private ClientRequest bearer(ClientRequest request, OAuth2Token token) {
|
||||||
|
|||||||
+10
-4
@@ -18,11 +18,13 @@ package org.springframework.security.oauth2.server.resource.web.reactive.functio
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.util.context.Context;
|
import reactor.util.context.Context;
|
||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
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.ClientRequest;
|
||||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||||
@@ -71,9 +73,13 @@ public final class ServletBearerExchangeFilterFunction implements ExchangeFilter
|
|||||||
return Mono.deferContextual(Mono::just)
|
return Mono.deferContextual(Mono::just)
|
||||||
.cast(Context.class)
|
.cast(Context.class)
|
||||||
.flatMap(this::currentAuthentication)
|
.flatMap(this::currentAuthentication)
|
||||||
.filter((authentication) -> authentication.getCredentials() instanceof OAuth2Token)
|
.filter((authentication) -> authentication.getCredentials() != null
|
||||||
.map(Authentication::getCredentials)
|
&& authentication.getCredentials() instanceof OAuth2Token)
|
||||||
.cast(OAuth2Token.class);
|
.map((authentication) -> {
|
||||||
|
Object credentials = authentication.getCredentials();
|
||||||
|
Assert.notNull(credentials, "credentials cannot be null");
|
||||||
|
return (OAuth2Token) credentials;
|
||||||
|
});
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +87,7 @@ public final class ServletBearerExchangeFilterFunction implements ExchangeFilter
|
|||||||
return Mono.justOrEmpty(getAttribute(ctx, Authentication.class));
|
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
|
// NOTE: SecurityReactorContextConfiguration.SecurityReactorContextSubscriber adds
|
||||||
// this key
|
// this key
|
||||||
if (!ctx.hasKey(SECURITY_REACTOR_CONTEXT_ATTRIBUTES_KEY)) {
|
if (!ctx.hasKey(SECURITY_REACTOR_CONTEXT_ATTRIBUTES_KEY)) {
|
||||||
|
|||||||
+23
@@ -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;
|
||||||
+3
-2
@@ -19,6 +19,7 @@ package org.springframework.security.oauth2.server.resource.web.server;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
@@ -49,9 +50,9 @@ import org.springframework.web.server.ServerWebExchange;
|
|||||||
*/
|
*/
|
||||||
public final class BearerTokenServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
|
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;
|
this.realmName = realmName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
@@ -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;
|
||||||
+23
@@ -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;
|
||||||
+6
-6
@@ -47,10 +47,10 @@ public class JwtAuthenticationTokenTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getNameWhenJwtHasNoSubjectThenReturnsNull() {
|
public void getNameWhenJwtHasNoSubjectThenReturnsEmptyString() {
|
||||||
Jwt jwt = builder().claim("claim", "value").build();
|
Jwt jwt = builder().claim("claim", "value").build();
|
||||||
JwtAuthenticationToken token = new JwtAuthenticationToken(jwt);
|
JwtAuthenticationToken token = new JwtAuthenticationToken(jwt);
|
||||||
assertThat(token.getName()).isNull();
|
assertThat(token.getName()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -108,12 +108,12 @@ public class JwtAuthenticationTokenTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getNameWhenConstructedWithNoSubjectThenReturnsNull() {
|
public void getNameWhenConstructedWithNoSubjectThenReturnsEmptyString() {
|
||||||
Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("test");
|
Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("test");
|
||||||
Jwt jwt = builder().claim("claim", "value").build();
|
Jwt jwt = builder().claim("claim", "value").build();
|
||||||
assertThat(new JwtAuthenticationToken(jwt, authorities, (String) null).getName()).isNull();
|
assertThat(new JwtAuthenticationToken(jwt, authorities, (String) null).getName()).isEmpty();
|
||||||
assertThat(new JwtAuthenticationToken(jwt, authorities).getName()).isNull();
|
assertThat(new JwtAuthenticationToken(jwt, authorities).getName()).isEmpty();
|
||||||
assertThat(new JwtAuthenticationToken(jwt).getName()).isNull();
|
assertThat(new JwtAuthenticationToken(jwt).getName()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user