From 25b582c822f9871cd78b94e3ab8ad4d90bf9e572 Mon Sep 17 00:00:00 2001 From: Steve Riesenberg Date: Wed, 12 Apr 2023 13:40:40 -0500 Subject: [PATCH 1/2] Add properties to support device grant This commit adds the following properties under spring.security.oauth2.authorizationserver.client.[registration-id]: * endpoint.device-authorization-uri * endpoint.device-verification-uri * token.device-code-time-to-live See gh-34957 --- .../OAuth2AuthorizationServerProperties.java | 39 +++++++++++++++++++ ...h2AuthorizationServerPropertiesMapper.java | 3 ++ ...orizationServerAutoConfigurationTests.java | 4 ++ ...horizationServerPropertiesMapperTests.java | 5 +++ 4 files changed, 51 insertions(+) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java index f9d8cf9df2..65d3c1019f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java @@ -99,6 +99,16 @@ public class OAuth2AuthorizationServerProperties implements InitializingBean { */ private String authorizationUri; + /** + * Authorization Server's OAuth 2.0 Device Authorization Endpoint. + */ + private String deviceAuthorizationUri; + + /** + * Authorization Server's OAuth 2.0 Device Verification Endpoint. + */ + private String deviceVerificationUri; + /** * Authorization Server's OAuth 2.0 Token Endpoint. */ @@ -133,6 +143,22 @@ public class OAuth2AuthorizationServerProperties implements InitializingBean { this.authorizationUri = authorizationUri; } + public String getDeviceAuthorizationUri() { + return this.deviceAuthorizationUri; + } + + public void setDeviceAuthorizationUri(String deviceAuthorizationUri) { + this.deviceAuthorizationUri = deviceAuthorizationUri; + } + + public String getDeviceVerificationUri() { + return this.deviceVerificationUri; + } + + public void setDeviceVerificationUri(String deviceVerificationUri) { + this.deviceVerificationUri = deviceVerificationUri; + } + public String getTokenUri() { return this.tokenUri; } @@ -430,6 +456,11 @@ public class OAuth2AuthorizationServerProperties implements InitializingBean { */ private String accessTokenFormat; + /** + * Time-to-live for a device code. + */ + private Duration deviceCodeTimeToLive; + /** * Whether refresh tokens are reused or a new refresh token is issued when * returning the access token response. @@ -470,6 +501,14 @@ public class OAuth2AuthorizationServerProperties implements InitializingBean { this.accessTokenFormat = accessTokenFormat; } + public Duration getDeviceCodeTimeToLive() { + return this.deviceCodeTimeToLive; + } + + public void setDeviceCodeTimeToLive(Duration deviceCodeTimeToLive) { + this.deviceCodeTimeToLive = deviceCodeTimeToLive; + } + public boolean isReuseRefreshTokens() { return this.reuseRefreshTokens; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapper.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapper.java index 0d7019b37a..308bc0cfb5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapper.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapper.java @@ -53,6 +53,8 @@ final class OAuth2AuthorizationServerPropertiesMapper { AuthorizationServerSettings.Builder builder = AuthorizationServerSettings.builder(); map.from(this.properties::getIssuer).to(builder::issuer); map.from(endpoint::getAuthorizationUri).to(builder::authorizationEndpoint); + map.from(endpoint::getDeviceAuthorizationUri).to(builder::deviceAuthorizationEndpoint); + map.from(endpoint::getDeviceVerificationUri).to(builder::deviceVerificationEndpoint); map.from(endpoint::getTokenUri).to(builder::tokenEndpoint); map.from(endpoint::getJwkSetUri).to(builder::jwkSetEndpoint); map.from(endpoint::getTokenRevocationUri).to(builder::tokenRevocationEndpoint); @@ -111,6 +113,7 @@ final class OAuth2AuthorizationServerPropertiesMapper { map.from(token::getAuthorizationCodeTimeToLive).to(builder::authorizationCodeTimeToLive); map.from(token::getAccessTokenTimeToLive).to(builder::accessTokenTimeToLive); map.from(token::getAccessTokenFormat).as(OAuth2TokenFormat::new).to(builder::accessTokenFormat); + map.from(token::getDeviceCodeTimeToLive).to(builder::deviceCodeTimeToLive); map.from(token::isReuseRefreshTokens).to(builder::reuseRefreshTokens); map.from(token::getRefreshTokenTimeToLive).to(builder::refreshTokenTimeToLive); map.from(token::getIdTokenSignatureAlgorithm) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java index 947962e625..3f2a89e737 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java @@ -124,6 +124,8 @@ class OAuth2AuthorizationServerAutoConfigurationTests { this.contextRunner .withPropertyValues(PROPERTIES_PREFIX + ".issuer=https://example.com", PROPERTIES_PREFIX + ".endpoint.authorization-uri=/authorize", + PROPERTIES_PREFIX + ".endpoint.device-authorization-uri=/device_authorization", + PROPERTIES_PREFIX + ".endpoint.device-verification-uri=/device_verification", PROPERTIES_PREFIX + ".endpoint.token-uri=/token", PROPERTIES_PREFIX + ".endpoint.jwk-set-uri=/jwks", PROPERTIES_PREFIX + ".endpoint.token-revocation-uri=/revoke", PROPERTIES_PREFIX + ".endpoint.token-introspection-uri=/introspect", @@ -134,6 +136,8 @@ class OAuth2AuthorizationServerAutoConfigurationTests { AuthorizationServerSettings settings = context.getBean(AuthorizationServerSettings.class); assertThat(settings.getIssuer()).isEqualTo("https://example.com"); assertThat(settings.getAuthorizationEndpoint()).isEqualTo("/authorize"); + assertThat(settings.getDeviceAuthorizationEndpoint()).isEqualTo("/device_authorization"); + assertThat(settings.getDeviceVerificationEndpoint()).isEqualTo("/device_verification"); assertThat(settings.getTokenEndpoint()).isEqualTo("/token"); assertThat(settings.getJwkSetEndpoint()).isEqualTo("/jwks"); assertThat(settings.getTokenRevocationEndpoint()).isEqualTo("/revoke"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java index 39f7292bbb..9364ae7cde 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java @@ -89,6 +89,7 @@ class OAuth2AuthorizationServerPropertiesMapperTests { token.setAccessTokenFormat("reference"); token.setAccessTokenTimeToLive(Duration.ofSeconds(300)); token.setRefreshTokenTimeToLive(Duration.ofHours(24)); + token.setDeviceCodeTimeToLive(Duration.ofMinutes(30)); token.setReuseRefreshTokens(true); token.setIdTokenSignatureAlgorithm("rs512"); return client; @@ -99,6 +100,8 @@ class OAuth2AuthorizationServerPropertiesMapperTests { this.properties.setIssuer("https://example.com"); OAuth2AuthorizationServerProperties.Endpoint endpoints = this.properties.getEndpoint(); endpoints.setAuthorizationUri("/authorize"); + endpoints.setDeviceAuthorizationUri("/device_authorization"); + endpoints.setDeviceVerificationUri("/device_verification"); endpoints.setTokenUri("/token"); endpoints.setJwkSetUri("/jwks"); endpoints.setTokenRevocationUri("/revoke"); @@ -110,6 +113,8 @@ class OAuth2AuthorizationServerPropertiesMapperTests { AuthorizationServerSettings settings = this.mapper.asAuthorizationServerSettings(); assertThat(settings.getIssuer()).isEqualTo("https://example.com"); assertThat(settings.getAuthorizationEndpoint()).isEqualTo("/authorize"); + assertThat(settings.getDeviceAuthorizationEndpoint()).isEqualTo("/device_authorization"); + assertThat(settings.getDeviceVerificationEndpoint()).isEqualTo("/device_verification"); assertThat(settings.getTokenEndpoint()).isEqualTo("/token"); assertThat(settings.getJwkSetEndpoint()).isEqualTo("/jwks"); assertThat(settings.getTokenRevocationEndpoint()).isEqualTo("/revoke"); From 4eb755870701d3c4d3f44badbab047205895bd2d Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 18 Apr 2023 19:15:35 +0100 Subject: [PATCH 2/2] Polish "Add properties to support device grant" See gh-34957 --- .../servlet/OAuth2AuthorizationServerProperties.java | 2 +- .../OAuth2AuthorizationServerPropertiesMapperTests.java | 1 + .../OAuth2AuthorizationServerPropertiesTests.java | 9 +++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java index 65d3c1019f..b8661d72bf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerProperties.java @@ -459,7 +459,7 @@ public class OAuth2AuthorizationServerProperties implements InitializingBean { /** * Time-to-live for a device code. */ - private Duration deviceCodeTimeToLive; + private Duration deviceCodeTimeToLive = Duration.ofMinutes(5); /** * Whether refresh tokens are reused or a new refresh token is issued when diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java index 9364ae7cde..8fbfb1eb4f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesMapperTests.java @@ -66,6 +66,7 @@ class OAuth2AuthorizationServerPropertiesMapperTests { assertThat(registeredClient.getTokenSettings().getAccessTokenFormat()).isEqualTo(OAuth2TokenFormat.REFERENCE); assertThat(registeredClient.getTokenSettings().getAccessTokenTimeToLive()).isEqualTo(Duration.ofSeconds(300)); assertThat(registeredClient.getTokenSettings().getRefreshTokenTimeToLive()).isEqualTo(Duration.ofHours(24)); + assertThat(registeredClient.getTokenSettings().getDeviceCodeTimeToLive()).isEqualTo(Duration.ofMinutes(30)); assertThat(registeredClient.getTokenSettings().isReuseRefreshTokens()).isEqualTo(true); assertThat(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm()) .isEqualTo(SignatureAlgorithm.RS512); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesTests.java index 28daa4c253..0d4df95b04 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerPropertiesTests.java @@ -18,6 +18,9 @@ package org.springframework.boot.autoconfigure.security.oauth2.server.servlet; import org.junit.jupiter.api.Test; +import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; + +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** @@ -69,4 +72,10 @@ class OAuth2AuthorizationServerPropertiesTests { .withMessage("Authorization grant types must not be empty."); } + @Test + void defaultDeviceCodeTimeToLiveMatchesBuilderDefault() { + assertThat(new OAuth2AuthorizationServerProperties.Client().getToken().getDeviceCodeTimeToLive()) + .isEqualTo(TokenSettings.builder().build().getDeviceCodeTimeToLive()); + } + }