Polish gh-1106 Device Authorization Grant

This commit is contained in:
Joe Grandja
2023-03-21 05:21:48 -04:00
parent 47ff4ad06e
commit 1354ca4549
38 changed files with 371 additions and 308 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 the original author or authors.
* Copyright 2020-2023 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.
@@ -38,6 +38,7 @@ import org.springframework.util.Assert;
* @since 0.1.1
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3.2">3.2. Authorization Server Metadata Response</a>
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2. OpenID Provider Configuration Response</a>
* @see <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8628.html#section-4">4. Device Authorization Grant Metadata</a>
*/
public abstract class AbstractOAuth2AuthorizationServerMetadata implements OAuth2AuthorizationServerMetadataClaimAccessor, Serializable {
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
@@ -96,6 +97,17 @@ public abstract class AbstractOAuth2AuthorizationServerMetadata implements OAuth
return claim(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, authorizationEndpoint);
}
/**
* Use this {@code device_authorization_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
*
* @param deviceAuthorizationEndpoint the {@code URL} of the OAuth 2.0 Device Authorization Endpoint
* @return the {@link AbstractBuilder} for further configuration
* @since 1.1
*/
public B deviceAuthorizationEndpoint(String deviceAuthorizationEndpoint) {
return claim(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT, deviceAuthorizationEndpoint);
}
/**
* Use this {@code token_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
*
@@ -346,6 +358,9 @@ public abstract class AbstractOAuth2AuthorizationServerMetadata implements OAuth
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.ISSUER), "issuer must be a valid URL");
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), "authorizationEndpoint cannot be null");
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), "authorizationEndpoint must be a valid URL");
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT) != null) {
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT), "deviceAuthorizationEndpoint must be a valid URL");
}
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint cannot be null");
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint must be a valid URL");
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) {

View File

@@ -155,7 +155,9 @@ public final class InMemoryOAuth2AuthorizationService implements OAuth2Authoriza
matchesAuthorizationCode(authorization, token) ||
matchesAccessToken(authorization, token) ||
matchesIdToken(authorization, token) ||
matchesRefreshToken(authorization, token);
matchesRefreshToken(authorization, token) ||
matchesDeviceCode(authorization, token) ||
matchesUserCode(authorization, token);
} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
return matchesState(authorization, token);
} else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2023 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.
@@ -253,18 +253,6 @@ public class OAuth2Authorization implements Serializable {
*/
public static final String INVALIDATED_METADATA_NAME = TOKEN_METADATA_NAMESPACE.concat("invalidated");
/**
* The name of the metadata that indicates if access has been denied by the resource owner.
* Used with the OAuth 2.0 Device Authorization Grant.
*/
public static final String ACCESS_DENIED_METADATA_NAME = TOKEN_METADATA_NAMESPACE.concat("access_denied");
/**
* The name of the metadata that indicates if access has been denied by the resource owner.
* Used with the OAuth 2.0 Device Authorization Grant.
*/
public static final String ACCESS_GRANTED_METADATA_NAME = TOKEN_METADATA_NAMESPACE.concat("access_granted");
/**
* The name of the metadata used for the claims of the token.
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 the original author or authors.
* Copyright 2020-2023 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.
@@ -30,6 +30,7 @@ import org.springframework.security.oauth2.core.ClaimAccessor;
* @see OAuth2AuthorizationServerMetadataClaimNames
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2. Authorization Server Metadata</a>
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
* @see <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8628.html#section-4">4. Device Authorization Grant Metadata</a>
*/
public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAccessor {
@@ -51,6 +52,16 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT);
}
/**
* Returns the {@code URL} of the OAuth 2.0 Device Authorization Endpoint {@code (device_authorization_endpoint)}.
*
* @return the {@code URL} of the OAuth 2.0 Device Authorization Endpoint
* @since 1.1
*/
default URL getDeviceAuthorizationEndpoint() {
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT);
}
/**
* Returns the {@code URL} of the OAuth 2.0 Token Endpoint {@code (token_endpoint)}.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 the original author or authors.
* Copyright 2020-2023 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.
@@ -23,6 +23,7 @@ package org.springframework.security.oauth2.server.authorization;
* @since 0.1.1
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2. Authorization Server Metadata</a>
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
* @see <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8628.html#section-4">4. Device Authorization Grant Metadata</a>
*/
public class OAuth2AuthorizationServerMetadataClaimNames {
@@ -36,6 +37,12 @@ public class OAuth2AuthorizationServerMetadataClaimNames {
*/
public static final String AUTHORIZATION_ENDPOINT = "authorization_endpoint";
/**
* {@code device_authorization_endpoint} - the {@code URL} of the OAuth 2.0 Device Authorization Endpoint
* @since 1.1
*/
public static final String DEVICE_AUTHORIZATION_ENDPOINT = "device_authorization_endpoint";
/**
* {@code token_endpoint} - the {@code URL} of the OAuth 2.0 Token Endpoint
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2023 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.
@@ -113,10 +113,6 @@ public final class OAuth2AuthorizationConsentAuthenticationContext implements OA
super(authentication);
}
private Builder(OAuth2DeviceAuthorizationConsentAuthenticationToken authentication) {
super(authentication);
}
/**
* Sets the {@link OAuth2AuthorizationConsent.Builder authorization consent builder}.
*

View File

@@ -91,6 +91,12 @@ public final class OAuth2AuthorizationConsentAuthenticationProvider implements A
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication instanceof OAuth2DeviceAuthorizationConsentAuthenticationToken) {
// This is NOT an OAuth 2.0 Authorization Consent for the Authorization Code Grant,
// return null and let OAuth2DeviceAuthorizationConsentAuthenticationProvider handle it instead
return null;
}
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication =
(OAuth2AuthorizationConsentAuthenticationToken) authentication;

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.security.Principal;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -24,6 +23,7 @@ import java.util.function.Consumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -33,7 +33,6 @@ import org.springframework.security.oauth2.core.OAuth2DeviceCode;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2UserCode;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
@@ -45,8 +44,8 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
import org.springframework.util.Assert;
/**
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Consent
* used in the Device Authorization Grant.
* An {@link AuthenticationProvider} implementation for the Device Authorization Consent
* used in the OAuth 2.0 Device Authorization Grant.
*
* @author Steve Riesenberg
* @since 1.1
@@ -61,7 +60,7 @@ import org.springframework.util.Assert;
*/
public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implements AuthenticationProvider {
private static final String DEFAULT_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
private final Log logger = LogFactory.getLog(getClass());
@@ -104,7 +103,11 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem
this.logger.trace("Retrieved authorization with device authorization consent state");
}
// The authorization must be associated to the current principal
Authentication principal = (Authentication) deviceAuthorizationConsentAuthentication.getPrincipal();
if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
}
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
deviceAuthorizationConsentAuthentication.getClientId());
@@ -116,12 +119,8 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem
this.logger.trace("Retrieved registered client");
}
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
OAuth2AuthorizationRequest.class.getName());
Set<String> requestedScopes = authorizationRequest.getScopes();
Set<String> authorizedScopes = deviceAuthorizationConsentAuthentication.getScopes() != null ?
new HashSet<>(deviceAuthorizationConsentAuthentication.getScopes()) :
new HashSet<>();
Set<String> requestedScopes = authorization.getAttribute(OAuth2ParameterNames.SCOPE);
Set<String> authorizedScopes = new HashSet<>(deviceAuthorizationConsentAuthentication.getScopes());
if (!requestedScopes.containsAll(authorizedScopes)) {
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE);
}
@@ -162,7 +161,6 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem
.authorizationConsent(authorizationConsentBuilder)
.registeredClient(registeredClient)
.authorization(authorization)
.authorizationRequest(authorizationRequest)
.build();
// @formatter:on
this.authorizationConsentCustomizer.accept(authorizationConsentAuthenticationContext);
@@ -187,15 +185,16 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem
}
authorization = OAuth2Authorization.from(authorization)
.token(deviceCodeToken.getToken(), metadata ->
metadata.put(OAuth2Authorization.Token.ACCESS_DENIED_METADATA_NAME, true))
metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true))
.token(userCodeToken.getToken(), metadata ->
metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true))
.attributes(attrs -> attrs.remove(OAuth2ParameterNames.STATE))
.build();
this.authorizationService.save(authorization);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Invalidated device code and user code because authorization consent was denied");
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.ACCESS_DENIED);
throwError(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID);
}
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build();
@@ -206,26 +205,23 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem
}
}
OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization)
.principalName(principal.getName())
authorization = OAuth2Authorization.from(authorization)
.authorizedScopes(authorizedScopes)
.token(deviceCodeToken.getToken(), metadata -> metadata
.put(OAuth2Authorization.Token.ACCESS_GRANTED_METADATA_NAME, true))
.token(userCodeToken.getToken(), metadata -> metadata
.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true))
.attribute(Principal.class.getName(), principal)
.token(userCodeToken.getToken(), metadata ->
metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true))
.attributes(attrs -> attrs.remove(OAuth2ParameterNames.STATE))
.attributes(attrs -> attrs.remove(OAuth2ParameterNames.SCOPE))
.build();
this.authorizationService.save(updatedAuthorization);
this.authorizationService.save(authorization);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization with authorized scopes");
// This log is kept separate for consistency with other providers
this.logger.trace("Authenticated authorization consent request");
this.logger.trace("Authenticated device authorization consent request");
}
return new OAuth2DeviceVerificationAuthenticationToken(registeredClient.getClientId(), principal,
deviceAuthorizationConsentAuthentication.getUserCode());
return new OAuth2DeviceVerificationAuthenticationToken(principal,
deviceAuthorizationConsentAuthentication.getUserCode(), registeredClient.getClientId());
}
@Override
@@ -244,10 +240,9 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem
* prior to {@link OAuth2AuthorizationConsentService#save(OAuth2AuthorizationConsent)}.</li>
* <li>The {@link Authentication} of type
* {@link OAuth2DeviceAuthorizationConsentAuthenticationToken}.</li>
* <li>The {@link RegisteredClient} associated with the authorization request.</li>
* <li>The {@link RegisteredClient} associated with the device authorization request.</li>
* <li>The {@link OAuth2Authorization} associated with the state token presented in the
* authorization consent request.</li>
* <li>The {@link OAuth2AuthorizationRequest} associated with the authorization consent request.</li>
* device authorization consent request.</li>
* </ul>
*
* @param authorizationConsentCustomizer the {@code Consumer} providing access to the
@@ -258,8 +253,14 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem
this.authorizationConsentCustomizer = authorizationConsentCustomizer;
}
private static boolean isPrincipalAuthenticated(Authentication principal) {
return principal != null &&
!AnonymousAuthenticationToken.class.isAssignableFrom(principal.getClass()) &&
principal.isAuthenticated();
}
private static void throwError(String errorCode, String parameterName) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, DEFAULT_ERROR_URI);
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ERROR_URI);
throw new OAuth2AuthenticationException(error);
}

View File

@@ -21,16 +21,19 @@ import java.util.Map;
import java.util.Set;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
/**
* An {@link Authentication} implementation for the Authorization Consent used
* An {@link Authentication} implementation for the Device Authorization Consent used
* in the OAuth 2.0 Device Authorization Grant.
*
* @author Steve Riesenberg
* @since 1.1
* @see AbstractAuthenticationToken
* @see OAuth2DeviceAuthorizationConsentAuthenticationProvider
*/
public class OAuth2DeviceAuthorizationConsentAuthenticationToken extends OAuth2AuthorizationConsentAuthenticationToken {
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
@@ -43,7 +46,7 @@ public class OAuth2DeviceAuthorizationConsentAuthenticationToken extends OAuth2A
* @param authorizationUri the authorization URI
* @param clientId the client identifier
* @param principal the {@code Principal} (Resource Owner)
* @param userCode the user code associated with the device authorization request
* @param userCode the user code associated with the device authorization response
* @param state the state
* @param authorizedScopes the authorized scope(s)
* @param additionalParameters the additional parameters
@@ -64,7 +67,7 @@ public class OAuth2DeviceAuthorizationConsentAuthenticationToken extends OAuth2A
* @param authorizationUri the authorization URI
* @param clientId the client identifier
* @param principal the {@code Principal} (Resource Owner)
* @param userCode the user code associated with the device authorization request
* @param userCode the user code associated with the device authorization response
* @param state the state
* @param requestedScopes the requested scope(s)
* @param authorizedScopes the authorized scope(s)

View File

@@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.authentication;
import java.security.Principal;
import java.time.Instant;
import java.util.Base64;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
@@ -37,7 +38,6 @@ import org.springframework.security.oauth2.core.OAuth2DeviceCode;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2UserCode;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
@@ -48,6 +48,7 @@ import org.springframework.security.oauth2.server.authorization.token.DefaultOAu
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
@@ -100,11 +101,19 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationProvider implem
this.logger.trace("Retrieved registered client");
}
// Validate client grant types has device_code grant type
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.DEVICE_CODE)) {
throwError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID);
}
Set<String> requestedScopes = deviceAuthorizationRequestAuthentication.getScopes();
if (!CollectionUtils.isEmpty(requestedScopes)) {
for (String requestedScope : requestedScopes) {
if (!registeredClient.getScopes().contains(requestedScope)) {
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE);
}
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated device authorization request parameters");
}
@@ -128,7 +137,7 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationProvider implem
}
if (this.logger.isTraceEnabled()) {
logger.trace("Generated device code");
this.logger.trace("Generated device code");
}
// Generate a low-entropy string to use as the user code
@@ -141,21 +150,9 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationProvider implem
}
if (this.logger.isTraceEnabled()) {
logger.trace("Generated user code");
this.logger.trace("Generated user code");
}
String authorizationUri = deviceAuthorizationRequestAuthentication.getAuthorizationUri();
Set<String> requestedScopes = deviceAuthorizationRequestAuthentication.getScopes();
// @formatter:off
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri(authorizationUri)
.clientId(registeredClient.getClientId())
.scopes(requestedScopes)
.build();
// @formatter:on
// @formatter:off
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(clientPrincipal.getName())
@@ -163,7 +160,7 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationProvider implem
.token(deviceCode)
.token(userCode)
.attribute(Principal.class.getName(), clientPrincipal)
.attribute(OAuth2AuthorizationRequest.class.getName(), authorizationRequest)
.attribute(OAuth2ParameterNames.SCOPE, new HashSet<>(requestedScopes))
.build();
// @formatter:on
this.authorizationService.save(authorization);
@@ -176,7 +173,8 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationProvider implem
this.logger.trace("Authenticated device authorization request");
}
return new OAuth2DeviceAuthorizationRequestAuthenticationToken(clientPrincipal, requestedScopes, deviceCode, userCode);
return new OAuth2DeviceAuthorizationRequestAuthenticationToken(
clientPrincipal, requestedScopes, deviceCode, userCode);
}
@Override

View File

@@ -16,6 +16,7 @@
package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -29,12 +30,13 @@ import org.springframework.security.oauth2.server.authorization.util.SpringAutho
import org.springframework.util.Assert;
/**
* An {@link Authentication} implementation for the OAuth 2.0 Device Authorization Request
* used in the Device Authorization Grant.
* An {@link Authentication} implementation for the Device Authorization Request
* used in the OAuth 2.0 Device Authorization Grant.
*
* @author Steve Riesenberg
* @since 1.1
* @see AbstractAuthenticationToken
* @see OAuth2ClientAuthenticationToken
* @see OAuth2DeviceAuthorizationRequestAuthenticationProvider
*/
public class OAuth2DeviceAuthorizationRequestAuthenticationToken extends AbstractAuthenticationToken {
@@ -65,7 +67,10 @@ public class OAuth2DeviceAuthorizationRequestAuthenticationToken extends Abstrac
scopes != null ?
new HashSet<>(scopes) :
Collections.emptySet());
this.additionalParameters = additionalParameters;
this.additionalParameters = Collections.unmodifiableMap(
additionalParameters != null ?
new HashMap<>(additionalParameters) :
Collections.emptyMap());
this.deviceCode = null;
this.userCode = null;
}
@@ -109,16 +114,16 @@ public class OAuth2DeviceAuthorizationRequestAuthenticationToken extends Abstrac
/**
* Returns the authorization {@code URI}.
*
* @return the authorization {@code URI}.
* @return the authorization {@code URI}
*/
public String getAuthorizationUri() {
return authorizationUri;
return this.authorizationUri;
}
/**
* Returns the requested scope(s).
*
* @return the requested scope(s).
* @return the requested scope(s)
*/
public Set<String> getScopes() {
return this.scopes;

View File

@@ -26,7 +26,6 @@ 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;
import org.springframework.security.oauth2.core.OAuth2DeviceCode;
@@ -34,7 +33,7 @@ import org.springframework.security.oauth2.core.OAuth2Error;
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.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.OAuth2UserCode;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
@@ -46,8 +45,11 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Toke
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.Assert;
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
/**
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Device Authorization Grant.
* An {@link AuthenticationProvider} implementation for the Device Access Token Request
* used in the OAuth 2.0 Device Authorization Grant.
*
* @author Steve Riesenberg
* @since 1.1
@@ -94,8 +96,8 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat
OAuth2DeviceCodeAuthenticationToken deviceCodeAuthentication =
(OAuth2DeviceCodeAuthenticationToken) authentication;
OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils
.getAuthenticatedClientElseThrowInvalidClient(deviceCodeAuthentication);
OAuth2ClientAuthenticationToken clientPrincipal =
getAuthenticatedClientElseThrowInvalidClient(deviceCodeAuthentication);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
if (this.logger.isTraceEnabled()) {
@@ -112,19 +114,17 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat
this.logger.trace("Retrieved authorization with device code");
}
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
OAuth2AuthorizationRequest.class.getName());
OAuth2Authorization.Token<OAuth2UserCode> userCode = authorization.getToken(OAuth2UserCode.class);
OAuth2Authorization.Token<OAuth2DeviceCode> deviceCode = authorization.getToken(OAuth2DeviceCode.class);
if (!registeredClient.getClientId().equals(authorizationRequest.getClientId())) {
if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) {
if (!deviceCode.isInvalidated()) {
// Invalidate the device code given that a different client is attempting to use it
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, deviceCode.getToken());
this.authorizationService.save(authorization);
if (this.logger.isWarnEnabled()) {
this.logger.warn(LogMessage.format(
"Invalidated device code used by registered client '%s'", registeredClient.getId()));
"Invalidated device code used by registered client '%s'", authorization.getRegisteredClientId()));
}
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
@@ -133,29 +133,6 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat
// In https://www.rfc-editor.org/rfc/rfc8628.html#section-3.5,
// the following error codes are defined:
// access_denied
// The authorization request was denied.
if (Boolean.TRUE.equals(deviceCode.getMetadata(OAuth2Authorization.Token.ACCESS_DENIED_METADATA_NAME))) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.ACCESS_DENIED, null, DEVICE_ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
// expired_token
// The "device_code" has expired, and the device authorization
// session has concluded. The client MAY commence a new device
// authorization request but SHOULD wait for user interaction before
// restarting to avoid unnecessary polling.
if (deviceCode.isExpired()) {
OAuth2Error error = new OAuth2Error(EXPIRED_TOKEN, null, DEVICE_ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
// slow_down
// A variant of "authorization_pending", the authorization request is
// still pending and polling should continue, but the interval MUST
// be increased by 5 seconds for this and all subsequent requests.
// Note: This error is not handled in the framework.
// authorization_pending
// The authorization request is still pending as the end user hasn't
// yet completed the user-interaction steps (Section 3.3). The
@@ -166,17 +143,43 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat
// Section 3.2), or 5 seconds if none was provided, and respect any
// increase in the polling interval required by the "slow_down"
// error.
if (!Boolean.TRUE.equals(deviceCode.getMetadata(OAuth2Authorization.Token.ACCESS_GRANTED_METADATA_NAME))) {
if (!userCode.isInvalidated()) {
OAuth2Error error = new OAuth2Error(AUTHORIZATION_PENDING, null, DEVICE_ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
if (!deviceCode.isActive()) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
// slow_down
// A variant of "authorization_pending", the authorization request is
// still pending and polling should continue, but the interval MUST
// be increased by 5 seconds for this and all subsequent requests.
// NOTE: This error is not handled in the framework.
// access_denied
// The authorization request was denied.
if (deviceCode.isInvalidated()) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.ACCESS_DENIED, null, DEVICE_ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
// expired_token
// The "device_code" has expired, and the device authorization
// session has concluded. The client MAY commence a new device
// authorization request but SHOULD wait for user interaction before
// restarting to avoid unnecessary polling.
if (deviceCode.isExpired()) {
// Invalidate the device code
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, deviceCode.getToken());
this.authorizationService.save(authorization);
if (this.logger.isWarnEnabled()) {
this.logger.warn(LogMessage.format(
"Invalidated device code used by registered client '%s'", authorization.getRegisteredClientId()));
}
OAuth2Error error = new OAuth2Error(EXPIRED_TOKEN, null, DEVICE_ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated token request parameters");
this.logger.trace("Validated device token request parameters");
}
// @formatter:off
@@ -222,10 +225,7 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat
// ----- Refresh token -----
OAuth2RefreshToken refreshToken = null;
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
// Do not issue refresh token to public client
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) {
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
@@ -250,6 +250,10 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat
this.logger.trace("Saved authorization");
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authenticated device token request");
}
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken);
}

View File

@@ -23,7 +23,8 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.util.Assert;
/**
* An {@link Authentication} implementation used for the OAuth 2.0 Device Authorization Grant.
* An {@link Authentication} implementation for the Device Access Token Request
* used in the OAuth 2.0 Device Authorization Grant.
*
* @author Steve Riesenberg
* @since 1.1
@@ -41,7 +42,8 @@ public class OAuth2DeviceCodeAuthenticationToken extends OAuth2AuthorizationGran
* @param clientPrincipal the authenticated client principal
* @param additionalParameters the additional parameters
*/
public OAuth2DeviceCodeAuthenticationToken(String deviceCode, Authentication clientPrincipal, @Nullable Map<String, Object> additionalParameters) {
public OAuth2DeviceCodeAuthenticationToken(String deviceCode, Authentication clientPrincipal,
@Nullable Map<String, Object> additionalParameters) {
super(AuthorizationGrantType.DEVICE_CODE, clientPrincipal, additionalParameters);
Assert.hasText(deviceCode, "deviceCode cannot be empty");
this.deviceCode = deviceCode;

View File

@@ -29,10 +29,8 @@ 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.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2DeviceCode;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2UserCode;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
@@ -41,16 +39,17 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.util.Assert;
/**
* An {@link AuthenticationProvider} implementation for the Verification {@code URI}
* (submission of the user code)} used in the OAuth 2.0 Device Authorization Grant.
* An {@link AuthenticationProvider} implementation for the Device Verification Request
* (submission of the user code) used in the OAuth 2.0 Device Authorization Grant.
*
* @author Steve Riesenberg
* @since 1.1
* @see OAuth2DeviceVerificationAuthenticationToken
* @see OAuth2AuthorizationConsent
* @see OAuth2DeviceAuthorizationRequestAuthenticationProvider
* @see OAuth2DeviceAuthorizationConsentAuthenticationProvider
* @see OAuth2DeviceCodeAuthenticationProvider
@@ -105,6 +104,15 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut
this.logger.trace("Retrieved authorization with user code");
}
Authentication principal = (Authentication) deviceVerificationAuthentication.getPrincipal();
if (!isPrincipalAuthenticated(principal)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not authenticate device verification request since principal not authenticated");
}
// Return the device verification request as-is where isAuthenticated() is false
return deviceVerificationAuthentication;
}
RegisteredClient registeredClient = this.registeredClientRepository.findById(
authorization.getRegisteredClientId());
@@ -112,31 +120,21 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut
this.logger.trace("Retrieved registered client");
}
Authentication principal = (Authentication) deviceVerificationAuthentication.getPrincipal();
if (!isPrincipalAuthenticated(principal)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not authenticate device authorization request since principal not authenticated");
}
// Return the authorization request as-is where isAuthenticated() is false
return deviceVerificationAuthentication;
}
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
Set<String> requestedScopes = authorization.getAttribute(OAuth2ParameterNames.SCOPE);
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService.findById(
registeredClient.getId(), principal.getName());
Set<String> currentAuthorizedScopes = currentAuthorizationConsent != null ?
currentAuthorizationConsent.getScopes() : null;
if (requiresAuthorizationConsent(registeredClient, authorizationRequest, currentAuthorizationConsent)) {
if (requiresAuthorizationConsent(requestedScopes, currentAuthorizationConsent)) {
String state = DEFAULT_STATE_GENERATOR.generateKey();
authorization = OAuth2Authorization.from(authorization)
.principalName(principal.getName())
.attribute(Principal.class.getName(), principal)
.attribute(OAuth2ParameterNames.STATE, state)
.build();
if (this.logger.isTraceEnabled()) {
logger.trace("Generated authorization consent state");
this.logger.trace("Generated device authorization consent state");
}
this.authorizationService.save(authorization);
@@ -145,33 +143,39 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut
this.logger.trace("Saved authorization");
}
return new OAuth2DeviceAuthorizationConsentAuthenticationToken(authorizationRequest.getAuthorizationUri(),
Set<String> currentAuthorizedScopes = currentAuthorizationConsent != null ?
currentAuthorizationConsent.getScopes() : null;
AuthorizationServerSettings authorizationServerSettings =
AuthorizationServerContextHolder.getContext().getAuthorizationServerSettings();
String deviceVerificationUri = authorizationServerSettings.getDeviceVerificationEndpoint();
return new OAuth2DeviceAuthorizationConsentAuthenticationToken(deviceVerificationUri,
registeredClient.getClientId(), principal, deviceVerificationAuthentication.getUserCode(), state,
authorizationRequest.getScopes(), currentAuthorizedScopes);
requestedScopes, currentAuthorizedScopes);
}
OAuth2Authorization.Token<OAuth2DeviceCode> deviceCode = authorization.getToken(OAuth2DeviceCode.class);
OAuth2Authorization.Token<OAuth2UserCode> userCode = authorization.getToken(OAuth2UserCode.class);
OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization)
// @formatter:off
authorization = OAuth2Authorization.from(authorization)
.principalName(principal.getName())
.authorizedScopes(authorizationRequest.getScopes())
.token(deviceCode.getToken(), metadata -> metadata
.put(OAuth2Authorization.Token.ACCESS_GRANTED_METADATA_NAME, true))
.authorizedScopes(requestedScopes)
.token(userCode.getToken(), metadata -> metadata
.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true))
.attribute(Principal.class.getName(), principal)
.attributes(attrs -> attrs.remove(OAuth2ParameterNames.STATE))
.attributes(attributes -> attributes.remove(OAuth2ParameterNames.SCOPE))
.build();
this.authorizationService.save(updatedAuthorization);
// @formatter:on
this.authorizationService.save(authorization);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization with authorized scopes");
// This log is kept separate for consistency with other providers
this.logger.trace("Authenticated authorization consent request");
this.logger.trace("Authenticated device verification request");
}
return new OAuth2DeviceVerificationAuthenticationToken(registeredClient.getClientId(), principal,
deviceVerificationAuthentication.getUserCode());
return new OAuth2DeviceVerificationAuthenticationToken(principal,
deviceVerificationAuthentication.getUserCode(), registeredClient.getClientId());
}
@Override
@@ -179,15 +183,11 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut
return OAuth2DeviceVerificationAuthenticationToken.class.isAssignableFrom(authentication);
}
private static boolean requiresAuthorizationConsent(RegisteredClient registeredClient,
OAuth2AuthorizationRequest authorizationRequest, OAuth2AuthorizationConsent authorizationConsent) {
if (!registeredClient.getClientSettings().isRequireAuthorizationConsent()) {
return false;
}
private static boolean requiresAuthorizationConsent(
Set<String> requestedScopes, OAuth2AuthorizationConsent authorizationConsent) {
if (authorizationConsent != null &&
authorizationConsent.getScopes().containsAll(authorizationRequest.getScopes())) {
authorizationConsent.getScopes().containsAll(requestedScopes)) {
return false;
}

View File

@@ -16,6 +16,7 @@
package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.lang.Nullable;
@@ -25,7 +26,7 @@ import org.springframework.security.oauth2.server.authorization.util.SpringAutho
import org.springframework.util.Assert;
/**
* An {@link Authentication} implementation for the Verification {@code URI}
* An {@link Authentication} implementation for the Device Verification Request
* (submission of the user code) used in the OAuth 2.0 Device Authorization Grant.
*
* @author Steve Riesenberg
@@ -35,44 +36,47 @@ import org.springframework.util.Assert;
*/
public class OAuth2DeviceVerificationAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private final String clientId;
private final Authentication principal;
private final String userCode;
private final Map<String, Object> additionalParameters;
private final String clientId;
/**
* Constructs an {@code OAuth2DeviceVerificationAuthenticationToken} using the provided parameters.
*
* @param principal the {@code Principal} (Resource Owner)
* @param userCode the user code associated with the device authorization request
* @param userCode the user code associated with the device authorization response
* @param additionalParameters the additional parameters
*/
public OAuth2DeviceVerificationAuthenticationToken(Authentication principal, String userCode,
@Nullable Map<String, Object> additionalParameters) {
super(Collections.emptyList());
Assert.notNull(principal, "principal cannot be null");
Assert.notNull(userCode, "userCode cannot be null");
this.clientId = null;
Assert.hasText(userCode, "userCode cannot be empty");
this.principal = principal;
this.userCode = userCode;
this.additionalParameters = additionalParameters;
this.additionalParameters = Collections.unmodifiableMap(
additionalParameters != null ?
new HashMap<>(additionalParameters) :
Collections.emptyMap());
this.clientId = null;
}
/**
* Constructs an {@code OAuth2DeviceVerificationAuthenticationToken} using the provided parameters.
*
* @param clientId the client identifier
* @param principal the {@code Principal} (Resource Owner)
* @param userCode the user code associated with the device authorization request
* @param userCode the user code associated with the device authorization response
* @param clientId the client identifier
*/
public OAuth2DeviceVerificationAuthenticationToken(String clientId, Authentication principal, String userCode) {
public OAuth2DeviceVerificationAuthenticationToken(Authentication principal, String userCode, String clientId) {
super(Collections.emptyList());
Assert.hasText(clientId, "clientId cannot be empty");
Assert.notNull(principal, "principal cannot be null");
Assert.notNull(userCode, "userCode cannot be null");
this.clientId = clientId;
Assert.hasText(userCode, "userCode cannot be empty");
Assert.hasText(clientId, "clientId cannot be empty");
this.principal = principal;
this.userCode = userCode;
this.clientId = clientId;
this.additionalParameters = null;
setAuthenticated(true);
}
@@ -87,15 +91,6 @@ public class OAuth2DeviceVerificationAuthenticationToken extends AbstractAuthent
return "";
}
/**
* Returns the client identifier.
*
* @return the client identifier
*/
public String getClientId() {
return this.clientId;
}
/**
* Returns the user code.
*
@@ -114,4 +109,13 @@ public class OAuth2DeviceVerificationAuthenticationToken extends AbstractAuthent
return this.additionalParameters;
}
/**
* Returns the client identifier.
*
* @return the client identifier
*/
public String getClientId() {
return this.clientId;
}
}

View File

@@ -67,6 +67,8 @@ import org.springframework.util.Assert;
* @see OAuth2TokenEndpointConfigurer
* @see OAuth2TokenIntrospectionEndpointConfigurer
* @see OAuth2TokenRevocationEndpointConfigurer
* @see OAuth2DeviceAuthorizationEndpointConfigurer
* @see OAuth2DeviceVerificationEndpointConfigurer
* @see OidcConfigurer
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
@@ -316,7 +318,8 @@ public final class OAuth2AuthorizationServerConfigurer
new OrRequestMatcher(
getRequestMatcher(OAuth2TokenEndpointConfigurer.class),
getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class),
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class))
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class),
getRequestMatcher(OAuth2DeviceAuthorizationEndpointConfigurer.class))
);
}
}

View File

@@ -26,7 +26,9 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizationResponse;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken;
@@ -41,6 +43,7 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Configurer for the OAuth 2.0 Device Authorization Endpoint.
@@ -53,8 +56,8 @@ import org.springframework.util.Assert;
public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private final List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> authenticationConvertersConsumer = (authenticationConverters) -> {};
private final List<AuthenticationConverter> deviceAuthorizationRequestConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> deviceAuthorizationRequestConvertersConsumer = (deviceAuthorizationRequestConverters) -> {};
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
private AuthenticationSuccessHandler deviceAuthorizationResponseHandler;
@@ -77,7 +80,7 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO
*/
public OAuth2DeviceAuthorizationEndpointConfigurer deviceAuthorizationRequestConverter(AuthenticationConverter deviceAuthorizationRequestConverter) {
Assert.notNull(deviceAuthorizationRequestConverter, "deviceAuthorizationRequestConverter cannot be null");
this.authenticationConverters.add(deviceAuthorizationRequestConverter);
this.deviceAuthorizationRequestConverters.add(deviceAuthorizationRequestConverter);
return this;
}
@@ -92,7 +95,7 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO
public OAuth2DeviceAuthorizationEndpointConfigurer deviceAuthorizationRequestConverters(
Consumer<List<AuthenticationConverter>> deviceAuthorizationRequestConvertersConsumer) {
Assert.notNull(deviceAuthorizationRequestConvertersConsumer, "deviceAuthorizationRequestConvertersConsumer cannot be null");
this.authenticationConvertersConsumer = deviceAuthorizationRequestConvertersConsumer;
this.deviceAuthorizationRequestConvertersConsumer = deviceAuthorizationRequestConvertersConsumer;
return this;
}
@@ -125,7 +128,7 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2DeviceAuthorizationRequestAuthenticationToken}
* and returning the Device Authorization Response.
* and returning the {@link OAuth2DeviceAuthorizationResponse Device Authorization Response}.
*
* @param deviceAuthorizationResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2DeviceAuthorizationRequestAuthenticationToken}
* @return the {@link OAuth2DeviceAuthorizationEndpointConfigurer} for further configuration
@@ -136,10 +139,10 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO
}
/**
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2DeviceAuthorizationRequestAuthenticationToken}
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* and returning the {@link OAuth2Error Error Response}.
*
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2DeviceAuthorizationRequestAuthenticationToken}
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* @return the {@link OAuth2DeviceAuthorizationEndpointConfigurer} for further configuration
*/
public OAuth2DeviceAuthorizationEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
@@ -184,10 +187,10 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO
authenticationManager, authorizationServerSettings.getDeviceAuthorizationEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.authenticationConverters.isEmpty()) {
authenticationConverters.addAll(0, this.authenticationConverters);
if (!this.deviceAuthorizationRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.deviceAuthorizationRequestConverters);
}
this.authenticationConvertersConsumer.accept(authenticationConverters);
this.deviceAuthorizationRequestConvertersConsumer.accept(authenticationConverters);
deviceAuthorizationEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.deviceAuthorizationResponseHandler != null) {
@@ -196,7 +199,7 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO
if (this.errorResponseHandler != null) {
deviceAuthorizationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
if (this.verificationUri != null) {
if (StringUtils.hasText(this.verificationUri)) {
deviceAuthorizationEndpointFilter.setVerificationUri(this.verificationUri);
}
builder.addFilterAfter(postProcess(deviceAuthorizationEndpointFilter), AuthorizationFilter.class);
@@ -214,7 +217,7 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO
return authenticationConverters;
}
private List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity builder) {
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity builder) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(builder);

View File

@@ -30,7 +30,6 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationProvider;
@@ -41,10 +40,10 @@ import org.springframework.security.oauth2.server.authorization.web.OAuth2Device
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationConsentAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceVerificationAuthenticationConverter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -62,8 +61,8 @@ import org.springframework.util.StringUtils;
public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private final List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> authenticationConvertersConsumer = (authenticationConverters) -> {};
private final List<AuthenticationConverter> deviceVerificationRequestConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> deviceVerificationRequestConvertersConsumer = (deviceVerificationRequestConverters) -> {};
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
private AuthenticationSuccessHandler deviceVerificationResponseHandler;
@@ -78,15 +77,15 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract a Device Verification Request (or Consent) from {@link HttpServletRequest}
* Sets the {@link AuthenticationConverter} used when attempting to extract a Device Verification Request (or Device Authorization Consent) from {@link HttpServletRequest}
* to an instance of {@link OAuth2DeviceVerificationAuthenticationToken} or {@link OAuth2DeviceAuthorizationConsentAuthenticationToken} used for authenticating the request.
*
* @param deviceVerificationRequestConverter the {@link AuthenticationConverter} used when attempting to extract a Device Authorization Request from {@link HttpServletRequest}
* @param deviceVerificationRequestConverter the {@link AuthenticationConverter} used when attempting to extract a Device Verification Request (or Device Authorization Consent) from {@link HttpServletRequest}
* @return the {@link OAuth2DeviceVerificationEndpointConfigurer} for further configuration
*/
public OAuth2DeviceVerificationEndpointConfigurer deviceVerificationRequestConverter(AuthenticationConverter deviceVerificationRequestConverter) {
Assert.notNull(deviceVerificationRequestConverter, "deviceVerificationRequestConverter cannot be null");
this.authenticationConverters.add(deviceVerificationRequestConverter);
this.deviceVerificationRequestConverters.add(deviceVerificationRequestConverter);
return this;
}
@@ -101,14 +100,14 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
public OAuth2DeviceVerificationEndpointConfigurer deviceVerificationRequestConverters(
Consumer<List<AuthenticationConverter>> deviceVerificationRequestConvertersConsumer) {
Assert.notNull(deviceVerificationRequestConvertersConsumer, "deviceVerificationRequestConvertersConsumer cannot be null");
this.authenticationConvertersConsumer = deviceVerificationRequestConvertersConsumer;
this.deviceVerificationRequestConvertersConsumer = deviceVerificationRequestConvertersConsumer;
return this;
}
/**
* Adds an {@link AuthenticationProvider} used for authenticating an {@link OAuth2DeviceVerificationAuthenticationToken}.
* Adds an {@link AuthenticationProvider} used for authenticating an {@link OAuth2DeviceVerificationAuthenticationToken} or {@link OAuth2DeviceAuthorizationConsentAuthenticationToken}.
*
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating an {@link OAuth2DeviceVerificationAuthenticationToken}
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating an {@link OAuth2DeviceVerificationAuthenticationToken} or {@link OAuth2DeviceAuthorizationConsentAuthenticationToken}
* @return the {@link OAuth2DeviceVerificationEndpointConfigurer} for further configuration
*/
public OAuth2DeviceVerificationEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
@@ -133,10 +132,10 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2DeviceAuthorizationConsentAuthenticationToken}
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2DeviceVerificationAuthenticationToken}
* and returning the response.
*
* @param deviceVerificationResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
* @param deviceVerificationResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2DeviceVerificationAuthenticationToken}
* @return the {@link OAuth2DeviceVerificationEndpointConfigurer} for further configuration
*/
public OAuth2DeviceVerificationEndpointConfigurer deviceVerificationResponseHandler(AuthenticationSuccessHandler deviceVerificationResponseHandler) {
@@ -166,9 +165,9 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
*
* <ul>
* <li>{@code client_id} - the client identifier</li>
* <li>{@code scope} - a space-delimited list of scopes present in the authorization request</li>
* <li>{@code scope} - a space-delimited list of scopes present in the device authorization request</li>
* <li>{@code state} - a CSRF protection token</li>
* <li>@code code} - the user code</li>
* <li>{@code user_code} - the user code</li>
* </ul>
*
* In general, the consent page should create a form that submits
@@ -181,7 +180,7 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
* <li>It must include the received {@code state} as an HTTP parameter</li>
* <li>It must include the list of {@code scope}s the {@code Resource Owner}
* consented to as an HTTP parameter</li>
* <li>It must include the user {@code code} as an HTTP parameter</li>
* <li>It must include the received {@code user_code} as an HTTP parameter</li>
* </ul>
*
* @param consentPage the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent")
@@ -198,9 +197,11 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
OAuth2ConfigurerUtils.getAuthorizationServerSettings(builder);
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
authorizationServerSettings.getDeviceVerificationEndpoint(), HttpMethod.GET.name()),
authorizationServerSettings.getDeviceVerificationEndpoint(),
HttpMethod.GET.name()),
new AntPathRequestMatcher(
authorizationServerSettings.getDeviceVerificationEndpoint(), HttpMethod.POST.name()));
authorizationServerSettings.getDeviceVerificationEndpoint(),
HttpMethod.POST.name()));
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(builder);
if (!this.authenticationProviders.isEmpty()) {
@@ -214,18 +215,18 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
@Override
public void configure(HttpSecurity builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings =
OAuth2ConfigurerUtils.getAuthorizationServerSettings(builder);
OAuth2DeviceVerificationEndpointFilter deviceVerificationEndpointFilter =
new OAuth2DeviceVerificationEndpointFilter(
authenticationManager, authorizationServerSettings.getDeviceVerificationEndpoint());
authenticationManager,
authorizationServerSettings.getDeviceVerificationEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.authenticationConverters.isEmpty()) {
authenticationConverters.addAll(0, this.authenticationConverters);
if (!this.deviceVerificationRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.deviceVerificationRequestConverters);
}
this.authenticationConvertersConsumer.accept(authenticationConverters);
this.deviceVerificationRequestConvertersConsumer.accept(authenticationConverters);
deviceVerificationEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.deviceVerificationResponseHandler != null) {
@@ -237,7 +238,7 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
if (StringUtils.hasText(this.consentPage)) {
deviceVerificationEndpointFilter.setConsentPage(this.consentPage);
}
builder.addFilterAfter(postProcess(deviceVerificationEndpointFilter), AuthorizationFilter.class);
builder.addFilterBefore(postProcess(deviceVerificationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
@Override
@@ -247,6 +248,7 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
authenticationConverters.add(new OAuth2DeviceVerificationAuthenticationConverter());
authenticationConverters.add(new OAuth2DeviceAuthorizationConsentAuthenticationConverter());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 the original author or authors.
* Copyright 2020-2023 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.
@@ -136,6 +136,7 @@ public class OAuth2AuthorizationServerMetadataHttpMessageConverter
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, urlConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT, urlConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, urlConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, urlConverter);

View File

@@ -94,6 +94,7 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
OidcProviderConfiguration.Builder providerConfiguration = OidcProviderConfiguration.builder()
.issuer(issuer)
.authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint()))
.deviceAuthorizationEndpoint(asUrl(issuer, authorizationServerSettings.getDeviceAuthorizationEndpoint()))
.tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint()))
.tokenEndpointAuthenticationMethods(clientAuthenticationMethods())
.jwkSetUrl(asUrl(issuer, authorizationServerSettings.getJwkSetEndpoint()))
@@ -103,6 +104,7 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
.grantType(AuthorizationGrantType.DEVICE_CODE.getValue())
.tokenRevocationEndpoint(asUrl(issuer, authorizationServerSettings.getTokenRevocationEndpoint()))
.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
.tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint()))

View File

@@ -55,16 +55,18 @@ public final class AuthorizationServerSettings extends AbstractSettings {
/**
* Returns the OAuth 2.0 Device Authorization endpoint. The default is {@code /oauth2/device_authorization}.
*
* @return the Authorization endpoint
* @return the Device Authorization endpoint
* @since 1.1
*/
public String getDeviceAuthorizationEndpoint() {
return getSetting(ConfigurationSettingNames.AuthorizationServer.DEVICE_AUTHORIZATION_ENDPOINT);
}
/**
* Returns the OAuth 2.0 Device VERIFICATION endpoint. The default is {@code /oauth2/device_verification}.
* Returns the OAuth 2.0 Device Verification endpoint. The default is {@code /oauth2/device_verification}.
*
* @return the Authorization endpoint
* @return the Device Verification endpoint
* @since 1.1
*/
public String getDeviceVerificationEndpoint() {
return getSetting(ConfigurationSettingNames.AuthorizationServer.DEVICE_VERIFICATION_ENDPOINT);
@@ -198,6 +200,7 @@ public final class AuthorizationServerSettings extends AbstractSettings {
*
* @param deviceAuthorizationEndpoint the Device Authorization endpoint
* @return the {@link Builder} for further configuration
* @since 1.1
*/
public Builder deviceAuthorizationEndpoint(String deviceAuthorizationEndpoint) {
return setting(ConfigurationSettingNames.AuthorizationServer.DEVICE_AUTHORIZATION_ENDPOINT, deviceAuthorizationEndpoint);
@@ -208,6 +211,7 @@ public final class AuthorizationServerSettings extends AbstractSettings {
*
* @param deviceVerificationEndpoint the Device Verification endpoint
* @return the {@link Builder} for further configuration
* @since 1.1
*/
public Builder deviceVerificationEndpoint(String deviceVerificationEndpoint) {
return setting(ConfigurationSettingNames.AuthorizationServer.DEVICE_VERIFICATION_ENDPOINT, deviceVerificationEndpoint);

View File

@@ -67,9 +67,9 @@ public final class TokenSettings extends AbstractSettings {
}
/**
* Returns the time-to-live for a device code. The default is 30 minutes.
* Returns the time-to-live for a device code. The default is 5 minutes.
*
* @return the time-to-live for an authorization code
* @return the time-to-live for a device code
* @since 1.1
*/
public Duration getDeviceCodeTimeToLive() {
@@ -113,7 +113,7 @@ public final class TokenSettings extends AbstractSettings {
.authorizationCodeTimeToLive(Duration.ofMinutes(5))
.accessTokenTimeToLive(Duration.ofMinutes(5))
.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
.deviceCodeTimeToLive(Duration.ofMinutes(30))
.deviceCodeTimeToLive(Duration.ofMinutes(5))
.reuseRefreshTokens(true)
.refreshTokenTimeToLive(Duration.ofMinutes(60))
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 the original author or authors.
* Copyright 2020-2023 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.
@@ -92,6 +92,7 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP
OAuth2AuthorizationServerMetadata.Builder authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
.issuer(issuer)
.authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint()))
.deviceAuthorizationEndpoint(asUrl(issuer, authorizationServerSettings.getDeviceAuthorizationEndpoint()))
.tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint()))
.tokenEndpointAuthenticationMethods(clientAuthenticationMethods())
.jwkSetUrl(asUrl(issuer, authorizationServerSettings.getJwkSetEndpoint()))
@@ -99,6 +100,7 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
.grantType(AuthorizationGrantType.DEVICE_CODE.getValue())
.tokenRevocationEndpoint(asUrl(issuer, authorizationServerSettings.getTokenRevocationEndpoint()))
.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
.tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint()))

View File

@@ -27,6 +27,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
@@ -40,7 +41,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizati
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2DeviceAuthorizationResponseHttpMessageConverter;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
@@ -56,7 +56,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UriComponentsBuilder;
/**
* A {@code Filter} for the OAuth 2.0 Device Authorization Grant,
* A {@code Filter} for the OAuth 2.0 Device Authorization endpoint,
* which handles the processing of the OAuth 2.0 Device Authorization Request.
*
* @author Steve Riesenberg
@@ -72,20 +72,18 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques
private static final String DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI = "/oauth2/device_authorization";
private static final String DEFAULT_DEVICE_VERIFICATION_URI = "/oauth2/device_verification";
private final AuthenticationManager authenticationManager;
private final RequestMatcher deviceAuthorizationEndpointMatcher;
private final HttpMessageConverter<OAuth2DeviceAuthorizationResponse> deviceAuthorizationHttpResponseConverter =
new OAuth2DeviceAuthorizationResponseHttpMessageConverter();
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
new OAuth2ErrorHttpMessageConverter();
private AuthenticationConverter authenticationConverter;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource =
new WebAuthenticationDetailsSource();
private AuthenticationConverter authenticationConverter;
private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendDeviceAuthorizationResponse;
private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse;
private String verificationUri = DEFAULT_DEVICE_VERIFICATION_URI;
private String verificationUri = OAuth2DeviceVerificationEndpointFilter.DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI;
/**
* Constructs an {@code OAuth2DeviceAuthorizationEndpointFilter} using the provided parameters.
@@ -121,17 +119,17 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques
}
try {
OAuth2DeviceAuthorizationRequestAuthenticationToken deviceAuthorizationRequestAuthenticationToken =
(OAuth2DeviceAuthorizationRequestAuthenticationToken) this.authenticationConverter.convert(request);
deviceAuthorizationRequestAuthenticationToken.setDetails(
this.authenticationDetailsSource.buildDetails(request));
Authentication deviceAuthorizationRequestAuthentication = this.authenticationConverter.convert(request);
if (deviceAuthorizationRequestAuthentication instanceof AbstractAuthenticationToken) {
((AbstractAuthenticationToken) deviceAuthorizationRequestAuthentication)
.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
OAuth2DeviceAuthorizationRequestAuthenticationToken deviceAuthorizationRequestAuthenticationTokenResult =
(OAuth2DeviceAuthorizationRequestAuthenticationToken) this.authenticationManager.authenticate(
deviceAuthorizationRequestAuthenticationToken);
Authentication deviceAuthorizationRequestAuthenticationResult =
this.authenticationManager.authenticate(deviceAuthorizationRequestAuthentication);
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response,
deviceAuthorizationRequestAuthenticationTokenResult);
deviceAuthorizationRequestAuthenticationResult);
} catch (OAuth2AuthenticationException ex) {
SecurityContextHolder.clearContext();
if (this.logger.isTraceEnabled()) {
@@ -141,17 +139,6 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques
}
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract a Device Authorization Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2DeviceAuthorizationRequestAuthenticationToken} used for authenticating the request.
*
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract a DeviceAuthorization Request from {@link HttpServletRequest}
*/
public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
}
/**
* Sets the {@link AuthenticationDetailsSource} used for building an authentication details instance from {@link HttpServletRequest}.
*
@@ -162,9 +149,20 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques
this.authenticationDetailsSource = authenticationDetailsSource;
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract a Device Authorization Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2DeviceAuthorizationRequestAuthenticationToken} used for authenticating the request.
*
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract a Device Authorization Request from {@link HttpServletRequest}
*/
public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2DeviceAuthorizationRequestAuthenticationToken}
* and returning the Device Authorization Response.
* and returning the {@link OAuth2DeviceAuthorizationResponse Device Authorization Response}.
*
* @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2DeviceAuthorizationRequestAuthenticationToken}
*/
@@ -174,10 +172,10 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques
}
/**
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2DeviceAuthorizationRequestAuthenticationToken}
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* and returning the {@link OAuth2Error Error Response}.
*
* @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationException}
* @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
*/
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
@@ -198,11 +196,11 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques
private void sendDeviceAuthorizationResponse(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
OAuth2DeviceAuthorizationRequestAuthenticationToken deviceAuthorizationRequestAuthenticationToken =
OAuth2DeviceAuthorizationRequestAuthenticationToken deviceAuthorizationRequestAuthentication =
(OAuth2DeviceAuthorizationRequestAuthenticationToken) authentication;
OAuth2DeviceCode deviceCode = deviceAuthorizationRequestAuthenticationToken.getDeviceCode();
OAuth2UserCode userCode = deviceAuthorizationRequestAuthenticationToken.getUserCode();
OAuth2DeviceCode deviceCode = deviceAuthorizationRequestAuthentication.getDeviceCode();
OAuth2UserCode userCode = deviceAuthorizationRequestAuthentication.getUserCode();
// Generate the fully-qualified verification URI
String issuerUri = AuthorizationServerContextHolder.getContext().getIssuer();
@@ -237,5 +235,3 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques
}
}

View File

@@ -63,9 +63,9 @@ import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UriComponentsBuilder;
/**
* A {@code Filter} for the OAuth 2.0 Device Authorization Grant, which handles
* the processing of the Verification {@code URI} (submission of the user code)
* and OAuth 2.0 Authorization Consent.
* A {@code Filter} for the OAuth 2.0 Device Authorization Grant,
* which handles the processing of the Device Verification Request (submission of the user code)
* and the Device Authorization Consent.
*
* @author Steve Riesenberg
* @since 1.1
@@ -79,7 +79,7 @@ import org.springframework.web.util.UriComponentsBuilder;
*/
public final class OAuth2DeviceVerificationEndpointFilter extends OncePerRequestFilter {
private static final String DEFAULT_DEVICE_VERIFICATION_URI = "/oauth2/device_verification";
static final String DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI = "/oauth2/device_verification";
private final AuthenticationManager authenticationManager;
private final RequestMatcher deviceVerificationEndpointMatcher;
@@ -93,16 +93,16 @@ public final class OAuth2DeviceVerificationEndpointFilter extends OncePerRequest
private String consentPage;
/**
* Construct an {@code OAuth2DeviceVerificationEndpointFilter} using the provided parameters.
* Constructs an {@code OAuth2DeviceVerificationEndpointFilter} using the provided parameters.
*
* @param authenticationManager the authentication manager
*/
public OAuth2DeviceVerificationEndpointFilter(AuthenticationManager authenticationManager) {
this(authenticationManager, DEFAULT_DEVICE_VERIFICATION_URI);
this(authenticationManager, DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI);
}
/**
* Construct an {@code OAuth2DeviceVerificationEndpointFilter} using the provided parameters.
* Constructs an {@code OAuth2DeviceVerificationEndpointFilter} using the provided parameters.
*
* @param authenticationManager the authentication manager
* @param deviceVerificationEndpointUri the endpoint {@code URI} for device verification requests
@@ -184,11 +184,11 @@ public final class OAuth2DeviceVerificationEndpointFilter extends OncePerRequest
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* Sets the {@link AuthenticationConverter} used when attempting to extract a Device Verification Request (or Device Authorization Consent) from {@link HttpServletRequest}
* to an instance of {@link OAuth2DeviceVerificationAuthenticationToken} or {@link OAuth2DeviceAuthorizationConsentAuthenticationToken}
* used for authenticating the request.
*
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract a Device Verification Request (or Device Authorization Consent) from {@link HttpServletRequest}
*/
public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
@@ -207,10 +207,10 @@ public final class OAuth2DeviceVerificationEndpointFilter extends OncePerRequest
}
/**
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2DeviceVerificationAuthenticationToken}
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* and returning the {@link OAuth2Error Error Response}.
*
* @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2DeviceVerificationAuthenticationToken}
* @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
*/
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");

View File

@@ -49,6 +49,7 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;
@@ -92,6 +93,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
* @see OAuth2AuthorizationCodeAuthenticationProvider
* @see OAuth2RefreshTokenAuthenticationProvider
* @see OAuth2ClientCredentialsAuthenticationProvider
* @see OAuth2DeviceCodeAuthenticationProvider
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-3.2">Section 3.2 Token Endpoint</a>
*/
public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter {

View File

@@ -35,7 +35,7 @@ import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* Attempts to extract an Authorization Consent from {@link HttpServletRequest}
* Attempts to extract a Device Authorization Consent from {@link HttpServletRequest}
* for the OAuth 2.0 Device Authorization Grant and then converts it to an
* {@link OAuth2DeviceAuthorizationConsentAuthenticationToken} used for
* authenticating the request.
@@ -48,14 +48,14 @@ import org.springframework.util.StringUtils;
*/
public final class OAuth2DeviceAuthorizationConsentAuthenticationConverter implements AuthenticationConverter {
private static final String DEFAULT_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
private static final String DEVICE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc8628#section-3.3";
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken(
"anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
@Override
public Authentication convert(HttpServletRequest request) {
if (!"POST".equals(request.getMethod())) {
if (!"POST".equals(request.getMethod()) ||
request.getParameter(OAuth2ParameterNames.STATE) == null) {
return null;
}
@@ -63,22 +63,14 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationConverter imple
String authorizationUri = request.getRequestURL().toString();
// user_code (REQUIRED)
String userCode = parameters.getFirst(OAuth2ParameterNames.USER_CODE);
if (!StringUtils.hasText(userCode) || parameters.get(OAuth2ParameterNames.USER_CODE).size() != 1) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.USER_CODE,
DEVICE_ERROR_URI);
}
// client_id (REQUIRED)
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
if (!StringUtils.hasText(clientId) ||
parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.CLIENT_ID,
DEFAULT_ERROR_URI);
ERROR_URI);
}
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
@@ -86,13 +78,24 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationConverter imple
principal = ANONYMOUS_AUTHENTICATION;
}
// user_code (REQUIRED)
String userCode = parameters.getFirst(OAuth2ParameterNames.USER_CODE);
if (!StringUtils.hasText(userCode) ||
parameters.get(OAuth2ParameterNames.USER_CODE).size() != 1) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.USER_CODE,
ERROR_URI);
}
// state (REQUIRED)
String state = parameters.getFirst(OAuth2ParameterNames.STATE);
if (!StringUtils.hasText(state) || parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
if (!StringUtils.hasText(state) ||
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.STATE,
DEFAULT_ERROR_URI);
ERROR_URI);
}
// scope (OPTIONAL)
@@ -104,9 +107,9 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationConverter imple
Map<String, Object> additionalParameters = new HashMap<>();
parameters.forEach((key, value) -> {
if (!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
!key.equals(OAuth2ParameterNames.USER_CODE) &&
!key.equals(OAuth2ParameterNames.STATE) &&
!key.equals(OAuth2ParameterNames.SCOPE) &&
!key.equals(OAuth2ParameterNames.USER_CODE)) {
!key.equals(OAuth2ParameterNames.SCOPE)) {
additionalParameters.put(key, value.get(0));
}
});

View File

@@ -20,6 +20,7 @@ import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@@ -32,7 +33,7 @@ import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* Attempts to extract an Access Token Request from {@link HttpServletRequest} for the
* Attempts to extract a Device Access Token Request from {@link HttpServletRequest} for the
* OAuth 2.0 Device Authorization Grant and then converts it to an
* {@link OAuth2DeviceCodeAuthenticationToken} used for authenticating the
* authorization grant.
@@ -45,6 +46,7 @@ import org.springframework.util.StringUtils;
*/
public final class OAuth2DeviceCodeAuthenticationConverter implements AuthenticationConverter {
@Nullable
@Override
public Authentication convert(HttpServletRequest request) {
// grant_type (REQUIRED)
@@ -59,7 +61,8 @@ public final class OAuth2DeviceCodeAuthenticationConverter implements Authentica
// device_code (REQUIRED)
String deviceCode = parameters.getFirst(OAuth2ParameterNames.DEVICE_CODE);
if (!StringUtils.hasText(deviceCode) || parameters.get(OAuth2ParameterNames.DEVICE_CODE).size() != 1) {
if (!StringUtils.hasText(deviceCode) ||
parameters.get(OAuth2ParameterNames.DEVICE_CODE).size() != 1) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.DEVICE_CODE,

View File

@@ -46,7 +46,7 @@ import org.springframework.util.StringUtils;
*/
public final class OAuth2DeviceVerificationAuthenticationConverter implements AuthenticationConverter {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc8628#section-3.3";
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken(
"anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
@@ -64,7 +64,8 @@ public final class OAuth2DeviceVerificationAuthenticationConverter implements Au
// user_code (REQUIRED)
String userCode = parameters.getFirst(OAuth2ParameterNames.USER_CODE);
if (!StringUtils.hasText(userCode) || parameters.get(OAuth2ParameterNames.USER_CODE).size() != 1) {
if (!StringUtils.hasText(userCode) ||
parameters.get(OAuth2ParameterNames.USER_CODE).size() != 1) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.USER_CODE,

View File

@@ -24,12 +24,13 @@ import java.util.Base64;
import java.util.List;
import java.util.function.Consumer;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;

View File

@@ -126,7 +126,7 @@ public class OidcProviderConfigurationEndpointFilterTests {
assertThat(providerConfigurationResponse).contains("\"jwks_uri\":\"https://example.com/issuer1/oauth2/v1/jwks\"");
assertThat(providerConfigurationResponse).contains("\"scopes_supported\":[\"openid\"]");
assertThat(providerConfigurationResponse).contains("\"response_types_supported\":[\"code\"]");
assertThat(providerConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\"]");
assertThat(providerConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:device_code\"]");
assertThat(providerConfigurationResponse).contains("\"revocation_endpoint\":\"https://example.com/issuer1/oauth2/v1/revoke\"");
assertThat(providerConfigurationResponse).contains("\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\"]");
assertThat(providerConfigurationResponse).contains("\"introspection_endpoint\":\"https://example.com/issuer1/oauth2/v1/introspect\"");

View File

@@ -36,9 +36,9 @@ public class TokenSettingsTests {
TokenSettings tokenSettings = TokenSettings.builder().build();
assertThat(tokenSettings.getSettings()).hasSize(7);
assertThat(tokenSettings.getAuthorizationCodeTimeToLive()).isEqualTo(Duration.ofMinutes(5));
assertThat(tokenSettings.getDeviceCodeTimeToLive()).isEqualTo(Duration.ofMinutes(30));
assertThat(tokenSettings.getAccessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(5));
assertThat(tokenSettings.getAccessTokenFormat()).isEqualTo(OAuth2TokenFormat.SELF_CONTAINED);
assertThat(tokenSettings.getDeviceCodeTimeToLive()).isEqualTo(Duration.ofMinutes(5));
assertThat(tokenSettings.isReuseRefreshTokens()).isTrue();
assertThat(tokenSettings.getRefreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60));
assertThat(tokenSettings.getIdTokenSignatureAlgorithm()).isEqualTo(SignatureAlgorithm.RS256);

View File

@@ -122,7 +122,7 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests {
assertThat(authorizationServerMetadataResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\"]");
assertThat(authorizationServerMetadataResponse).contains("\"jwks_uri\":\"https://example.com/issuer1/oauth2/v1/jwks\"");
assertThat(authorizationServerMetadataResponse).contains("\"response_types_supported\":[\"code\"]");
assertThat(authorizationServerMetadataResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\"]");
assertThat(authorizationServerMetadataResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:device_code\"]");
assertThat(authorizationServerMetadataResponse).contains("\"revocation_endpoint\":\"https://example.com/issuer1/oauth2/v1/revoke\"");
assertThat(authorizationServerMetadataResponse).contains("\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\"]");
assertThat(authorizationServerMetadataResponse).contains("\"introspection_endpoint\":\"https://example.com/issuer1/oauth2/v1/introspect\"");

View File

@@ -28,7 +28,7 @@ import org.springframework.security.web.authentication.LoginUrlAuthenticationEnt
* @author Steve Riesenberg
* @since 1.1
*/
@Configuration
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class SecurityConfig {

View File

@@ -18,9 +18,7 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-security"
implementation "org.springframework.boot:spring-boot-starter-jdbc"
implementation project(":spring-security-oauth2-authorization-server")
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
implementation "org.springframework:spring-webflux"
implementation "org.webjars:webjars-locator-core"
implementation "org.webjars:bootstrap:3.4.1"
implementation "org.webjars:jquery:3.4.1"

View File

@@ -56,7 +56,7 @@ import org.springframework.security.web.authentication.LoginUrlAuthenticationEnt
* @author Steve Riesenberg
* @since 1.1
*/
@Configuration
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class SecurityConfig {
@@ -100,7 +100,7 @@ public class SecurityConfig {
public UserDetailsService userDetailsService() {
// @formatter:off
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.username("user1")
.password("password")
.roles("USER")
.build();
@@ -144,7 +144,7 @@ public class SecurityConfig {
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
@@ -167,4 +167,4 @@ public class SecurityConfig {
return AuthorizationServerSettings.builder().build();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2020-2023 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.

View File

@@ -12,7 +12,7 @@
<div class="container">
<div class="row">
<div class="col-md-8">
<form th:action="@{/oauth2/device_verification}" method="get">
<form th:action="@{/oauth2/device_verification}" method="post">
<h2>Device Activation</h2>
<p>Enter the activation code to authorize the device.</p>
<p class="gap">Activation Code</p>
@@ -30,4 +30,4 @@
</div>
</div>
</body>
</html>
</html>