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 ae2190702..27dd4f6e7 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 @@ -46,6 +46,7 @@ import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.security.web.util.RedirectUrlBuilder; import org.springframework.security.web.util.UrlUtils; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -220,9 +221,7 @@ private void sendDeviceAuthorizationResponse(HttpServletRequest request, HttpSer OAuth2UserCode userCode = deviceAuthorizationRequestAuthentication.getUserCode(); // Generate the fully-qualified verification URI - UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder - .fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) - .replacePath(this.verificationUri); + UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(resolveVerificationUri(request)); String verificationUri = uriComponentsBuilder.build().toUriString(); // @formatter:off String verificationUriComplete = uriComponentsBuilder @@ -242,4 +241,17 @@ private void sendDeviceAuthorizationResponse(HttpServletRequest request, HttpSer this.deviceAuthorizationHttpResponseConverter.write(deviceAuthorizationResponse, null, httpResponse); } + private String resolveVerificationUri(HttpServletRequest request) { + if (UrlUtils.isAbsoluteUrl(this.verificationUri)) { + return this.verificationUri; + } + RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder(); + urlBuilder.setScheme(request.getScheme()); + urlBuilder.setServerName(request.getServerName()); + urlBuilder.setPort(request.getServerPort()); + urlBuilder.setContextPath(request.getContextPath()); + urlBuilder.setPathInfo(this.verificationUri); + return urlBuilder.getUrl(); + } + } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java index 9b9e18ed4..8b09ec169 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java @@ -241,6 +241,39 @@ public void doFilterWhenDeviceAuthorizationRequestThenDeviceAuthorizationRespons assertThat(deviceCode.getExpiresAt()).isAfter(deviceCode.getIssuedAt()); } + @Test + public void doFilterWhenDeviceAuthorizationRequestWithContextPathThenDeviceAuthorizationResponse() throws Exception { + Authentication authenticationResult = createAuthentication(); + given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); + + Authentication clientPrincipal = (Authentication) authenticationResult.getPrincipal(); + mockSecurityContext(clientPrincipal); + + MockHttpServletRequest request = createRequest(); + request.setContextPath("/contextPath"); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain filterChain = mock(FilterChain.class); + this.filter.doFilter(request, response, filterChain); + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + + ArgumentCaptor deviceAuthorizationRequestAuthenticationCaptor = ArgumentCaptor + .forClass(OAuth2DeviceAuthorizationRequestAuthenticationToken.class); + verify(this.authenticationManager).authenticate(deviceAuthorizationRequestAuthenticationCaptor.capture()); + verifyNoInteractions(filterChain); + + OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = readDeviceAuthorizationResponse(response); + String verificationUri = ISSUER_URI + "/contextPath" + VERIFICATION_URI; + assertThat(deviceAuthorizationResponse.getVerificationUri()).isEqualTo(verificationUri); + assertThat(deviceAuthorizationResponse.getVerificationUriComplete()) + .isEqualTo("%s?%s=%s".formatted(verificationUri, OAuth2ParameterNames.USER_CODE, USER_CODE)); + OAuth2DeviceCode deviceCode = deviceAuthorizationResponse.getDeviceCode(); + assertThat(deviceCode.getTokenValue()).isEqualTo(DEVICE_CODE); + assertThat(deviceCode.getExpiresAt()).isAfter(deviceCode.getIssuedAt()); + OAuth2UserCode userCode = deviceAuthorizationResponse.getUserCode(); + assertThat(userCode.getTokenValue()).isEqualTo(USER_CODE); + assertThat(deviceCode.getExpiresAt()).isAfter(deviceCode.getIssuedAt()); + } + @Test public void doFilterWhenInvalidRequestErrorThenBadRequest() throws Exception { AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class);