Add integration tests for device grant
Issue gh-1116
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
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;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2DeviceCode;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.security.oauth2.core.OAuth2UserCode;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizationResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2DeviceAuthorizationResponseHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.jose.TestJwks;
|
||||
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
|
||||
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.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.server.authorization.test.SpringTestContext;
|
||||
import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Integration tests for OAuth 2.0 Device Grant.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class OAuth2DeviceCodeGrantTests {
|
||||
private static final String DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI = "/oauth2/device_authorization";
|
||||
private static final String DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI = "/oauth2/device_verification";
|
||||
private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token";
|
||||
private static final OAuth2TokenType DEVICE_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.DEVICE_CODE);
|
||||
private static final String USER_CODE = "ABCD-EFGH";
|
||||
private static final String STATE = "123";
|
||||
private static final String DEVICE_CODE = "abc-XYZ";
|
||||
|
||||
private static EmbeddedDatabase db;
|
||||
|
||||
private static JWKSource<SecurityContext> jwkSource;
|
||||
|
||||
private static final HttpMessageConverter<OAuth2DeviceAuthorizationResponse> deviceAuthorizationResponseHttpMessageConverter =
|
||||
new OAuth2DeviceAuthorizationResponseHttpMessageConverter();
|
||||
|
||||
private static final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenResponseHttpMessageConverter =
|
||||
new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext();
|
||||
|
||||
@Autowired
|
||||
private MockMvc mvc;
|
||||
|
||||
@Autowired
|
||||
private JdbcOperations jdbcOperations;
|
||||
|
||||
@Autowired
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizationConsentService authorizationConsentService;
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
|
||||
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
|
||||
// @formatter:off
|
||||
db = new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(EmbeddedDatabaseType.HSQL)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
|
||||
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
|
||||
.addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
jdbcOperations.update("truncate table oauth2_authorization");
|
||||
jdbcOperations.update("truncate table oauth2_authorization_consent");
|
||||
jdbcOperations.update("truncate table oauth2_registered_client");
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void destroy() {
|
||||
db.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenDeviceAuthorizationRequestNotAuthenticatedThenUnauthorized() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
parameters.set(OAuth2ParameterNames.SCOPE,
|
||||
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
|
||||
|
||||
// @formatter:off
|
||||
this.mvc.perform(post(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI)
|
||||
.params(parameters))
|
||||
.andExpect(status().isUnauthorized());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenRegisteredClientMissingThenUnauthorized() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
parameters.set(OAuth2ParameterNames.SCOPE,
|
||||
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
|
||||
|
||||
// @formatter:off
|
||||
this.mvc.perform(post(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI)
|
||||
.params(parameters)
|
||||
.headers(withClientAuth(registeredClient)))
|
||||
.andExpect(status().isUnauthorized());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenDeviceAuthorizationRequestValidThenReturnDeviceAuthorizationResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
parameters.set(OAuth2ParameterNames.SCOPE,
|
||||
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
|
||||
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI)
|
||||
.params(parameters)
|
||||
.headers(withClientAuth(registeredClient)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.device_code").isNotEmpty())
|
||||
.andExpect(jsonPath("$.user_code").isNotEmpty())
|
||||
.andExpect(jsonPath("$.expires_in").isNumber())
|
||||
.andExpect(jsonPath("$.verification_uri").isNotEmpty())
|
||||
.andExpect(jsonPath("$.verification_uri_complete").isNotEmpty())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
|
||||
MockHttpServletResponse servletResponse = mvcResult.getResponse();
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(),
|
||||
HttpStatus.OK);
|
||||
OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse =
|
||||
deviceAuthorizationResponseHttpMessageConverter.read(OAuth2DeviceAuthorizationResponse.class,
|
||||
httpResponse);
|
||||
String userCode = deviceAuthorizationResponse.getUserCode().getTokenValue();
|
||||
assertThat(userCode).matches("[A-Z]{4}-[A-Z]{4}");
|
||||
assertThat(deviceAuthorizationResponse.getVerificationUri())
|
||||
.isEqualTo("http://localhost/oauth2/device_verification");
|
||||
assertThat(deviceAuthorizationResponse.getVerificationUriComplete())
|
||||
.isEqualTo("http://localhost/oauth2/device_verification?user_code=" + userCode);
|
||||
|
||||
String deviceCode = deviceAuthorizationResponse.getDeviceCode().getTokenValue();
|
||||
OAuth2Authorization authorization = this.authorizationService.findByToken(deviceCode, DEVICE_CODE_TOKEN_TYPE);
|
||||
assertThat(authorization.getToken(OAuth2DeviceCode.class)).isNotNull();
|
||||
assertThat(authorization.getToken(OAuth2UserCode.class)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenDeviceVerificationRequestUnauthenticatedThenUnauthorized() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plusSeconds(300);
|
||||
// @formatter:off
|
||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(registeredClient.getClientId())
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt))
|
||||
.token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt))
|
||||
.attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes())
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE);
|
||||
|
||||
// @formatter:off
|
||||
this.mvc.perform(get(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI)
|
||||
.params(parameters))
|
||||
.andExpect(status().isUnauthorized());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenDeviceVerificationRequestValidThenDisplaysConsentPage() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plusSeconds(300);
|
||||
// @formatter:off
|
||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(registeredClient.getClientId())
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt))
|
||||
.token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt))
|
||||
.attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes())
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE);
|
||||
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mvc.perform(get(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI)
|
||||
.params(parameters)
|
||||
.with(user("user")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
|
||||
String responseHtml = mvcResult.getResponse().getContentAsString();
|
||||
assertThat(responseHtml).contains("Consent required");
|
||||
|
||||
OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId());
|
||||
assertThat(updatedAuthorization.getPrincipalName()).isEqualTo("user");
|
||||
assertThat(updatedAuthorization).isNotNull();
|
||||
// @formatter:off
|
||||
assertThat(updatedAuthorization.getToken(OAuth2UserCode.class))
|
||||
.extracting(isInvalidated())
|
||||
.isEqualTo(false);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenBadRequest() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plusSeconds(300);
|
||||
// @formatter:off
|
||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName("user")
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt))
|
||||
.token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt))
|
||||
.attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes())
|
||||
.attribute(OAuth2ParameterNames.STATE, STATE)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE);
|
||||
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
parameters.set(OAuth2ParameterNames.SCOPE, registeredClient.getScopes().iterator().next());
|
||||
parameters.set(OAuth2ParameterNames.STATE, STATE);
|
||||
|
||||
// @formatter:off
|
||||
this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI)
|
||||
.params(parameters))
|
||||
.andExpect(status().isBadRequest());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenDeviceAuthorizationConsentRequestValidThenRedirectsToSuccessPage() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plusSeconds(300);
|
||||
// @formatter:off
|
||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName("user")
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt))
|
||||
.token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt))
|
||||
.attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes())
|
||||
.attribute(OAuth2ParameterNames.STATE, STATE)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE);
|
||||
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
parameters.set(OAuth2ParameterNames.SCOPE, registeredClient.getScopes().iterator().next());
|
||||
parameters.set(OAuth2ParameterNames.STATE, STATE);
|
||||
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI)
|
||||
.params(parameters)
|
||||
.with(user("user")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
|
||||
assertThat(mvcResult.getResponse().getHeader(HttpHeaders.LOCATION)).isEqualTo("/?success");
|
||||
|
||||
OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId());
|
||||
assertThat(updatedAuthorization).isNotNull();
|
||||
// @formatter:off
|
||||
assertThat(updatedAuthorization.getToken(OAuth2UserCode.class))
|
||||
.extracting(isInvalidated())
|
||||
.isEqualTo(true);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenAccessTokenRequestUnauthenticatedThenUnauthorized() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plusSeconds(300);
|
||||
// @formatter:off
|
||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(registeredClient.getClientId())
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt))
|
||||
.token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt), withInvalidated())
|
||||
.authorizedScopes(registeredClient.getScopes())
|
||||
.attribute(Principal.class.getName(), new UsernamePasswordAuthenticationToken("user", null))
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue());
|
||||
parameters.set(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE);
|
||||
|
||||
// @formatter:off
|
||||
this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
|
||||
.params(parameters))
|
||||
.andExpect(status().isUnauthorized());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenAccessTokenRequestValidThenReturnAccessTokenResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
// @formatter:off
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plusSeconds(300);
|
||||
// @formatter:off
|
||||
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(registeredClient.getClientId())
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt))
|
||||
.token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt), withInvalidated())
|
||||
.authorizedScopes(registeredClient.getScopes())
|
||||
.attribute(Principal.class.getName(), new UsernamePasswordAuthenticationToken("user", null))
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationConsent authorizationConsent =
|
||||
OAuth2AuthorizationConsent.withId(registeredClient.getClientId(), "user")
|
||||
.scope(registeredClient.getScopes().iterator().next())
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.authorizationConsentService.save(authorizationConsent);
|
||||
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue());
|
||||
parameters.set(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE);
|
||||
|
||||
// @formatter:off
|
||||
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
|
||||
.params(parameters)
|
||||
.headers(withClientAuth(registeredClient)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.refresh_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.expires_in").isNumber())
|
||||
.andExpect(jsonPath("$.scope").isNotEmpty())
|
||||
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
|
||||
OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId());
|
||||
assertThat(updatedAuthorization).isNotNull();
|
||||
assertThat(updatedAuthorization.getAccessToken()).isNotNull();
|
||||
assertThat(updatedAuthorization.getRefreshToken()).isNotNull();
|
||||
// @formatter:off
|
||||
assertThat(updatedAuthorization.getToken(OAuth2DeviceCode.class))
|
||||
.extracting(isInvalidated())
|
||||
.isEqualTo(true);
|
||||
// @formatter:on
|
||||
|
||||
MockHttpServletResponse servletResponse = mvcResult.getResponse();
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(),
|
||||
HttpStatus.OK);
|
||||
OAuth2AccessTokenResponse accessTokenResponse =
|
||||
accessTokenResponseHttpMessageConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
|
||||
|
||||
String accessToken = accessTokenResponse.getAccessToken().getTokenValue();
|
||||
OAuth2Authorization accessTokenAuthorization = this.authorizationService.findByToken(accessToken,
|
||||
OAuth2TokenType.ACCESS_TOKEN);
|
||||
assertThat(accessTokenAuthorization).isEqualTo(updatedAuthorization);
|
||||
}
|
||||
|
||||
private static HttpHeaders withClientAuth(RegisteredClient registeredClient) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret());
|
||||
return headers;
|
||||
}
|
||||
|
||||
private static Consumer<Map<String, Object>> withInvalidated() {
|
||||
return (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true);
|
||||
}
|
||||
|
||||
private static Function<OAuth2Authorization.Token<? extends OAuth2Token>, Boolean> isInvalidated() {
|
||||
return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(OAuth2AuthorizationServerConfiguration.class)
|
||||
static class AuthorizationServerConfiguration {
|
||||
|
||||
@Bean
|
||||
RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) {
|
||||
return new JdbcRegisteredClientRepository(jdbcOperations);
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
|
||||
RegisteredClientRepository registeredClientRepository) {
|
||||
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations,
|
||||
RegisteredClientRepository registeredClientRepository) {
|
||||
return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
JdbcOperations jdbcOperations() {
|
||||
return new JdbcTemplate(db);
|
||||
}
|
||||
|
||||
@Bean
|
||||
JWKSource<SecurityContext> jwkSource() {
|
||||
return jwkSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
PasswordEncoder passwordEncoder() {
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user