From 1354ca4549cf8e7f1584f873f71f2d3e5168cc97 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 21 Mar 2023 05:21:48 -0400 Subject: [PATCH] Polish gh-1106 Device Authorization Grant --- ...ractOAuth2AuthorizationServerMetadata.java | 17 +++- .../InMemoryOAuth2AuthorizationService.java | 4 +- .../authorization/OAuth2Authorization.java | 14 +-- ...horizationServerMetadataClaimAccessor.java | 13 ++- ...AuthorizationServerMetadataClaimNames.java | 9 +- ...orizationConsentAuthenticationContext.java | 6 +- ...rizationConsentAuthenticationProvider.java | 6 ++ ...rizationConsentAuthenticationProvider.java | 59 ++++++------- ...thorizationConsentAuthenticationToken.java | 9 +- ...rizationRequestAuthenticationProvider.java | 34 ++++---- ...thorizationRequestAuthenticationToken.java | 17 ++-- ...Auth2DeviceCodeAuthenticationProvider.java | 86 ++++++++++--------- .../OAuth2DeviceCodeAuthenticationToken.java | 6 +- ...iceVerificationAuthenticationProvider.java | 78 ++++++++--------- ...DeviceVerificationAuthenticationToken.java | 46 +++++----- .../OAuth2AuthorizationServerConfigurer.java | 5 +- ...DeviceAuthorizationEndpointConfigurer.java | 27 +++--- ...2DeviceVerificationEndpointConfigurer.java | 48 ++++++----- ...ionServerMetadataHttpMessageConverter.java | 3 +- ...dcProviderConfigurationEndpointFilter.java | 2 + .../settings/AuthorizationServerSettings.java | 10 ++- .../authorization/settings/TokenSettings.java | 6 +- ...orizationServerMetadataEndpointFilter.java | 4 +- ...uth2DeviceAuthorizationEndpointFilter.java | 62 +++++++------ ...Auth2DeviceVerificationEndpointFilter.java | 22 ++--- .../web/OAuth2TokenEndpointFilter.java | 2 + ...izationConsentAuthenticationConverter.java | 41 +++++---- ...uth2DeviceCodeAuthenticationConverter.java | 7 +- ...ceVerificationAuthenticationConverter.java | 5 +- .../OAuth2ClientCredentialsGrantTests.java | 7 +- ...viderConfigurationEndpointFilterTests.java | 2 +- .../settings/TokenSettingsTests.java | 2 +- ...tionServerMetadataEndpointFilterTests.java | 2 +- .../java/sample/config/SecurityConfig.java | 2 +- ...es-device-grant-authorizationserver.gradle | 2 - .../java/sample/config/SecurityConfig.java | 8 +- .../java/sample/web/DeviceController.java | 2 +- .../main/resources/templates/activate.html | 4 +- 38 files changed, 371 insertions(+), 308 deletions(-) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java index b7c7b81f..1c382949 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java @@ -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 3.2. Authorization Server Metadata Response * @see 4.2. OpenID Provider Configuration Response + * @see 4. Device Authorization Grant Metadata */ 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) { diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java index abdc15d7..701042ad 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java @@ -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())) { diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java index afdfc3a6..ef8bb69d 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java @@ -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. */ diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java index 8767843f..089944d1 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java @@ -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 2. Authorization Server Metadata * @see 3. OpenID Provider Metadata + * @see 4. Device Authorization Grant Metadata */ 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)}. * diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java index a4fb116a..831e0ec7 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java @@ -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 2. Authorization Server Metadata * @see 3. OpenID Provider Metadata + * @see 4. Device Authorization Grant Metadata */ 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 */ diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java index 343526fd..c8e572c7 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java @@ -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}. * diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java index 9603393d..8cdd789b 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java @@ -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; diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java index 07e8f584..a3cc37aa 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java @@ -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 requestedScopes = authorizationRequest.getScopes(); - Set authorizedScopes = deviceAuthorizationConsentAuthentication.getScopes() != null ? - new HashSet<>(deviceAuthorizationConsentAuthentication.getScopes()) : - new HashSet<>(); + Set requestedScopes = authorization.getAttribute(OAuth2ParameterNames.SCOPE); + Set 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)}. *
  • The {@link Authentication} of type * {@link OAuth2DeviceAuthorizationConsentAuthenticationToken}.
  • - *
  • The {@link RegisteredClient} associated with the authorization request.
  • + *
  • The {@link RegisteredClient} associated with the device authorization request.
  • *
  • The {@link OAuth2Authorization} associated with the state token presented in the - * authorization consent request.
  • - *
  • The {@link OAuth2AuthorizationRequest} associated with the authorization consent request.
  • + * device authorization consent request. * * * @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); } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java index 20cf0b0c..fbfe3d35 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java @@ -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) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java index 70a4a220..86edb109 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java @@ -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 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 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 diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java index 41429364..c80d9b64 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java @@ -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 getScopes() { return this.scopes; diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java index 03653937..5e08ea54 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java @@ -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 userCode = authorization.getToken(OAuth2UserCode.class); OAuth2Authorization.Token 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); } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java index 2df84805..29f7cfdf 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java @@ -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 additionalParameters) { + public OAuth2DeviceCodeAuthenticationToken(String deviceCode, Authentication clientPrincipal, + @Nullable Map additionalParameters) { super(AuthorizationGrantType.DEVICE_CODE, clientPrincipal, additionalParameters); Assert.hasText(deviceCode, "deviceCode cannot be empty"); this.deviceCode = deviceCode; diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java index e4ca833d..d0b0c2e9 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java @@ -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 requestedScopes = authorization.getAttribute(OAuth2ParameterNames.SCOPE); OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService.findById( registeredClient.getId(), principal.getName()); - Set 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 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 deviceCode = authorization.getToken(OAuth2DeviceCode.class); OAuth2Authorization.Token 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 requestedScopes, OAuth2AuthorizationConsent authorizationConsent) { if (authorizationConsent != null && - authorizationConsent.getScopes().containsAll(authorizationRequest.getScopes())) { + authorizationConsent.getScopes().containsAll(requestedScopes)) { return false; } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java index f24b0600..40d40de2 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java @@ -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 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 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; + } + } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java index 121d15a2..dc6d204e 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java @@ -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)) ); } } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java index b9e5d615..765548f3 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java @@ -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 authenticationConverters = new ArrayList<>(); - private Consumer> authenticationConvertersConsumer = (authenticationConverters) -> {}; + private final List deviceAuthorizationRequestConverters = new ArrayList<>(); + private Consumer> deviceAuthorizationRequestConvertersConsumer = (deviceAuthorizationRequestConverters) -> {}; private final List authenticationProviders = new ArrayList<>(); private Consumer> 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> 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 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 createDefaultAuthenticationProviders(HttpSecurity builder) { + private static List createDefaultAuthenticationProviders(HttpSecurity builder) { List authenticationProviders = new ArrayList<>(); OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(builder); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java index c973bddd..bc50f7b4 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java @@ -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 authenticationConverters = new ArrayList<>(); - private Consumer> authenticationConvertersConsumer = (authenticationConverters) -> {}; + private final List deviceVerificationRequestConverters = new ArrayList<>(); + private Consumer> deviceVerificationRequestConvertersConsumer = (deviceVerificationRequestConverters) -> {}; private final List authenticationProviders = new ArrayList<>(); private Consumer> 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> 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 * *
      *
    • {@code client_id} - the client identifier
    • - *
    • {@code scope} - a space-delimited list of scopes present in the authorization request
    • + *
    • {@code scope} - a space-delimited list of scopes present in the device authorization request
    • *
    • {@code state} - a CSRF protection token
    • - *
    • @code code} - the user code
    • + *
    • {@code user_code} - the user code
    • *
    * * In general, the consent page should create a form that submits @@ -181,7 +180,7 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA *
  • It must include the received {@code state} as an HTTP parameter
  • *
  • It must include the list of {@code scope}s the {@code Resource Owner} * consented to as an HTTP parameter
  • - *
  • It must include the user {@code code} as an HTTP parameter
  • + *
  • It must include the received {@code user_code} as an HTTP parameter
  • * * * @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 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 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 createDefaultAuthenticationConverters() { List authenticationConverters = new ArrayList<>(); + authenticationConverters.add(new OAuth2DeviceVerificationAuthenticationConverter()); authenticationConverters.add(new OAuth2DeviceAuthorizationConsentAuthenticationConverter()); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java index 50ae02d7..af156ec0 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java @@ -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> 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); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java index 608aafd5..35752f24 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java @@ -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())) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java index 7dc50231..57af7f7e 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java @@ -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); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java index 16c364b3..2cbb024c 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java @@ -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); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java index c0e31f39..c561260c 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java @@ -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())) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java index 480c7d40..f5473f92 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java @@ -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 deviceAuthorizationHttpResponseConverter = new OAuth2DeviceAuthorizationResponseHttpMessageConverter(); private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - private AuthenticationConverter authenticationConverter; private AuthenticationDetailsSource 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 } } - - diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java index 0b13c264..5e0e5dc1 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java @@ -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"); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java index 91d67a90..e44106dd 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java @@ -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 Section 3.2 Token Endpoint */ public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter { diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java index 7d6f5bfc..d6652376 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java @@ -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 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)); } }); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java index d0fcaa27..8738b21e 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java @@ -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, diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java index 4d3bf3e2..b5248352 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java @@ -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, diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java index 4fb563f2..9b097db8 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java @@ -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; diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java index 3d175e67..6e457252 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java @@ -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\""); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java index 230f3ca3..d1552dfe 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java @@ -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); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java index 1d5f6d9e..b68f077d 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java @@ -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\""); diff --git a/samples/device-client/src/main/java/sample/config/SecurityConfig.java b/samples/device-client/src/main/java/sample/config/SecurityConfig.java index 4f823847..525189e8 100644 --- a/samples/device-client/src/main/java/sample/config/SecurityConfig.java +++ b/samples/device-client/src/main/java/sample/config/SecurityConfig.java @@ -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 { diff --git a/samples/device-grant-authorizationserver/samples-device-grant-authorizationserver.gradle b/samples/device-grant-authorizationserver/samples-device-grant-authorizationserver.gradle index 9b13324e..cc860e04 100644 --- a/samples/device-grant-authorizationserver/samples-device-grant-authorizationserver.gradle +++ b/samples/device-grant-authorizationserver/samples-device-grant-authorizationserver.gradle @@ -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" diff --git a/samples/device-grant-authorizationserver/src/main/java/sample/config/SecurityConfig.java b/samples/device-grant-authorizationserver/src/main/java/sample/config/SecurityConfig.java index ab66f4b8..2ea1b33d 100644 --- a/samples/device-grant-authorizationserver/src/main/java/sample/config/SecurityConfig.java +++ b/samples/device-grant-authorizationserver/src/main/java/sample/config/SecurityConfig.java @@ -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(); } -} \ No newline at end of file +} diff --git a/samples/device-grant-authorizationserver/src/main/java/sample/web/DeviceController.java b/samples/device-grant-authorizationserver/src/main/java/sample/web/DeviceController.java index 3b40e2b2..418f241c 100644 --- a/samples/device-grant-authorizationserver/src/main/java/sample/web/DeviceController.java +++ b/samples/device-grant-authorizationserver/src/main/java/sample/web/DeviceController.java @@ -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. diff --git a/samples/device-grant-authorizationserver/src/main/resources/templates/activate.html b/samples/device-grant-authorizationserver/src/main/resources/templates/activate.html index fa5d76cb..4607e0de 100644 --- a/samples/device-grant-authorizationserver/src/main/resources/templates/activate.html +++ b/samples/device-grant-authorizationserver/src/main/resources/templates/activate.html @@ -12,7 +12,7 @@
    -
    +

    Device Activation

    Enter the activation code to authorize the device.

    Activation Code

    @@ -30,4 +30,4 @@
    - \ No newline at end of file +