Skip to content

Commit 50f0d04

Browse files
committed
feat(config): disable device_code grant by default
Before: Device Authorization Grant endpoints were always enabled, exposing an attack surface that most deployments do not need. After: The grant is now opt-in. A new deviceGrantEnabled flag on AuthorizationServerSettings defaults to false; filters and providers are registered only when it is explicitly true. This change strengthens the framework’s secure-by-default posture and aligns with the discussion in gh-1709. Closes gh-1709 Signed-off-by: renechoi [email protected] Signed-off-by: renechoi <[email protected]>
1 parent 51904bb commit 50f0d04

File tree

7 files changed

+111
-38
lines changed

7 files changed

+111
-38
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,10 @@ public void init(HttpSecurity httpSecurity) throws Exception {
365365
List<RequestMatcher> requestMatchers = new ArrayList<>();
366366
this.configurers.values().forEach((configurer) -> {
367367
configurer.init(httpSecurity);
368-
requestMatchers.add(configurer.getRequestMatcher());
368+
RequestMatcher matcher = configurer.getRequestMatcher();
369+
if (matcher != null) {
370+
requestMatchers.add(matcher);
371+
}
369372
});
370373
String jwkSetEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
371374
? OAuth2ConfigurerUtils.withMultipleIssuersPattern(authorizationServerSettings.getJwkSetEndpoint())
@@ -380,7 +383,10 @@ public void init(HttpSecurity httpSecurity) throws Exception {
380383
preferredMatchers.add(getRequestMatcher(OAuth2TokenEndpointConfigurer.class));
381384
preferredMatchers.add(getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class));
382385
preferredMatchers.add(getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class));
383-
preferredMatchers.add(getRequestMatcher(OAuth2DeviceAuthorizationEndpointConfigurer.class));
386+
RequestMatcher deviceAuthMatcher = getRequestMatcher(OAuth2DeviceAuthorizationEndpointConfigurer.class);
387+
if (deviceAuthMatcher != null) {
388+
preferredMatchers.add(deviceAuthMatcher);
389+
}
384390
RequestMatcher preferredMatcher = getRequestMatcher(
385391
OAuth2PushedAuthorizationRequestEndpointConfigurer.class);
386392
if (preferredMatcher != null) {

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,19 +192,23 @@ void init(HttpSecurity httpSecurity) {
192192
? OAuth2ConfigurerUtils
193193
.withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint())
194194
: authorizationServerSettings.getTokenRevocationEndpoint();
195-
String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
196-
? OAuth2ConfigurerUtils
197-
.withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint())
198-
: authorizationServerSettings.getDeviceAuthorizationEndpoint();
199195
String pushedAuthorizationRequestEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
200196
? OAuth2ConfigurerUtils
201-
.withMultipleIssuersPattern(authorizationServerSettings.getPushedAuthorizationRequestEndpoint())
197+
.withMultipleIssuersPattern(authorizationServerSettings.getPushedAuthorizationRequestEndpoint())
202198
: authorizationServerSettings.getPushedAuthorizationRequestEndpoint();
203-
this.requestMatcher = new OrRequestMatcher(new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name()),
204-
new AntPathRequestMatcher(tokenIntrospectionEndpointUri, HttpMethod.POST.name()),
205-
new AntPathRequestMatcher(tokenRevocationEndpointUri, HttpMethod.POST.name()),
206-
new AntPathRequestMatcher(deviceAuthorizationEndpointUri, HttpMethod.POST.name()),
207-
new AntPathRequestMatcher(pushedAuthorizationRequestEndpointUri, HttpMethod.POST.name()));
199+
List<RequestMatcher> requestMatchers = new ArrayList<>();
200+
requestMatchers.add(new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name()));
201+
requestMatchers.add(new AntPathRequestMatcher(tokenIntrospectionEndpointUri, HttpMethod.POST.name()));
202+
requestMatchers.add(new AntPathRequestMatcher(tokenRevocationEndpointUri, HttpMethod.POST.name()));
203+
if (authorizationServerSettings.isDeviceGrantEnabled()) {
204+
String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
205+
? OAuth2ConfigurerUtils
206+
.withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint())
207+
: authorizationServerSettings.getDeviceAuthorizationEndpoint();
208+
requestMatchers.add(new AntPathRequestMatcher(deviceAuthorizationEndpointUri, HttpMethod.POST.name()));
209+
}
210+
requestMatchers.add(new AntPathRequestMatcher(pushedAuthorizationRequestEndpointUri, HttpMethod.POST.name()));
211+
this.requestMatcher = new OrRequestMatcher(requestMatchers);
208212
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
209213
if (!this.authenticationProviders.isEmpty()) {
210214
authenticationProviders.addAll(0, this.authenticationProviders);

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,13 @@ public OAuth2DeviceAuthorizationEndpointConfigurer verificationUri(String verifi
197197
@Override
198198
public void init(HttpSecurity builder) {
199199
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
200-
.getAuthorizationServerSettings(builder);
200+
.getAuthorizationServerSettings(builder);
201+
if (!authorizationServerSettings.isDeviceGrantEnabled()) {
202+
return;
203+
}
201204
String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
202205
? OAuth2ConfigurerUtils
203-
.withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint())
206+
.withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint())
204207
: authorizationServerSettings.getDeviceAuthorizationEndpoint();
205208
this.requestMatcher = new AntPathRequestMatcher(deviceAuthorizationEndpointUri, HttpMethod.POST.name());
206209

@@ -217,7 +220,11 @@ public void init(HttpSecurity builder) {
217220
public void configure(HttpSecurity builder) {
218221
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
219222
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
220-
.getAuthorizationServerSettings(builder);
223+
.getAuthorizationServerSettings(builder);
224+
225+
if (!authorizationServerSettings.isDeviceGrantEnabled()) {
226+
return;
227+
}
221228

222229
String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
223230
? OAuth2ConfigurerUtils

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,13 @@ public OAuth2DeviceVerificationEndpointConfigurer consentPage(String consentPage
232232
@Override
233233
public void init(HttpSecurity builder) {
234234
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
235-
.getAuthorizationServerSettings(builder);
235+
.getAuthorizationServerSettings(builder);
236+
if (!authorizationServerSettings.isDeviceGrantEnabled()) {
237+
return;
238+
}
236239
String deviceVerificationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
237240
? OAuth2ConfigurerUtils
238-
.withMultipleIssuersPattern(authorizationServerSettings.getDeviceVerificationEndpoint())
241+
.withMultipleIssuersPattern(authorizationServerSettings.getDeviceVerificationEndpoint())
239242
: authorizationServerSettings.getDeviceVerificationEndpoint();
240243
this.requestMatcher = new OrRequestMatcher(
241244
new AntPathRequestMatcher(deviceVerificationEndpointUri, HttpMethod.GET.name()),
@@ -254,7 +257,11 @@ public void init(HttpSecurity builder) {
254257
public void configure(HttpSecurity builder) {
255258
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
256259
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
257-
.getAuthorizationServerSettings(builder);
260+
.getAuthorizationServerSettings(builder);
261+
262+
if (!authorizationServerSettings.isDeviceGrantEnabled()) {
263+
return;
264+
}
258265

259266
String deviceVerificationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
260267
? OAuth2ConfigurerUtils

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,21 +163,31 @@ public String getOidcLogoutEndpoint() {
163163
return getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_LOGOUT_ENDPOINT);
164164
}
165165

166+
/**
167+
* Returns {@code true} if the OAuth 2.0 Device Authorization Grant is enabled.
168+
* The default is {@code false}.
169+
* @return {@code true} if the Device Authorization Grant is enabled, {@code false} otherwise
170+
*/
171+
public boolean isDeviceGrantEnabled() {
172+
return getSetting(ConfigurationSettingNames.AuthorizationServer.DEVICE_GRANT_ENABLED);
173+
}
174+
166175
/**
167176
* Constructs a new {@link Builder} with the default settings.
168177
* @return the {@link Builder}
169178
*/
170179
public static Builder builder() {
171180
return new Builder().multipleIssuersAllowed(false)
172-
.authorizationEndpoint("/oauth2/authorize")
173-
.pushedAuthorizationRequestEndpoint("/oauth2/par")
174-
.deviceAuthorizationEndpoint("/oauth2/device_authorization")
175-
.deviceVerificationEndpoint("/oauth2/device_verification")
176-
.tokenEndpoint("/oauth2/token")
177-
.jwkSetEndpoint("/oauth2/jwks")
178-
.tokenRevocationEndpoint("/oauth2/revoke")
179-
.tokenIntrospectionEndpoint("/oauth2/introspect")
180-
.oidcClientRegistrationEndpoint("/connect/register")
181+
.authorizationEndpoint("/oauth2/authorize")
182+
.pushedAuthorizationRequestEndpoint("/oauth2/par")
183+
.deviceAuthorizationEndpoint("/oauth2/device_authorization")
184+
.deviceVerificationEndpoint("/oauth2/device_verification")
185+
.deviceGrantEnabled(false)
186+
.tokenEndpoint("/oauth2/token")
187+
.jwkSetEndpoint("/oauth2/jwks")
188+
.tokenRevocationEndpoint("/oauth2/revoke")
189+
.tokenIntrospectionEndpoint("/oauth2/introspect")
190+
.oidcClientRegistrationEndpoint("/connect/register")
181191
.oidcUserInfoEndpoint("/userinfo")
182192
.oidcLogoutEndpoint("/connect/logout");
183193
}
@@ -281,6 +291,16 @@ public Builder deviceVerificationEndpoint(String deviceVerificationEndpoint) {
281291
deviceVerificationEndpoint);
282292
}
283293

294+
/**
295+
* Enables the OAuth 2.0 Device Authorization Grant.
296+
* @param deviceGrantEnabled {@code true} to enable the Device Authorization Grant
297+
* @return the {@link Builder} for further configuration
298+
*/
299+
public Builder deviceGrantEnabled(boolean deviceGrantEnabled) {
300+
return setting(ConfigurationSettingNames.AuthorizationServer.DEVICE_GRANT_ENABLED,
301+
deviceGrantEnabled);
302+
}
303+
284304
/**
285305
* Sets the OAuth 2.0 Token endpoint.
286306
* @param tokenEndpoint the Token endpoint

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,15 @@ public static final class AuthorizationServer {
129129
* Set the OAuth 2.0 Device Verification endpoint.
130130
*/
131131
public static final String DEVICE_VERIFICATION_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE
132-
.concat("device-verification-endpoint");
132+
.concat("device-verification-endpoint");
133+
134+
/**
135+
* Set to {@code true} if the OAuth 2.0 Device Authorization Grant is enabled.
136+
* The default is {@code false}.
137+
*/
138+
public static final String DEVICE_GRANT_ENABLED = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE
139+
.concat("device-grant-enabled");
140+
133141

134142
/**
135143
* Set the OAuth 2.0 Token endpoint.

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceCodeGrantTests.java

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,17 @@ public static void destroy() {
176176
}
177177

178178
@Test
179-
public void requestWhenDeviceAuthorizationRequestNotAuthenticatedThenUnauthorized() throws Exception {
179+
public void requestWhenDeviceAuthorizationEndpointDisabledThenNotFound() throws Exception {
180180
this.spring.register(AuthorizationServerConfiguration.class).autowire();
181181

182+
this.mvc.perform(post(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI))
183+
.andExpect(status().isNotFound());
184+
}
185+
186+
@Test
187+
public void requestWhenDeviceAuthorizationRequestNotAuthenticatedThenUnauthorized() throws Exception {
188+
this.spring.register(AuthorizationServerConfigurationWithDeviceGrant.class).autowire();
189+
182190
// @formatter:off
183191
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
184192
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
@@ -200,7 +208,7 @@ public void requestWhenDeviceAuthorizationRequestNotAuthenticatedThenUnauthorize
200208

201209
@Test
202210
public void requestWhenRegisteredClientMissingThenUnauthorized() throws Exception {
203-
this.spring.register(AuthorizationServerConfiguration.class).autowire();
211+
this.spring.register(AuthorizationServerConfigurationWithDeviceGrant.class).autowire();
204212

205213
// @formatter:off
206214
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
@@ -272,7 +280,7 @@ public void requestWhenDeviceAuthorizationRequestValidThenReturnDeviceAuthorizat
272280

273281
@Test
274282
public void requestWhenDeviceVerificationRequestUnauthenticatedThenUnauthorized() throws Exception {
275-
this.spring.register(AuthorizationServerConfiguration.class).autowire();
283+
this.spring.register(AuthorizationServerConfigurationWithDeviceGrant.class).autowire();
276284

277285
// @formatter:off
278286
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
@@ -357,7 +365,7 @@ public void requestWhenDeviceVerificationRequestValidThenDisplaysConsentPage() t
357365

358366
@Test
359367
public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenBadRequest() throws Exception {
360-
this.spring.register(AuthorizationServerConfiguration.class).autowire();
368+
this.spring.register(AuthorizationServerConfigurationWithDeviceGrant.class).autowire();
361369

362370
// @formatter:off
363371
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
@@ -395,7 +403,7 @@ public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenBadRe
395403

396404
@Test
397405
public void requestWhenDeviceAuthorizationConsentRequestValidThenRedirectsToSuccessPage() throws Exception {
398-
this.spring.register(AuthorizationServerConfiguration.class).autowire();
406+
this.spring.register(AuthorizationServerConfigurationWithDeviceGrant.class).autowire();
399407

400408
// @formatter:off
401409
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
@@ -445,7 +453,7 @@ public void requestWhenDeviceAuthorizationConsentRequestValidThenRedirectsToSucc
445453

446454
@Test
447455
public void requestWhenAccessTokenRequestUnauthenticatedThenUnauthorized() throws Exception {
448-
this.spring.register(AuthorizationServerConfiguration.class).autowire();
456+
this.spring.register(AuthorizationServerConfigurationWithDeviceGrant.class).autowire();
449457

450458
// @formatter:off
451459
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
@@ -481,7 +489,7 @@ public void requestWhenAccessTokenRequestUnauthenticatedThenUnauthorized() throw
481489

482490
@Test
483491
public void requestWhenAccessTokenRequestValidThenReturnAccessTokenResponse() throws Exception {
484-
this.spring.register(AuthorizationServerConfiguration.class).autowire();
492+
this.spring.register(AuthorizationServerConfigurationWithDeviceGrant.class).autowire();
485493

486494
// @formatter:off
487495
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
@@ -553,7 +561,7 @@ public void requestWhenAccessTokenRequestValidThenReturnAccessTokenResponse() th
553561

554562
@Test
555563
public void requestWhenAccessTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception {
556-
this.spring.register(AuthorizationServerConfiguration.class).autowire();
564+
this.spring.register(AuthorizationServerConfigurationWithDeviceGrant.class).autowire();
557565

558566
// @formatter:off
559567
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
@@ -683,11 +691,24 @@ PasswordEncoder passwordEncoder() {
683691

684692
@EnableWebSecurity
685693
@Import(OAuth2AuthorizationServerConfiguration.class)
686-
static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration {
694+
static class AuthorizationServerConfigurationWithDeviceGrant extends AuthorizationServerConfiguration {
695+
696+
@Bean
697+
AuthorizationServerSettings authorizationServerSettings() {
698+
return AuthorizationServerSettings.builder().deviceGrantEnabled(true).build();
699+
}
700+
701+
}
702+
703+
@EnableWebSecurity
704+
@Import(OAuth2AuthorizationServerConfiguration.class)
705+
static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfigurationWithDeviceGrant {
687706

688707
@Bean
689708
AuthorizationServerSettings authorizationServerSettings() {
690-
return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build();
709+
return AuthorizationServerSettings.builder().multipleIssuersAllowed(true)
710+
.deviceGrantEnabled(true)
711+
.build();
691712
}
692713

693714
}

0 commit comments

Comments
 (0)