Polish gh-1106 Device Authorization Grant
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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)}.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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\"");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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\"");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user