Add support for opaque access tokens
Closes gh-500
This commit is contained in:
@@ -26,6 +26,8 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -367,11 +369,12 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
providerSettings.getTokenIntrospectionEndpoint());
|
||||
builder.addFilterAfter(postProcess(tokenIntrospectionEndpointFilter), FilterSecurityInterceptor.class);
|
||||
|
||||
NimbusJwkSetEndpointFilter jwkSetEndpointFilter =
|
||||
new NimbusJwkSetEndpointFilter(
|
||||
OAuth2ConfigurerUtils.getJwkSource(builder),
|
||||
providerSettings.getJwkSetEndpoint());
|
||||
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
JWKSource<com.nimbusds.jose.proc.SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(builder);
|
||||
if (jwkSource != null) {
|
||||
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
|
||||
jwkSource, providerSettings.getJwkSetEndpoint());
|
||||
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
|
||||
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
|
||||
|
||||
@@ -34,9 +34,11 @@ import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2Au
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AccessTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2RefreshTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenClaimsContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
@@ -93,28 +95,55 @@ final class OAuth2ConfigurerUtils {
|
||||
if (tokenGenerator == null) {
|
||||
tokenGenerator = getOptionalBean(builder, OAuth2TokenGenerator.class);
|
||||
if (tokenGenerator == null) {
|
||||
JwtGenerator jwtGenerator = new JwtGenerator(getJwtEncoder(builder));
|
||||
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(builder);
|
||||
if (jwtCustomizer != null) {
|
||||
jwtGenerator.setJwtCustomizer(jwtCustomizer);
|
||||
JwtGenerator jwtGenerator = getJwtGenerator(builder);
|
||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer = getAccessTokenCustomizer(builder);
|
||||
if (accessTokenCustomizer != null) {
|
||||
accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer);
|
||||
}
|
||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
||||
tokenGenerator = new DelegatingOAuth2TokenGenerator(jwtGenerator, refreshTokenGenerator);
|
||||
if (jwtGenerator != null) {
|
||||
tokenGenerator = new DelegatingOAuth2TokenGenerator(
|
||||
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
|
||||
} else {
|
||||
tokenGenerator = new DelegatingOAuth2TokenGenerator(
|
||||
accessTokenGenerator, refreshTokenGenerator);
|
||||
}
|
||||
}
|
||||
builder.setSharedObject(OAuth2TokenGenerator.class, tokenGenerator);
|
||||
}
|
||||
return tokenGenerator;
|
||||
}
|
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> JwtGenerator getJwtGenerator(B builder) {
|
||||
JwtGenerator jwtGenerator = builder.getSharedObject(JwtGenerator.class);
|
||||
if (jwtGenerator == null) {
|
||||
JwtEncoder jwtEncoder = getJwtEncoder(builder);
|
||||
if (jwtEncoder != null) {
|
||||
jwtGenerator = new JwtGenerator(jwtEncoder);
|
||||
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(builder);
|
||||
if (jwtCustomizer != null) {
|
||||
jwtGenerator.setJwtCustomizer(jwtCustomizer);
|
||||
}
|
||||
builder.setSharedObject(JwtGenerator.class, jwtGenerator);
|
||||
}
|
||||
}
|
||||
return jwtGenerator;
|
||||
}
|
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> JwtEncoder getJwtEncoder(B builder) {
|
||||
JwtEncoder jwtEncoder = builder.getSharedObject(JwtEncoder.class);
|
||||
if (jwtEncoder == null) {
|
||||
jwtEncoder = getOptionalBean(builder, JwtEncoder.class);
|
||||
if (jwtEncoder == null) {
|
||||
JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
|
||||
jwtEncoder = new NimbusJwsEncoder(jwkSource);
|
||||
if (jwkSource != null) {
|
||||
jwtEncoder = new NimbusJwsEncoder(jwkSource);
|
||||
}
|
||||
}
|
||||
if (jwtEncoder != null) {
|
||||
builder.setSharedObject(JwtEncoder.class, jwtEncoder);
|
||||
}
|
||||
builder.setSharedObject(JwtEncoder.class, jwtEncoder);
|
||||
}
|
||||
return jwtEncoder;
|
||||
}
|
||||
@@ -124,23 +153,22 @@ final class OAuth2ConfigurerUtils {
|
||||
JWKSource<SecurityContext> jwkSource = builder.getSharedObject(JWKSource.class);
|
||||
if (jwkSource == null) {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(JWKSource.class, SecurityContext.class);
|
||||
jwkSource = getBean(builder, type);
|
||||
builder.setSharedObject(JWKSource.class, jwkSource);
|
||||
jwkSource = getOptionalBean(builder, type);
|
||||
if (jwkSource != null) {
|
||||
builder.setSharedObject(JWKSource.class, jwkSource);
|
||||
}
|
||||
}
|
||||
return jwkSource;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(B builder) {
|
||||
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = builder.getSharedObject(OAuth2TokenCustomizer.class);
|
||||
if (jwtCustomizer == null) {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
|
||||
jwtCustomizer = getOptionalBean(builder, type);
|
||||
if (jwtCustomizer != null) {
|
||||
builder.setSharedObject(OAuth2TokenCustomizer.class, jwtCustomizer);
|
||||
}
|
||||
}
|
||||
return jwtCustomizer;
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
|
||||
return getOptionalBean(builder, type);
|
||||
}
|
||||
|
||||
private static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(B builder) {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, OAuth2TokenClaimsContext.class);
|
||||
return getOptionalBean(builder, type);
|
||||
}
|
||||
|
||||
static <B extends HttpSecurityBuilder<B>> ProviderSettings getProviderSettings(B builder) {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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.
|
||||
*/
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link ClaimAccessor} for the "claims" that may be contained in an {@link OAuth2TokenClaimsSet}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.3
|
||||
* @see ClaimAccessor
|
||||
* @see OAuth2TokenClaimNames
|
||||
* @see OAuth2TokenClaimsSet
|
||||
*/
|
||||
public interface OAuth2TokenClaimAccessor extends ClaimAccessor {
|
||||
|
||||
/**
|
||||
* Returns the Issuer {@code (iss)} claim which identifies the principal that issued the OAuth 2.0 Token.
|
||||
*
|
||||
* @return the Issuer identifier
|
||||
*/
|
||||
default URL getIssuer() {
|
||||
return getClaimAsURL(OAuth2TokenClaimNames.ISS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Subject {@code (sub)} claim which identifies the principal that is the subject of the OAuth 2.0 Token.
|
||||
*
|
||||
* @return the Subject identifier
|
||||
*/
|
||||
default String getSubject() {
|
||||
return getClaimAsString(OAuth2TokenClaimNames.SUB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Audience {@code (aud)} claim which identifies the recipient(s) that the OAuth 2.0 Token is intended for.
|
||||
*
|
||||
* @return the Audience(s) that this OAuth 2.0 Token is intended for
|
||||
*/
|
||||
default List<String> getAudience() {
|
||||
return getClaimAsStringList(OAuth2TokenClaimNames.AUD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Expiration time {@code (exp)} claim which identifies the expiration time on or after
|
||||
* which the OAuth 2.0 Token MUST NOT be accepted for processing.
|
||||
*
|
||||
* @return the Expiration time on or after which the OAuth 2.0 Token MUST NOT be accepted for processing
|
||||
*/
|
||||
default Instant getExpiresAt() {
|
||||
return getClaimAsInstant(OAuth2TokenClaimNames.EXP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Not Before {@code (nbf)} claim which identifies the time before
|
||||
* which the OAuth 2.0 Token MUST NOT be accepted for processing.
|
||||
*
|
||||
* @return the Not Before time before which the OAuth 2.0 Token MUST NOT be accepted for processing
|
||||
*/
|
||||
default Instant getNotBefore() {
|
||||
return getClaimAsInstant(OAuth2TokenClaimNames.NBF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Issued at {@code (iat)} claim which identifies the time at which the OAuth 2.0 Token was issued.
|
||||
*
|
||||
* @return the Issued at claim which identifies the time at which the OAuth 2.0 Token was issued
|
||||
*/
|
||||
default Instant getIssuedAt() {
|
||||
return getClaimAsInstant(OAuth2TokenClaimNames.IAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID {@code (jti)} claim which provides a unique identifier for the OAuth 2.0 Token.
|
||||
*
|
||||
* @return the ID claim which provides a unique identifier for the OAuth 2.0 Token
|
||||
*/
|
||||
default String getId() {
|
||||
return getClaimAsString(OAuth2TokenClaimNames.JTI);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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.
|
||||
*/
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
/**
|
||||
* The names of the "claims" that may be contained in an {@link OAuth2TokenClaimsSet}
|
||||
* and are associated to an {@link OAuth2Token}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.3
|
||||
* @see OAuth2TokenClaimAccessor
|
||||
* @see OAuth2TokenClaimsSet
|
||||
* @see OAuth2Token
|
||||
*/
|
||||
public interface OAuth2TokenClaimNames {
|
||||
|
||||
/**
|
||||
* {@code iss} - the Issuer claim identifies the principal that issued the OAuth 2.0 Token
|
||||
*/
|
||||
String ISS = "iss";
|
||||
|
||||
/**
|
||||
* {@code sub} - the Subject claim identifies the principal that is the subject of the OAuth 2.0 Token
|
||||
*/
|
||||
String SUB = "sub";
|
||||
|
||||
/**
|
||||
* {@code aud} - the Audience claim identifies the recipient(s) that the OAuth 2.0 Token is intended for
|
||||
*/
|
||||
String AUD = "aud";
|
||||
|
||||
/**
|
||||
* {@code exp} - the Expiration time claim identifies the expiration time on or after
|
||||
* which the OAuth 2.0 Token MUST NOT be accepted for processing
|
||||
*/
|
||||
String EXP = "exp";
|
||||
|
||||
/**
|
||||
* {@code nbf} - the Not Before claim identifies the time before which the OAuth 2.0 Token
|
||||
* MUST NOT be accepted for processing
|
||||
*/
|
||||
String NBF = "nbf";
|
||||
|
||||
/**
|
||||
* {@code iat} - The Issued at claim identifies the time at which the OAuth 2.0 Token was issued
|
||||
*/
|
||||
String IAT = "iat";
|
||||
|
||||
/**
|
||||
* {@code jti} - The ID claim provides a unique identifier for the OAuth 2.0 Token
|
||||
*/
|
||||
String JTI = "jti";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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.
|
||||
*/
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A representation of a set of claims that are associated to an {@link OAuth2Token}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.3
|
||||
* @see OAuth2TokenClaimAccessor
|
||||
* @see OAuth2TokenClaimNames
|
||||
* @see OAuth2Token
|
||||
*/
|
||||
public final class OAuth2TokenClaimsSet implements OAuth2TokenClaimAccessor {
|
||||
private final Map<String, Object> claims;
|
||||
|
||||
private OAuth2TokenClaimsSet(Map<String, Object> claims) {
|
||||
this.claims = Collections.unmodifiableMap(new HashMap<>(claims));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder}.
|
||||
*
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link OAuth2TokenClaimsSet}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private final Map<String, Object> claims = new HashMap<>();
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the issuer {@code (iss)} claim, which identifies the principal that issued the OAuth 2.0 Token.
|
||||
*
|
||||
* @param issuer the issuer identifier
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder issuer(String issuer) {
|
||||
return claim(OAuth2TokenClaimNames.ISS, issuer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subject {@code (sub)} claim, which identifies the principal that is the subject of the OAuth 2.0 Token.
|
||||
*
|
||||
* @param subject the subject identifier
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder subject(String subject) {
|
||||
return claim(OAuth2TokenClaimNames.SUB, subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the audience {@code (aud)} claim, which identifies the recipient(s) that the OAuth 2.0 Token is intended for.
|
||||
*
|
||||
* @param audience the audience that this OAuth 2.0 Token is intended for
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder audience(List<String> audience) {
|
||||
return claim(OAuth2TokenClaimNames.AUD, audience);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expiration time {@code (exp)} claim, which identifies the time on or after
|
||||
* which the OAuth 2.0 Token MUST NOT be accepted for processing.
|
||||
*
|
||||
* @param expiresAt the time on or after which the OAuth 2.0 Token MUST NOT be accepted for processing
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder expiresAt(Instant expiresAt) {
|
||||
return claim(OAuth2TokenClaimNames.EXP, expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the not before {@code (nbf)} claim, which identifies the time before
|
||||
* which the OAuth 2.0 Token MUST NOT be accepted for processing.
|
||||
*
|
||||
* @param notBefore the time before which the OAuth 2.0 Token MUST NOT be accepted for processing
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder notBefore(Instant notBefore) {
|
||||
return claim(OAuth2TokenClaimNames.NBF, notBefore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the issued at {@code (iat)} claim, which identifies the time at which the OAuth 2.0 Token was issued.
|
||||
*
|
||||
* @param issuedAt the time at which the OAuth 2.0 Token was issued
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder issuedAt(Instant issuedAt) {
|
||||
return claim(OAuth2TokenClaimNames.IAT, issuedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID {@code (jti)} claim, which provides a unique identifier for the OAuth 2.0 Token.
|
||||
*
|
||||
* @param jti the unique identifier for the OAuth 2.0 Token
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder id(String jti) {
|
||||
return claim(OAuth2TokenClaimNames.JTI, jti);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the claim.
|
||||
*
|
||||
* @param name the claim name
|
||||
* @param value the claim value
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder claim(String name, Object value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
this.claims.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} to be provided access to the claims allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param claimsConsumer a {@code Consumer} of the claims
|
||||
*/
|
||||
public Builder claims(Consumer<Map<String, Object>> claimsConsumer) {
|
||||
claimsConsumer.accept(this.claims);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link OAuth2TokenClaimsSet}.
|
||||
*
|
||||
* @return a {@link OAuth2TokenClaimsSet}
|
||||
*/
|
||||
public OAuth2TokenClaimsSet build() {
|
||||
Assert.notEmpty(this.claims, "claims cannot be empty");
|
||||
|
||||
// The value of the 'iss' claim is a String or URL (StringOrURI).
|
||||
// Attempt to convert to URL.
|
||||
Object issuer = this.claims.get(OAuth2TokenClaimNames.ISS);
|
||||
if (issuer != null) {
|
||||
URL convertedValue = ClaimConversionService.getSharedInstance().convert(issuer, URL.class);
|
||||
if (convertedValue != null) {
|
||||
this.claims.put(OAuth2TokenClaimNames.ISS, convertedValue);
|
||||
}
|
||||
}
|
||||
|
||||
return new OAuth2TokenClaimsSet(this.claims);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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.
|
||||
*/
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Standard data formats for OAuth 2.0 Tokens.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.3
|
||||
*/
|
||||
public final class OAuth2TokenFormat implements Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
|
||||
/**
|
||||
* Self-contained tokens use a protected, time-limited data structure that contains token metadata
|
||||
* and claims of the user and/or client. JSON Web Token (JWT) is a widely used format.
|
||||
*/
|
||||
public static final OAuth2TokenFormat SELF_CONTAINED = new OAuth2TokenFormat("self-contained");
|
||||
|
||||
/**
|
||||
* Reference (opaque) tokens are unique identifiers that serve as a reference
|
||||
* to the token metadata and claims of the user and/or client, stored at the provider.
|
||||
*/
|
||||
public static final OAuth2TokenFormat REFERENCE = new OAuth2TokenFormat("reference");
|
||||
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2TokenFormat} using the provided value.
|
||||
*
|
||||
* @param value the value of the token format
|
||||
*/
|
||||
public OAuth2TokenFormat(String value) {
|
||||
Assert.hasText(value, "value cannot be empty");
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the token format.
|
||||
*
|
||||
* @return the value of the token format
|
||||
*/
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
OAuth2TokenFormat that = (OAuth2TokenFormat) obj;
|
||||
return getValue().equals(that.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getValue().hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import java.util.function.Consumer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
@@ -75,6 +76,10 @@ public final class JwtGenerator implements OAuth2TokenGenerator<Jwt> {
|
||||
!OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue()))) {
|
||||
return null;
|
||||
}
|
||||
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) &&
|
||||
!OAuth2TokenFormat.SELF_CONTAINED.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String issuer = null;
|
||||
if (context.getProviderContext() != null) {
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenClaimsSet;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2TokenGenerator} that generates a
|
||||
* {@link OAuth2TokenFormat#REFERENCE "reference"} (opaque) {@link OAuth2AccessToken}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.3
|
||||
* @see OAuth2TokenGenerator
|
||||
* @see OAuth2AccessToken
|
||||
* @see OAuth2TokenCustomizer
|
||||
* @see OAuth2TokenClaimsContext
|
||||
* @see OAuth2TokenClaimsSet
|
||||
*/
|
||||
public final class OAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OAuth2AccessToken> {
|
||||
private final StringKeyGenerator accessTokenGenerator =
|
||||
new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
|
||||
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public OAuth2AccessToken generate(OAuth2TokenContext context) {
|
||||
if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) ||
|
||||
!OAuth2TokenFormat.REFERENCE.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String issuer = null;
|
||||
if (context.getProviderContext() != null) {
|
||||
issuer = context.getProviderContext().getIssuer();
|
||||
}
|
||||
RegisteredClient registeredClient = context.getRegisteredClient();
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive());
|
||||
|
||||
// @formatter:off
|
||||
OAuth2TokenClaimsSet.Builder claimsBuilder = OAuth2TokenClaimsSet.builder();
|
||||
if (StringUtils.hasText(issuer)) {
|
||||
claimsBuilder.issuer(issuer);
|
||||
}
|
||||
claimsBuilder
|
||||
.subject(context.getPrincipal().getName())
|
||||
.audience(Collections.singletonList(registeredClient.getClientId()))
|
||||
.issuedAt(issuedAt)
|
||||
.expiresAt(expiresAt)
|
||||
.notBefore(issuedAt)
|
||||
.id(UUID.randomUUID().toString());
|
||||
if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) {
|
||||
claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes());
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
if (this.accessTokenCustomizer != null) {
|
||||
// @formatter:off
|
||||
OAuth2TokenClaimsContext.Builder accessTokenContextBuilder = OAuth2TokenClaimsContext.with(claimsBuilder)
|
||||
.registeredClient(context.getRegisteredClient())
|
||||
.principal(context.getPrincipal())
|
||||
.providerContext(context.getProviderContext())
|
||||
.authorizedScopes(context.getAuthorizedScopes())
|
||||
.tokenType(context.getTokenType())
|
||||
.authorizationGrantType(context.getAuthorizationGrantType());
|
||||
if (context.getAuthorization() != null) {
|
||||
accessTokenContextBuilder.authorization(context.getAuthorization());
|
||||
}
|
||||
if (context.getAuthorizationGrant() != null) {
|
||||
accessTokenContextBuilder.authorizationGrant(context.getAuthorizationGrant());
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
OAuth2TokenClaimsContext accessTokenContext = accessTokenContextBuilder.build();
|
||||
this.accessTokenCustomizer.customize(accessTokenContext);
|
||||
}
|
||||
|
||||
OAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build();
|
||||
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER,
|
||||
this.accessTokenGenerator.generateKey(), accessTokenClaimsSet.getIssuedAt(), accessTokenClaimsSet.getExpiresAt(),
|
||||
context.getAuthorizedScopes(), accessTokenClaimsSet.getClaims());
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2TokenCustomizer} that customizes the
|
||||
* {@link OAuth2TokenClaimsContext.Builder#claims(Consumer) claims} for the {@link OAuth2AccessToken}.
|
||||
*
|
||||
* @param accessTokenCustomizer the {@link OAuth2TokenCustomizer} that customizes the claims for the {@code OAuth2AccessToken}
|
||||
*/
|
||||
public void setAccessTokenCustomizer(OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer) {
|
||||
Assert.notNull(accessTokenCustomizer, "accessTokenCustomizer cannot be null");
|
||||
this.accessTokenCustomizer = accessTokenCustomizer;
|
||||
}
|
||||
|
||||
private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor {
|
||||
private final Map<String, Object> claims;
|
||||
|
||||
private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue,
|
||||
Instant issuedAt, Instant expiresAt, Set<String> scopes, Map<String, Object> claims) {
|
||||
super(tokenType, tokenValue, issuedAt, expiresAt, scopes);
|
||||
this.claims = claims;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenClaimsSet;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2TokenContext} implementation that provides access
|
||||
* to the {@link #getClaims() claims} of an OAuth 2.0 Token, allowing the ability to customize.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.3
|
||||
* @see OAuth2TokenContext
|
||||
* @see OAuth2TokenClaimsSet.Builder
|
||||
*/
|
||||
public final class OAuth2TokenClaimsContext implements OAuth2TokenContext {
|
||||
private final Map<Object, Object> context;
|
||||
|
||||
private OAuth2TokenClaimsContext(Map<Object, Object> context) {
|
||||
this.context = Collections.unmodifiableMap(new HashMap<>(context));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
@Override
|
||||
public <V> V get(Object key) {
|
||||
return hasKey(key) ? (V) this.context.get(key) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey(Object key) {
|
||||
Assert.notNull(key, "key cannot be null");
|
||||
return this.context.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2TokenClaimsSet.Builder claims}
|
||||
* allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @return the {@link OAuth2TokenClaimsSet.Builder}
|
||||
*/
|
||||
public OAuth2TokenClaimsSet.Builder getClaims() {
|
||||
return get(OAuth2TokenClaimsSet.Builder.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with the provided claims.
|
||||
*
|
||||
* @param claimsBuilder the claims to initialize the builder
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder with(OAuth2TokenClaimsSet.Builder claimsBuilder) {
|
||||
return new Builder(claimsBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link OAuth2TokenClaimsContext}.
|
||||
*/
|
||||
public static final class Builder extends AbstractBuilder<OAuth2TokenClaimsContext, Builder> {
|
||||
|
||||
private Builder(OAuth2TokenClaimsSet.Builder claimsBuilder) {
|
||||
Assert.notNull(claimsBuilder, "claimsBuilder cannot be null");
|
||||
put(OAuth2TokenClaimsSet.Builder.class, claimsBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the {@link OAuth2TokenClaimsSet.Builder claims}
|
||||
* allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param claimsConsumer a {@code Consumer} of the {@link OAuth2TokenClaimsSet.Builder claims}
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder claims(Consumer<OAuth2TokenClaimsSet.Builder> claimsConsumer) {
|
||||
claimsConsumer.accept(get(OAuth2TokenClaimsSet.Builder.class));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link OAuth2TokenClaimsContext}.
|
||||
*
|
||||
* @return the {@link OAuth2TokenClaimsContext}
|
||||
*/
|
||||
public OAuth2TokenClaimsContext build() {
|
||||
return new OAuth2TokenClaimsContext(getContext());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenClaimsSet;
|
||||
|
||||
/**
|
||||
* Implementations of this interface are responsible for generating an {@link OAuth2Token}
|
||||
@@ -26,6 +28,8 @@ import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
* @since 0.2.3
|
||||
* @see OAuth2Token
|
||||
* @see OAuth2TokenContext
|
||||
* @see OAuth2TokenClaimsSet
|
||||
* @see ClaimAccessor
|
||||
* @param <T> the type of the OAuth 2.0 Token
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@@ -35,6 +39,10 @@ public interface OAuth2TokenGenerator<T extends OAuth2Token> {
|
||||
* Generate an OAuth 2.0 Token using the attributes contained in the {@link OAuth2TokenContext},
|
||||
* or return {@code null} if the {@link OAuth2TokenContext#getTokenType()} is not supported.
|
||||
*
|
||||
* <p>
|
||||
* If the returned {@link OAuth2Token} has a set of claims, it should implement {@link ClaimAccessor}
|
||||
* in order for it to be stored with the {@link OAuth2Authorization}.
|
||||
*
|
||||
* @param context the context containing the OAuth 2.0 Token attributes
|
||||
* @return an {@link OAuth2Token} or {@code null} if the {@link OAuth2TokenContext#getTokenType()} is not supported
|
||||
*/
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
@@ -47,6 +48,7 @@ import org.springframework.security.oauth2.server.authorization.DefaultOAuth2Tok
|
||||
import org.springframework.security.oauth2.server.authorization.DelegatingOAuth2TokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AccessTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2RefreshTokenGenerator;
|
||||
@@ -103,8 +105,8 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
|
||||
Assert.notNull(jwtEncoder, "jwtEncoder cannot be null");
|
||||
this.authorizationService = authorizationService;
|
||||
this.jwtGenerator = new JwtGenerator(jwtEncoder);
|
||||
this.tokenGenerator = new DelegatingOAuth2TokenGenerator(
|
||||
this.jwtGenerator, new OAuth2RefreshTokenGenerator());
|
||||
this.tokenGenerator = new DelegatingOAuth2TokenGenerator(this.jwtGenerator,
|
||||
new OAuth2AccessTokenGenerator(), new OAuth2RefreshTokenGenerator());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,9 +218,9 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
||||
if (generatedAccessToken instanceof Jwt) {
|
||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||
authorizationBuilder.token(accessToken, (metadata) ->
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((Jwt) generatedAccessToken).getClaims()));
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
|
||||
} else {
|
||||
authorizationBuilder.accessToken(accessToken);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
@@ -32,8 +33,10 @@ import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.DefaultOAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.DelegatingOAuth2TokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AccessTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenContext;
|
||||
@@ -65,6 +68,9 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||
|
||||
// TODO Remove after removing @Deprecated OAuth2ClientCredentialsAuthenticationProvider(OAuth2AuthorizationService, JwtEncoder)
|
||||
private JwtGenerator jwtGenerator;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2ClientCredentialsAuthenticationProvider} using the provided parameters.
|
||||
*
|
||||
@@ -78,7 +84,9 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
|
||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||
Assert.notNull(jwtEncoder, "jwtEncoder cannot be null");
|
||||
this.authorizationService = authorizationService;
|
||||
this.tokenGenerator = new JwtGenerator(jwtEncoder);
|
||||
this.jwtGenerator = new JwtGenerator(jwtEncoder);
|
||||
this.tokenGenerator = new DelegatingOAuth2TokenGenerator(
|
||||
this.jwtGenerator, new OAuth2AccessTokenGenerator());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,8 +115,8 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
|
||||
@Deprecated
|
||||
public void setJwtCustomizer(OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer) {
|
||||
Assert.notNull(jwtCustomizer, "jwtCustomizer cannot be null");
|
||||
if (this.tokenGenerator instanceof JwtGenerator) {
|
||||
((JwtGenerator) this.tokenGenerator).setJwtCustomizer(jwtCustomizer);
|
||||
if (this.jwtGenerator != null) {
|
||||
this.jwtGenerator.setJwtCustomizer(jwtCustomizer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,9 +175,9 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, authorizedScopes);
|
||||
// @formatter:on
|
||||
if (generatedAccessToken instanceof Jwt) {
|
||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||
authorizationBuilder.token(accessToken, (metadata) ->
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((Jwt) generatedAccessToken).getClaims()));
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
|
||||
} else {
|
||||
authorizationBuilder.accessToken(accessToken);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
@@ -44,6 +45,7 @@ import org.springframework.security.oauth2.server.authorization.DefaultOAuth2Tok
|
||||
import org.springframework.security.oauth2.server.authorization.DelegatingOAuth2TokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AccessTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2RefreshTokenGenerator;
|
||||
@@ -97,8 +99,8 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
|
||||
Assert.notNull(jwtEncoder, "jwtEncoder cannot be null");
|
||||
this.authorizationService = authorizationService;
|
||||
this.jwtGenerator = new JwtGenerator(jwtEncoder);
|
||||
this.tokenGenerator = new DelegatingOAuth2TokenGenerator(
|
||||
this.jwtGenerator, new OAuth2RefreshTokenGenerator());
|
||||
this.tokenGenerator = new DelegatingOAuth2TokenGenerator(this.jwtGenerator,
|
||||
new OAuth2AccessTokenGenerator(), new OAuth2RefreshTokenGenerator());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,9 +217,9 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
||||
if (generatedAccessToken instanceof Jwt) {
|
||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||
authorizationBuilder.token(accessToken, (metadata) -> {
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((Jwt) generatedAccessToken).getClaims());
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
||||
metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -18,15 +18,14 @@ package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimAccessor;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
@@ -121,25 +120,23 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut
|
||||
tokenClaims.scopes(scopes -> scopes.addAll(accessToken.getScopes()));
|
||||
tokenClaims.tokenType(accessToken.getTokenType().getValue());
|
||||
|
||||
Map<String, Object> claims = authorizedToken.getClaims();
|
||||
if (!CollectionUtils.isEmpty(claims)) {
|
||||
// Assuming JWT as it's the only (currently) supported access token format
|
||||
JwtClaimAccessor jwtClaims = () -> claims;
|
||||
if (!CollectionUtils.isEmpty(authorizedToken.getClaims())) {
|
||||
OAuth2TokenClaimAccessor accessTokenClaims = authorizedToken::getClaims;
|
||||
|
||||
Instant notBefore = jwtClaims.getNotBefore();
|
||||
Instant notBefore = accessTokenClaims.getNotBefore();
|
||||
if (notBefore != null) {
|
||||
tokenClaims.notBefore(notBefore);
|
||||
}
|
||||
tokenClaims.subject(jwtClaims.getSubject());
|
||||
List<String> audience = jwtClaims.getAudience();
|
||||
tokenClaims.subject(accessTokenClaims.getSubject());
|
||||
List<String> audience = accessTokenClaims.getAudience();
|
||||
if (!CollectionUtils.isEmpty(audience)) {
|
||||
tokenClaims.audiences(audiences -> audiences.addAll(audience));
|
||||
}
|
||||
URL issuer = jwtClaims.getIssuer();
|
||||
URL issuer = accessTokenClaims.getIssuer();
|
||||
if (issuer != null) {
|
||||
tokenClaims.issuer(issuer.toExternalForm());
|
||||
}
|
||||
String jti = jwtClaims.getId();
|
||||
String jti = accessTokenClaims.getId();
|
||||
if (StringUtils.hasText(jti)) {
|
||||
tokenClaims.id(jti);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -40,7 +40,9 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ConfigurationSettingNames;
|
||||
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -241,7 +243,11 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
|
||||
builder.clientSettings(ClientSettings.withSettings(clientSettingsMap).build());
|
||||
|
||||
Map<String, Object> tokenSettingsMap = parseMap(rs.getString("token_settings"));
|
||||
builder.tokenSettings(TokenSettings.withSettings(tokenSettingsMap).build());
|
||||
TokenSettings.Builder tokenSettingsBuilder = TokenSettings.withSettings(tokenSettingsMap);
|
||||
if (!tokenSettingsMap.containsKey(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT)) {
|
||||
tokenSettingsBuilder.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED);
|
||||
}
|
||||
builder.tokenSettings(tokenSettingsBuilder.build());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.security.oauth2.server.authorization.config;
|
||||
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
@@ -132,6 +133,12 @@ public final class ConfigurationSettingNames {
|
||||
*/
|
||||
public static final String ACCESS_TOKEN_TIME_TO_LIVE = TOKEN_SETTINGS_NAMESPACE.concat("access-token-time-to-live");
|
||||
|
||||
/**
|
||||
* Set the {@link OAuth2TokenFormat token format} for an access token.
|
||||
* @since 0.2.3
|
||||
*/
|
||||
public static final String ACCESS_TOKEN_FORMAT = TOKEN_SETTINGS_NAMESPACE.concat("access-token-format");
|
||||
|
||||
/**
|
||||
* Set to {@code true} if refresh tokens are reused when returning the access token response,
|
||||
* or {@code false} if a new refresh token is issued.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.config;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -45,6 +46,17 @@ public final class TokenSettings extends AbstractSettings {
|
||||
return getSetting(ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the token format for an access token.
|
||||
* The default is {@link OAuth2TokenFormat#SELF_CONTAINED}.
|
||||
*
|
||||
* @return the token format for an access token
|
||||
* @since 0.2.3
|
||||
*/
|
||||
public OAuth2TokenFormat getAccessTokenFormat() {
|
||||
return getSetting(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if refresh tokens are reused when returning the access token response,
|
||||
* or {@code false} if a new refresh token is issued. The default is {@code true}.
|
||||
@@ -80,6 +92,7 @@ public final class TokenSettings extends AbstractSettings {
|
||||
public static Builder builder() {
|
||||
return new Builder()
|
||||
.accessTokenTimeToLive(Duration.ofMinutes(5))
|
||||
.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
|
||||
.reuseRefreshTokens(true)
|
||||
.refreshTokenTimeToLive(Duration.ofMinutes(60))
|
||||
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256);
|
||||
@@ -117,6 +130,18 @@ public final class TokenSettings extends AbstractSettings {
|
||||
return setting(ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE, accessTokenTimeToLive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the token format for an access token.
|
||||
*
|
||||
* @param accessTokenFormat the token format for an access token
|
||||
* @return the {@link Builder} for further configuration
|
||||
* @since 0.2.3
|
||||
*/
|
||||
public Builder accessTokenFormat(OAuth2TokenFormat accessTokenFormat) {
|
||||
Assert.notNull(accessTokenFormat, "accessTokenFormat cannot be null");
|
||||
return setting(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT, accessTokenFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to {@code true} if refresh tokens are reused when returning the access token response,
|
||||
* or {@code false} if a new refresh token is issued.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
@@ -38,6 +39,7 @@ import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
* <li>{@link OAuth2AuthorizationRequestMixin}</li>
|
||||
* <li>{@link DurationMixin}</li>
|
||||
* <li>{@link JwsAlgorithmMixin}</li>
|
||||
* <li>{@link OAuth2TokenFormatMixin}</li>
|
||||
* </ul>
|
||||
*
|
||||
* If not already enabled, default typing will be automatically enabled as type info is
|
||||
@@ -60,6 +62,7 @@ import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
* @see OAuth2AuthorizationRequestMixin
|
||||
* @see DurationMixin
|
||||
* @see JwsAlgorithmMixin
|
||||
* @see OAuth2TokenFormatMixin
|
||||
*/
|
||||
public class OAuth2AuthorizationServerJackson2Module extends SimpleModule {
|
||||
|
||||
@@ -78,6 +81,7 @@ public class OAuth2AuthorizationServerJackson2Module extends SimpleModule {
|
||||
context.setMixInAnnotations(Duration.class, DurationMixin.class);
|
||||
context.setMixInAnnotations(SignatureAlgorithm.class, JwsAlgorithmMixin.class);
|
||||
context.setMixInAnnotations(MacAlgorithm.class, JwsAlgorithmMixin.class);
|
||||
context.setMixInAnnotations(OAuth2TokenFormat.class, OAuth2TokenFormatMixin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link OAuth2TokenFormat}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.2.3
|
||||
* @see OAuth2TokenFormat
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
abstract class OAuth2TokenFormatMixin {
|
||||
|
||||
@JsonCreator
|
||||
OAuth2TokenFormatMixin(@JsonProperty("value") String value) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
@@ -46,7 +47,6 @@ import org.springframework.security.oauth2.core.oidc.OidcClientMetadataClaimName
|
||||
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
|
||||
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.DefaultOAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtGenerator;
|
||||
@@ -286,9 +286,9 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, authorizedScopes);
|
||||
// @formatter:on
|
||||
if (registrationAccessToken instanceof Jwt) {
|
||||
if (registrationAccessToken instanceof ClaimAccessor) {
|
||||
authorizationBuilder.token(accessToken, (metadata) ->
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((Jwt) registrationAccessToken).getClaims()));
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) registrationAccessToken).getClaims()));
|
||||
} else {
|
||||
authorizationBuilder.accessToken(accessToken);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -15,10 +15,16 @@
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
@@ -28,10 +34,12 @@ import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
@@ -48,18 +56,26 @@ import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenClaimsSet;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2TokenIntrospectionHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.jose.TestJwks;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
|
||||
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenClaimsContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
|
||||
@@ -67,6 +83,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
@@ -74,7 +91,8 @@ import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -88,8 +106,11 @@ public class OAuth2TokenIntrospectionTests {
|
||||
private static EmbeddedDatabase db;
|
||||
private static JWKSource<SecurityContext> jwkSource;
|
||||
private static ProviderSettings providerSettings;
|
||||
private final HttpMessageConverter<OAuth2TokenIntrospection> tokenIntrospectionHttpResponseConverter =
|
||||
private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
|
||||
private static final HttpMessageConverter<OAuth2TokenIntrospection> tokenIntrospectionHttpResponseConverter =
|
||||
new OAuth2TokenIntrospectionHttpMessageConverter();
|
||||
private static final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
|
||||
new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
@@ -111,6 +132,7 @@ public class OAuth2TokenIntrospectionTests {
|
||||
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
|
||||
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
|
||||
providerSettings = ProviderSettings.builder().tokenIntrospectionEndpoint("/test/introspect").build();
|
||||
accessTokenCustomizer = mock(OAuth2TokenCustomizer.class);
|
||||
db = new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(EmbeddedDatabaseType.HSQL)
|
||||
@@ -145,9 +167,19 @@ public class OAuth2TokenIntrospectionTests {
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt,
|
||||
new HashSet<>(Arrays.asList("scope1", "scope2")));
|
||||
JwtClaimsSet tokenClaims = TestJwtClaimsSets.jwtClaimsSet().build();
|
||||
// @formatter:off
|
||||
OAuth2TokenClaimsSet accessTokenClaims = OAuth2TokenClaimsSet.builder()
|
||||
.issuer("https://provider.com")
|
||||
.subject("subject")
|
||||
.audience(Collections.singletonList(authorizedRegisteredClient.getClientId()))
|
||||
.issuedAt(issuedAt)
|
||||
.notBefore(issuedAt)
|
||||
.expiresAt(expiresAt)
|
||||
.id("id")
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||
.authorization(authorizedRegisteredClient, accessToken, tokenClaims.getClaims())
|
||||
.authorization(authorizedRegisteredClient, accessToken, accessTokenClaims.getClaims())
|
||||
.build();
|
||||
this.registeredClientRepository.save(authorizedRegisteredClient);
|
||||
this.authorizationService.save(authorization);
|
||||
@@ -155,7 +187,7 @@ public class OAuth2TokenIntrospectionTests {
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mvc.perform(post(providerSettings.getTokenIntrospectionEndpoint())
|
||||
.params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN))
|
||||
.with(httpBasic(introspectRegisteredClient.getClientId(), introspectRegisteredClient.getClientSecret())))
|
||||
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient)))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
@@ -165,17 +197,17 @@ public class OAuth2TokenIntrospectionTests {
|
||||
assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId());
|
||||
assertThat(tokenIntrospectionResponse.getUsername()).isNull();
|
||||
assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(
|
||||
accessToken.getIssuedAt().minusSeconds(1), accessToken.getIssuedAt().plusSeconds(1));
|
||||
accessTokenClaims.getIssuedAt().minusSeconds(1), accessTokenClaims.getIssuedAt().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween(
|
||||
accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1));
|
||||
accessTokenClaims.getExpiresAt().minusSeconds(1), accessTokenClaims.getExpiresAt().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getScopes()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes());
|
||||
assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo(accessToken.getTokenType().getValue());
|
||||
assertThat(tokenIntrospectionResponse.getNotBefore()).isBetween(
|
||||
tokenClaims.getNotBefore().minusSeconds(1), tokenClaims.getNotBefore().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(tokenClaims.getSubject());
|
||||
assertThat(tokenIntrospectionResponse.getAudience()).containsExactlyInAnyOrderElementsOf(tokenClaims.getAudience());
|
||||
assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(tokenClaims.getIssuer());
|
||||
assertThat(tokenIntrospectionResponse.getId()).isEqualTo(tokenClaims.getId());
|
||||
accessTokenClaims.getNotBefore().minusSeconds(1), accessTokenClaims.getNotBefore().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(accessTokenClaims.getSubject());
|
||||
assertThat(tokenIntrospectionResponse.getAudience()).containsExactlyInAnyOrderElementsOf(accessTokenClaims.getAudience());
|
||||
assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(accessTokenClaims.getIssuer());
|
||||
assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -195,7 +227,7 @@ public class OAuth2TokenIntrospectionTests {
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mvc.perform(post(providerSettings.getTokenIntrospectionEndpoint())
|
||||
.params(getTokenIntrospectionRequestParameters(refreshToken, OAuth2TokenType.REFRESH_TOKEN))
|
||||
.with(httpBasic(introspectRegisteredClient.getClientId(), introspectRegisteredClient.getClientSecret())))
|
||||
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient)))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
@@ -217,6 +249,71 @@ public class OAuth2TokenIntrospectionTests {
|
||||
assertThat(tokenIntrospectionResponse.getId()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenObtainReferenceAccessTokenAndIntrospectThenActive() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
// @formatter:off
|
||||
TokenSettings tokenSettings = TokenSettings.builder()
|
||||
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
||||
.build();
|
||||
RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient()
|
||||
.tokenSettings(tokenSettings)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.registeredClientRepository.save(authorizedRegisteredClient);
|
||||
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build();
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mvc.perform(post(providerSettings.getTokenEndpoint())
|
||||
.params(getAuthorizationCodeTokenRequestParameters(authorizedRegisteredClient, authorization))
|
||||
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(authorizedRegisteredClient)))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
|
||||
OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(mvcResult);
|
||||
OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
|
||||
|
||||
RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build();
|
||||
this.registeredClientRepository.save(introspectRegisteredClient);
|
||||
|
||||
// @formatter:off
|
||||
mvcResult = this.mvc.perform(post(providerSettings.getTokenIntrospectionEndpoint())
|
||||
.params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN))
|
||||
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient)))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
|
||||
OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult);
|
||||
|
||||
ArgumentCaptor<OAuth2TokenClaimsContext> accessTokenClaimsContextCaptor = ArgumentCaptor.forClass(OAuth2TokenClaimsContext.class);
|
||||
verify(accessTokenCustomizer).customize(accessTokenClaimsContextCaptor.capture());
|
||||
|
||||
OAuth2TokenClaimsContext accessTokenClaimsContext = accessTokenClaimsContextCaptor.getValue();
|
||||
OAuth2TokenClaimsSet accessTokenClaims = accessTokenClaimsContext.getClaims().build();
|
||||
|
||||
assertThat(tokenIntrospectionResponse.isActive()).isTrue();
|
||||
assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId());
|
||||
assertThat(tokenIntrospectionResponse.getUsername()).isNull();
|
||||
assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(
|
||||
accessTokenClaims.getIssuedAt().minusSeconds(1), accessTokenClaims.getIssuedAt().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween(
|
||||
accessTokenClaims.getExpiresAt().minusSeconds(1), accessTokenClaims.getExpiresAt().plusSeconds(1));
|
||||
List<String> scopes = new ArrayList<>(accessTokenClaims.getClaim(OAuth2ParameterNames.SCOPE));
|
||||
assertThat(tokenIntrospectionResponse.getScopes()).containsExactlyInAnyOrderElementsOf(scopes);
|
||||
assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo(accessToken.getTokenType().getValue());
|
||||
assertThat(tokenIntrospectionResponse.getNotBefore()).isBetween(
|
||||
accessTokenClaims.getNotBefore().minusSeconds(1), accessTokenClaims.getNotBefore().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(accessTokenClaims.getSubject());
|
||||
assertThat(tokenIntrospectionResponse.getAudience()).containsExactlyInAnyOrderElementsOf(accessTokenClaims.getAudience());
|
||||
assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(accessTokenClaims.getIssuer());
|
||||
assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId());
|
||||
}
|
||||
|
||||
private static MultiValueMap<String, String> getTokenIntrospectionRequestParameters(AbstractOAuth2Token token,
|
||||
OAuth2TokenType tokenType) {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
@@ -225,11 +322,37 @@ public class OAuth2TokenIntrospectionTests {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private OAuth2TokenIntrospection readTokenIntrospectionResponse(MvcResult mvcResult) throws Exception {
|
||||
private static OAuth2TokenIntrospection readTokenIntrospectionResponse(MvcResult mvcResult) throws Exception {
|
||||
MockHttpServletResponse servletResponse = mvcResult.getResponse();
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
|
||||
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
|
||||
return this.tokenIntrospectionHttpResponseConverter.read(OAuth2TokenIntrospection.class, httpResponse);
|
||||
return tokenIntrospectionHttpResponseConverter.read(OAuth2TokenIntrospection.class, httpResponse);
|
||||
}
|
||||
|
||||
private static MultiValueMap<String, String> getAuthorizationCodeTokenRequestParameters(RegisteredClient registeredClient,
|
||||
OAuth2Authorization authorization) {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
|
||||
parameters.set(OAuth2ParameterNames.CODE, authorization.getToken(OAuth2AuthorizationCode.class).getToken().getTokenValue());
|
||||
parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next());
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static OAuth2AccessTokenResponse readAccessTokenResponse(MvcResult mvcResult) throws Exception {
|
||||
MockHttpServletResponse servletResponse = mvcResult.getResponse();
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
|
||||
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
|
||||
return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
|
||||
}
|
||||
|
||||
private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception {
|
||||
String clientId = registeredClient.getClientId();
|
||||
String clientSecret = registeredClient.getClientSecret();
|
||||
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
|
||||
clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8.name());
|
||||
String credentialsString = clientId + ":" + clientSecret;
|
||||
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
|
||||
return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@@ -244,6 +367,11 @@ public class OAuth2TokenIntrospectionTests {
|
||||
return authorizationService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) {
|
||||
return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations, PasswordEncoder passwordEncoder) {
|
||||
JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository(jdbcOperations);
|
||||
@@ -259,13 +387,13 @@ public class OAuth2TokenIntrospectionTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
JWKSource<SecurityContext> jwkSource() {
|
||||
return jwkSource;
|
||||
ProviderSettings providerSettings() {
|
||||
return providerSettings;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ProviderSettings providerSettings() {
|
||||
return providerSettings;
|
||||
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer() {
|
||||
return accessTokenCustomizer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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.
|
||||
*/
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2TokenClaimsSet}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2TokenClaimsSetTests {
|
||||
|
||||
@Test
|
||||
public void buildWhenClaimsEmptyThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2TokenClaimsSet.builder().build())
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("claims cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAllClaimsProvidedThenAllClaimsAreSet() {
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
|
||||
String customClaimName = "custom-claim-name";
|
||||
String customClaimValue = "custom-claim-value";
|
||||
|
||||
// @formatter:off
|
||||
OAuth2TokenClaimsSet expectedClaimsSet = OAuth2TokenClaimsSet.builder()
|
||||
.issuer("https://provider.com")
|
||||
.subject("subject")
|
||||
.audience(Collections.singletonList("client-1"))
|
||||
.issuedAt(issuedAt)
|
||||
.notBefore(issuedAt)
|
||||
.expiresAt(expiresAt)
|
||||
.id("id")
|
||||
.claims(claims -> claims.put(customClaimName, customClaimValue))
|
||||
.build();
|
||||
|
||||
OAuth2TokenClaimsSet claimsSet = OAuth2TokenClaimsSet.builder()
|
||||
.issuer(expectedClaimsSet.getIssuer().toExternalForm())
|
||||
.subject(expectedClaimsSet.getSubject())
|
||||
.audience(expectedClaimsSet.getAudience())
|
||||
.issuedAt(expectedClaimsSet.getIssuedAt())
|
||||
.notBefore(expectedClaimsSet.getNotBefore())
|
||||
.expiresAt(expectedClaimsSet.getExpiresAt())
|
||||
.id(expectedClaimsSet.getId())
|
||||
.claims(claims -> claims.put(customClaimName, expectedClaimsSet.getClaim(customClaimName)))
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
assertThat(claimsSet.getIssuer()).isEqualTo(expectedClaimsSet.getIssuer());
|
||||
assertThat(claimsSet.getSubject()).isEqualTo(expectedClaimsSet.getSubject());
|
||||
assertThat(claimsSet.getAudience()).isEqualTo(expectedClaimsSet.getAudience());
|
||||
assertThat(claimsSet.getIssuedAt()).isEqualTo(expectedClaimsSet.getIssuedAt());
|
||||
assertThat(claimsSet.getNotBefore()).isEqualTo(expectedClaimsSet.getNotBefore());
|
||||
assertThat(claimsSet.getExpiresAt()).isEqualTo(expectedClaimsSet.getExpiresAt());
|
||||
assertThat(claimsSet.getId()).isEqualTo(expectedClaimsSet.getId());
|
||||
assertThat(claimsSet.<String>getClaim(customClaimName)).isEqualTo(expectedClaimsSet.getClaim(customClaimName));
|
||||
assertThat(claimsSet.getClaims()).isEqualTo(expectedClaimsSet.getClaims());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void claimWhenNameNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2TokenClaimsSet.builder().claim(null, "value"))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("name cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void claimWhenValueNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2TokenClaimsSet.builder().claim("name", null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("value cannot be null");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
@@ -45,6 +46,7 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -99,6 +101,24 @@ public class JwtGeneratorTests {
|
||||
assertThat(this.jwtGenerator.generate(tokenContext)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateWhenUnsupportedTokenFormatThenReturnNull() {
|
||||
// @formatter:off
|
||||
TokenSettings tokenSettings = TokenSettings.builder()
|
||||
.accessTokenFormat(new OAuth2TokenFormat("unsupported_token_format"))
|
||||
.build();
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.tokenSettings(tokenSettings)
|
||||
.build();
|
||||
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
assertThat(this.jwtGenerator.generate(tokenContext)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateWhenAccessTokenTypeThenReturnJwt() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2AccessTokenGenerator}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2AccessTokenGeneratorTests {
|
||||
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
|
||||
private OAuth2AccessTokenGenerator accessTokenGenerator;
|
||||
private ProviderContext providerContext;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.accessTokenCustomizer = mock(OAuth2TokenCustomizer.class);
|
||||
this.accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
this.accessTokenGenerator.setAccessTokenCustomizer(this.accessTokenCustomizer);
|
||||
ProviderSettings providerSettings = ProviderSettings.builder().issuer("https://provider.com").build();
|
||||
this.providerContext = new ProviderContext(providerSettings, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAccessTokenCustomizerWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.accessTokenGenerator.setAccessTokenCustomizer(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("accessTokenCustomizer cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateWhenUnsupportedTokenTypeThenReturnNull() {
|
||||
// @formatter:off
|
||||
TokenSettings tokenSettings = TokenSettings.builder()
|
||||
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
||||
.build();
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.tokenSettings(tokenSettings)
|
||||
.build();
|
||||
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.tokenType(new OAuth2TokenType("unsupported_token_type"))
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
assertThat(this.accessTokenGenerator.generate(tokenContext)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateWhenUnsupportedTokenFormatThenReturnNull() {
|
||||
// @formatter:off
|
||||
TokenSettings tokenSettings = TokenSettings.builder()
|
||||
.accessTokenFormat(new OAuth2TokenFormat("unsupported_token_format"))
|
||||
.build();
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.tokenSettings(tokenSettings)
|
||||
.build();
|
||||
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
assertThat(this.accessTokenGenerator.generate(tokenContext)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateWhenReferenceAccessTokenTypeThenReturnAccessToken() {
|
||||
// @formatter:off
|
||||
TokenSettings tokenSettings = TokenSettings.builder()
|
||||
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
||||
.build();
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.tokenSettings(tokenSettings)
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
Authentication principal = authorization.getAttribute(Principal.class.getName());
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||
OAuth2AuthorizationRequest.class.getName());
|
||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationCodeAuthenticationToken("code", clientPrincipal, authorizationRequest.getRedirectUri(), null);
|
||||
|
||||
// @formatter:off
|
||||
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.principal(principal)
|
||||
.providerContext(this.providerContext)
|
||||
.authorization(authorization)
|
||||
.authorizedScopes(authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME))
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrant(authentication)
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
OAuth2AccessToken accessToken = this.accessTokenGenerator.generate(tokenContext);
|
||||
assertThat(accessToken).isNotNull();
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(tokenContext.getRegisteredClient().getTokenSettings().getAccessTokenTimeToLive());
|
||||
assertThat(accessToken.getIssuedAt()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1));
|
||||
assertThat(accessToken.getExpiresAt()).isBetween(expiresAt.minusSeconds(1), expiresAt.plusSeconds(1));
|
||||
assertThat(accessToken.getScopes()).isEqualTo(tokenContext.getAuthorizedScopes());
|
||||
|
||||
assertThat(accessToken).isInstanceOf(ClaimAccessor.class);
|
||||
OAuth2TokenClaimAccessor accessTokenClaims = ((ClaimAccessor) accessToken)::getClaims;
|
||||
assertThat(accessTokenClaims.getClaims()).isNotEmpty();
|
||||
|
||||
assertThat(accessTokenClaims.getIssuer().toExternalForm()).isEqualTo(tokenContext.getProviderContext().getIssuer());
|
||||
assertThat(accessTokenClaims.getSubject()).isEqualTo(tokenContext.getPrincipal().getName());
|
||||
assertThat(accessTokenClaims.getAudience()).isEqualTo(
|
||||
Collections.singletonList(tokenContext.getRegisteredClient().getClientId()));
|
||||
assertThat(accessTokenClaims.getIssuedAt()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1));
|
||||
assertThat(accessTokenClaims.getExpiresAt()).isBetween(expiresAt.minusSeconds(1), expiresAt.plusSeconds(1));
|
||||
assertThat(accessTokenClaims.getNotBefore()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1));
|
||||
assertThat(accessTokenClaims.getId()).isNotNull();
|
||||
|
||||
Set<String> scopes = accessTokenClaims.getClaim(OAuth2ParameterNames.SCOPE);
|
||||
assertThat(scopes).isEqualTo(tokenContext.getAuthorizedScopes());
|
||||
|
||||
ArgumentCaptor<OAuth2TokenClaimsContext> tokenClaimsContextCaptor = ArgumentCaptor.forClass(OAuth2TokenClaimsContext.class);
|
||||
verify(this.accessTokenCustomizer).customize(tokenClaimsContextCaptor.capture());
|
||||
|
||||
OAuth2TokenClaimsContext tokenClaimsContext = tokenClaimsContextCaptor.getValue();
|
||||
assertThat(tokenClaimsContext.getClaims()).isNotNull();
|
||||
assertThat(tokenClaimsContext.getRegisteredClient()).isEqualTo(tokenContext.getRegisteredClient());
|
||||
assertThat(tokenClaimsContext.<Authentication>getPrincipal()).isEqualTo(tokenContext.getPrincipal());
|
||||
assertThat(tokenClaimsContext.getProviderContext()).isEqualTo(tokenContext.getProviderContext());
|
||||
assertThat(tokenClaimsContext.getAuthorization()).isEqualTo(tokenContext.getAuthorization());
|
||||
assertThat(tokenClaimsContext.getAuthorizedScopes()).isEqualTo(tokenContext.getAuthorizedScopes());
|
||||
assertThat(tokenClaimsContext.getTokenType()).isEqualTo(tokenContext.getTokenType());
|
||||
assertThat(tokenClaimsContext.getAuthorizationGrantType()).isEqualTo(tokenContext.getAuthorizationGrantType());
|
||||
assertThat(tokenClaimsContext.<Authentication>getAuthorizationGrant()).isEqualTo(tokenContext.getAuthorizationGrant());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenClaimsSet;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2TokenClaimsContext}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2TokenClaimsContextTests {
|
||||
|
||||
@Test
|
||||
public void withWhenClaimsNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2TokenClaimsContext.with(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("claimsBuilder cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAllValuesProvidedThenAllValuesAreSet() {
|
||||
String issuer = "https://provider.com";
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
|
||||
|
||||
// @formatter:off
|
||||
OAuth2TokenClaimsSet.Builder claims = OAuth2TokenClaimsSet.builder()
|
||||
.issuer(issuer)
|
||||
.subject("subject")
|
||||
.audience(Collections.singletonList("client-1"))
|
||||
.issuedAt(issuedAt)
|
||||
.notBefore(issuedAt)
|
||||
.expiresAt(expiresAt)
|
||||
.id("id");
|
||||
// @formatter:on
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
Authentication principal = authorization.getAttribute(Principal.class.getName());
|
||||
ProviderSettings providerSettings = ProviderSettings.builder().issuer(issuer).build();
|
||||
ProviderContext providerContext = new ProviderContext(providerSettings, null);
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||
OAuth2AuthorizationRequest.class.getName());
|
||||
OAuth2AuthorizationCodeAuthenticationToken authorizationGrant =
|
||||
new OAuth2AuthorizationCodeAuthenticationToken(
|
||||
"code", clientPrincipal, authorizationRequest.getRedirectUri(), null);
|
||||
|
||||
// @formatter:off
|
||||
OAuth2TokenClaimsContext context = OAuth2TokenClaimsContext.with(claims)
|
||||
.registeredClient(registeredClient)
|
||||
.principal(principal)
|
||||
.providerContext(providerContext)
|
||||
.authorization(authorization)
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrant(authorizationGrant)
|
||||
.put("custom-key-1", "custom-value-1")
|
||||
.context(ctx -> ctx.put("custom-key-2", "custom-value-2"))
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
assertThat(context.getClaims()).isEqualTo(claims);
|
||||
assertThat(context.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(context.<Authentication>getPrincipal()).isEqualTo(principal);
|
||||
assertThat(context.getProviderContext()).isEqualTo(providerContext);
|
||||
assertThat(context.getAuthorization()).isEqualTo(authorization);
|
||||
assertThat(context.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN);
|
||||
assertThat(context.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
assertThat(context.<OAuth2AuthorizationGrantAuthenticationToken>getAuthorizationGrant()).isEqualTo(authorizationGrant);
|
||||
assertThat(context.<String>get("custom-key-1")).isEqualTo("custom-value-1");
|
||||
assertThat(context.<String>get("custom-key-2")).isEqualTo("custom-value-2");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
@@ -52,9 +53,11 @@ import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.DelegatingOAuth2TokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AccessTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2RefreshTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenClaimsContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenGenerator;
|
||||
@@ -90,6 +93,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
private JwtEncoder jwtEncoder;
|
||||
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
|
||||
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
|
||||
private OAuth2TokenGenerator<?> tokenGenerator;
|
||||
private OAuth2AuthorizationCodeAuthenticationProvider authenticationProvider;
|
||||
|
||||
@@ -100,9 +104,12 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
this.jwtCustomizer = mock(OAuth2TokenCustomizer.class);
|
||||
JwtGenerator jwtGenerator = new JwtGenerator(this.jwtEncoder);
|
||||
jwtGenerator.setJwtCustomizer(this.jwtCustomizer);
|
||||
this.accessTokenCustomizer = mock(OAuth2TokenCustomizer.class);
|
||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
accessTokenGenerator.setAccessTokenCustomizer(this.accessTokenCustomizer);
|
||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
||||
OAuth2TokenGenerator<OAuth2Token> delegatingTokenGenerator =
|
||||
new DelegatingOAuth2TokenGenerator(jwtGenerator, refreshTokenGenerator);
|
||||
new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
|
||||
this.tokenGenerator = spy(new OAuth2TokenGenerator<OAuth2Token>() {
|
||||
@Override
|
||||
public OAuth2Token generate(OAuth2TokenContext context) {
|
||||
@@ -312,8 +319,6 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
|
||||
|
||||
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
|
||||
|
||||
doAnswer(answer -> {
|
||||
OAuth2TokenContext context = answer.getArgument(0);
|
||||
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
|
||||
@@ -686,6 +691,31 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
assertThat(accessTokenAuthentication.getRefreshToken().getTokenValue()).isEqualTo(refreshTokenGenerator.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenAccessTokenFormatReferenceThenAccessTokenGeneratorCalled() {
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.tokenSettings(TokenSettings.builder()
|
||||
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
||||
.build())
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||
OAuth2AuthorizationRequest.class.getName());
|
||||
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
|
||||
|
||||
this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
verify(this.accessTokenCustomizer).customize(any());
|
||||
}
|
||||
|
||||
private static Jwt createJwt() {
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
|
||||
|
||||
@@ -31,22 +31,28 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.DelegatingOAuth2TokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AccessTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenClaimsContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
|
||||
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
|
||||
|
||||
@@ -69,6 +75,7 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
private JwtEncoder jwtEncoder;
|
||||
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
|
||||
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
|
||||
private OAuth2TokenGenerator<?> tokenGenerator;
|
||||
private OAuth2ClientCredentialsAuthenticationProvider authenticationProvider;
|
||||
|
||||
@@ -79,10 +86,15 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
||||
this.jwtCustomizer = mock(OAuth2TokenCustomizer.class);
|
||||
JwtGenerator jwtGenerator = new JwtGenerator(this.jwtEncoder);
|
||||
jwtGenerator.setJwtCustomizer(this.jwtCustomizer);
|
||||
this.tokenGenerator = spy(new OAuth2TokenGenerator<Jwt>() {
|
||||
this.accessTokenCustomizer = mock(OAuth2TokenCustomizer.class);
|
||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
accessTokenGenerator.setAccessTokenCustomizer(this.accessTokenCustomizer);
|
||||
OAuth2TokenGenerator<OAuth2Token> delegatingTokenGenerator =
|
||||
new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator);
|
||||
this.tokenGenerator = spy(new OAuth2TokenGenerator<OAuth2Token>() {
|
||||
@Override
|
||||
public Jwt generate(OAuth2TokenContext context) {
|
||||
return jwtGenerator.generate(context);
|
||||
public OAuth2Token generate(OAuth2TokenContext context) {
|
||||
return delegatingTokenGenerator.generate(context);
|
||||
}
|
||||
});
|
||||
this.authenticationProvider = new OAuth2ClientCredentialsAuthenticationProvider(
|
||||
@@ -275,6 +287,25 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
||||
assertThat(accessTokenAuthentication.getAccessToken()).isEqualTo(authorization.getAccessToken().getToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenAccessTokenFormatReferenceThenAccessTokenGeneratorCalled() {
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2()
|
||||
.tokenSettings(TokenSettings.builder()
|
||||
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
||||
.build())
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
|
||||
OAuth2ClientCredentialsAuthenticationToken authentication =
|
||||
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
|
||||
|
||||
this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
verify(this.accessTokenCustomizer).customize(any());
|
||||
}
|
||||
|
||||
private static Jwt createJwt(Set<String> scope) {
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
@@ -50,9 +51,11 @@ import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.DelegatingOAuth2TokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AccessTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2RefreshTokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenClaimsContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenGenerator;
|
||||
@@ -88,6 +91,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
private JwtEncoder jwtEncoder;
|
||||
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
|
||||
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
|
||||
private OAuth2TokenGenerator<?> tokenGenerator;
|
||||
private OAuth2RefreshTokenAuthenticationProvider authenticationProvider;
|
||||
|
||||
@@ -99,9 +103,12 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
|
||||
this.jwtCustomizer = mock(OAuth2TokenCustomizer.class);
|
||||
JwtGenerator jwtGenerator = new JwtGenerator(this.jwtEncoder);
|
||||
jwtGenerator.setJwtCustomizer(this.jwtCustomizer);
|
||||
this.accessTokenCustomizer = mock(OAuth2TokenCustomizer.class);
|
||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
accessTokenGenerator.setAccessTokenCustomizer(this.accessTokenCustomizer);
|
||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
||||
OAuth2TokenGenerator<OAuth2Token> delegatingTokenGenerator =
|
||||
new DelegatingOAuth2TokenGenerator(jwtGenerator, refreshTokenGenerator);
|
||||
new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
|
||||
this.tokenGenerator = spy(new OAuth2TokenGenerator<OAuth2Token>() {
|
||||
@Override
|
||||
public OAuth2Token generate(OAuth2TokenContext context) {
|
||||
@@ -623,6 +630,31 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenAccessTokenFormatReferenceThenAccessTokenGeneratorCalled() {
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.tokenSettings(TokenSettings.builder()
|
||||
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
||||
.build())
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
when(this.authorizationService.findByToken(
|
||||
eq(authorization.getRefreshToken().getToken().getTokenValue()),
|
||||
eq(OAuth2TokenType.REFRESH_TOKEN)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
|
||||
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
|
||||
authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
|
||||
|
||||
this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
verify(this.accessTokenCustomizer).customize(any());
|
||||
}
|
||||
|
||||
private static Jwt createJwt(Set<String> scope) {
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -31,10 +31,9 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenClaimNames;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenClaimsSet;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||
@@ -189,7 +188,7 @@ public class OAuth2TokenIntrospectionAuthenticationProviderTests {
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofHours(1));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt);
|
||||
Map<String, Object> accessTokenClaims = Collections.singletonMap(JwtClaimNames.NBF, notBefore);
|
||||
Map<String, Object> accessTokenClaims = Collections.singletonMap(OAuth2TokenClaimNames.NBF, notBefore);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||
.authorization(registeredClient, accessToken, accessTokenClaims)
|
||||
.build();
|
||||
@@ -217,9 +216,21 @@ public class OAuth2TokenIntrospectionAuthenticationProviderTests {
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt,
|
||||
new HashSet<>(Arrays.asList("scope1", "scope2")));
|
||||
JwtClaimsSet jwtClaims = TestJwtClaimsSets.jwtClaimsSet().build();
|
||||
|
||||
// @formatter:off
|
||||
OAuth2TokenClaimsSet claimsSet = OAuth2TokenClaimsSet.builder()
|
||||
.issuer("https://provider.com")
|
||||
.subject("subject")
|
||||
.audience(Collections.singletonList(authorizedClient.getClientId()))
|
||||
.issuedAt(issuedAt)
|
||||
.notBefore(issuedAt)
|
||||
.expiresAt(expiresAt)
|
||||
.id("id")
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||
.authorization(authorizedClient, accessToken, jwtClaims.getClaims())
|
||||
.authorization(authorizedClient, accessToken, claimsSet.getClaims())
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull()))
|
||||
.thenReturn(authorization);
|
||||
@@ -243,11 +254,11 @@ public class OAuth2TokenIntrospectionAuthenticationProviderTests {
|
||||
assertThat(tokenClaims.getExpiresAt()).isEqualTo(accessToken.getExpiresAt());
|
||||
assertThat(tokenClaims.getScopes()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes());
|
||||
assertThat(tokenClaims.getTokenType()).isEqualTo(accessToken.getTokenType().getValue());
|
||||
assertThat(tokenClaims.getNotBefore()).isEqualTo(jwtClaims.getNotBefore());
|
||||
assertThat(tokenClaims.getSubject()).isEqualTo(jwtClaims.getSubject());
|
||||
assertThat(tokenClaims.getAudience()).containsExactlyInAnyOrderElementsOf(jwtClaims.getAudience());
|
||||
assertThat(tokenClaims.getIssuer()).isEqualTo(jwtClaims.getIssuer());
|
||||
assertThat(tokenClaims.getId()).isEqualTo(jwtClaims.getId());
|
||||
assertThat(tokenClaims.getNotBefore()).isEqualTo(claimsSet.getNotBefore());
|
||||
assertThat(tokenClaims.getSubject()).isEqualTo(claimsSet.getSubject());
|
||||
assertThat(tokenClaims.getAudience()).containsExactlyInAnyOrderElementsOf(claimsSet.getAudience());
|
||||
assertThat(tokenClaims.getIssuer()).isEqualTo(claimsSet.getIssuer());
|
||||
assertThat(tokenClaims.getId()).isEqualTo(claimsSet.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -19,6 +19,7 @@ import java.time.Duration;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -34,8 +35,9 @@ public class TokenSettingsTests {
|
||||
@Test
|
||||
public void buildWhenDefaultThenDefaultsAreSet() {
|
||||
TokenSettings tokenSettings = TokenSettings.builder().build();
|
||||
assertThat(tokenSettings.getSettings()).hasSize(4);
|
||||
assertThat(tokenSettings.getSettings()).hasSize(5);
|
||||
assertThat(tokenSettings.getAccessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(5));
|
||||
assertThat(tokenSettings.getAccessTokenFormat()).isEqualTo(OAuth2TokenFormat.SELF_CONTAINED);
|
||||
assertThat(tokenSettings.isReuseRefreshTokens()).isTrue();
|
||||
assertThat(tokenSettings.getRefreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60));
|
||||
assertThat(tokenSettings.getIdTokenSignatureAlgorithm()).isEqualTo(SignatureAlgorithm.RS256);
|
||||
@@ -68,6 +70,22 @@ public class TokenSettingsTests {
|
||||
.isEqualTo("accessTokenTimeToLive must be greater than Duration.ZERO");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenFormatWhenProvidedThenSet() {
|
||||
TokenSettings tokenSettings = TokenSettings.builder()
|
||||
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
||||
.build();
|
||||
assertThat(tokenSettings.getAccessTokenFormat()).isEqualTo(OAuth2TokenFormat.REFERENCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenFormatWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> TokenSettings.builder().accessTokenFormat(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.extracting(Throwable::getMessage)
|
||||
.isEqualTo("accessTokenFormat cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reuseRefreshTokensWhenFalseThenSet() {
|
||||
TokenSettings tokenSettings = TokenSettings.builder()
|
||||
@@ -118,7 +136,7 @@ public class TokenSettingsTests {
|
||||
.setting("name1", "value1")
|
||||
.settings(settings -> settings.put("name2", "value2"))
|
||||
.build();
|
||||
assertThat(tokenSettings.getSettings()).hasSize(6);
|
||||
assertThat(tokenSettings.getSettings()).hasSize(7);
|
||||
assertThat(tokenSettings.<String>getSetting("name1")).isEqualTo("value1");
|
||||
assertThat(tokenSettings.<String>getSetting("name2")).isEqualTo("value2");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user