Add tests and update examples in docs
Closes gh-1156
This commit is contained in:
@@ -116,6 +116,7 @@ public class SecurityConfig {
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
|
||||
.redirectUri("http://127.0.0.1:8080/authorized")
|
||||
.scope(OidcScopes.OPENID)
|
||||
|
||||
@@ -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.
|
||||
@@ -70,6 +70,20 @@ public class Authorization {
|
||||
@Column(length = 2000)
|
||||
private String oidcIdTokenClaims;
|
||||
|
||||
@Column(length = 4000)
|
||||
private String userCodeValue;
|
||||
private Instant userCodeIssuedAt;
|
||||
private Instant userCodeExpiresAt;
|
||||
@Column(length = 2000)
|
||||
private String userCodeMetadata;
|
||||
|
||||
@Column(length = 4000)
|
||||
private String deviceCodeValue;
|
||||
private Instant deviceCodeIssuedAt;
|
||||
private Instant deviceCodeExpiresAt;
|
||||
@Column(length = 2000)
|
||||
private String deviceCodeMetadata;
|
||||
|
||||
// @fold:on
|
||||
public String getId() {
|
||||
return id;
|
||||
@@ -278,5 +292,69 @@ public class Authorization {
|
||||
public void setOidcIdTokenClaims(String idTokenClaims) {
|
||||
this.oidcIdTokenClaims = idTokenClaims;
|
||||
}
|
||||
|
||||
public String getUserCodeValue() {
|
||||
return this.userCodeValue;
|
||||
}
|
||||
|
||||
public void setUserCodeValue(String userCodeValue) {
|
||||
this.userCodeValue = userCodeValue;
|
||||
}
|
||||
|
||||
public Instant getUserCodeIssuedAt() {
|
||||
return this.userCodeIssuedAt;
|
||||
}
|
||||
|
||||
public void setUserCodeIssuedAt(Instant userCodeIssuedAt) {
|
||||
this.userCodeIssuedAt = userCodeIssuedAt;
|
||||
}
|
||||
|
||||
public Instant getUserCodeExpiresAt() {
|
||||
return this.userCodeExpiresAt;
|
||||
}
|
||||
|
||||
public void setUserCodeExpiresAt(Instant userCodeExpiresAt) {
|
||||
this.userCodeExpiresAt = userCodeExpiresAt;
|
||||
}
|
||||
|
||||
public String getUserCodeMetadata() {
|
||||
return this.userCodeMetadata;
|
||||
}
|
||||
|
||||
public void setUserCodeMetadata(String userCodeMetadata) {
|
||||
this.userCodeMetadata = userCodeMetadata;
|
||||
}
|
||||
|
||||
public String getDeviceCodeValue() {
|
||||
return this.deviceCodeValue;
|
||||
}
|
||||
|
||||
public void setDeviceCodeValue(String deviceCodeValue) {
|
||||
this.deviceCodeValue = deviceCodeValue;
|
||||
}
|
||||
|
||||
public Instant getDeviceCodeIssuedAt() {
|
||||
return this.deviceCodeIssuedAt;
|
||||
}
|
||||
|
||||
public void setDeviceCodeIssuedAt(Instant deviceCodeIssuedAt) {
|
||||
this.deviceCodeIssuedAt = deviceCodeIssuedAt;
|
||||
}
|
||||
|
||||
public Instant getDeviceCodeExpiresAt() {
|
||||
return this.deviceCodeExpiresAt;
|
||||
}
|
||||
|
||||
public void setDeviceCodeExpiresAt(Instant deviceCodeExpiresAt) {
|
||||
this.deviceCodeExpiresAt = deviceCodeExpiresAt;
|
||||
}
|
||||
|
||||
public String getDeviceCodeMetadata() {
|
||||
return this.deviceCodeMetadata;
|
||||
}
|
||||
|
||||
public void setDeviceCodeMetadata(String deviceCodeMetadata) {
|
||||
this.deviceCodeMetadata = deviceCodeMetadata;
|
||||
}
|
||||
// @fold:off
|
||||
}
|
||||
|
||||
@@ -31,11 +31,15 @@ public interface AuthorizationRepository extends JpaRepository<Authorization, St
|
||||
Optional<Authorization> findByAccessTokenValue(String accessToken);
|
||||
Optional<Authorization> findByRefreshTokenValue(String refreshToken);
|
||||
Optional<Authorization> findByOidcIdTokenValue(String idToken);
|
||||
Optional<Authorization> findByUserCodeValue(String userCode);
|
||||
Optional<Authorization> findByDeviceCodeValue(String deviceCode);
|
||||
@Query("select a from Authorization a where a.state = :token" +
|
||||
" or a.authorizationCodeValue = :token" +
|
||||
" or a.accessTokenValue = :token" +
|
||||
" or a.refreshTokenValue = :token" +
|
||||
" or a.oidcIdTokenValue = :token"
|
||||
" or a.oidcIdTokenValue = :token" +
|
||||
" or a.userCodeValue = :token" +
|
||||
" or a.deviceCodeValue = :token"
|
||||
)
|
||||
Optional<Authorization> findByStateOrAuthorizationCodeValueOrAccessTokenValueOrRefreshTokenValueOrOidcIdTokenValue(@Param("token") String token);
|
||||
Optional<Authorization> findByStateOrAuthorizationCodeValueOrAccessTokenValueOrRefreshTokenValueOrOidcIdTokenValueOrUserCodeValueOrDeviceCodeValue(@Param("token") String token);
|
||||
}
|
||||
|
||||
@@ -31,8 +31,10 @@ import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2DeviceCode;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.security.oauth2.core.OAuth2UserCode;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
@@ -89,7 +91,7 @@ public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService
|
||||
|
||||
Optional<Authorization> result;
|
||||
if (tokenType == null) {
|
||||
result = this.authorizationRepository.findByStateOrAuthorizationCodeValueOrAccessTokenValueOrRefreshTokenValueOrOidcIdTokenValue(token);
|
||||
result = this.authorizationRepository.findByStateOrAuthorizationCodeValueOrAccessTokenValueOrRefreshTokenValueOrOidcIdTokenValueOrUserCodeValueOrDeviceCodeValue(token);
|
||||
} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
|
||||
result = this.authorizationRepository.findByState(token);
|
||||
} else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
|
||||
@@ -100,6 +102,10 @@ public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService
|
||||
result = this.authorizationRepository.findByRefreshTokenValue(token);
|
||||
} else if (OidcParameterNames.ID_TOKEN.equals(tokenType.getValue())) {
|
||||
result = this.authorizationRepository.findByOidcIdTokenValue(token);
|
||||
} else if (OAuth2ParameterNames.USER_CODE.equals(tokenType.getValue())) {
|
||||
result = this.authorizationRepository.findByUserCodeValue(token);
|
||||
} else if (OAuth2ParameterNames.DEVICE_CODE.equals(tokenType.getValue())) {
|
||||
result = this.authorizationRepository.findByDeviceCodeValue(token);
|
||||
} else {
|
||||
result = Optional.empty();
|
||||
}
|
||||
@@ -159,6 +165,22 @@ public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService
|
||||
builder.token(idToken, metadata -> metadata.putAll(parseMap(entity.getOidcIdTokenMetadata())));
|
||||
}
|
||||
|
||||
if (entity.getUserCodeValue() != null) {
|
||||
OAuth2UserCode userCode = new OAuth2UserCode(
|
||||
entity.getUserCodeValue(),
|
||||
entity.getUserCodeIssuedAt(),
|
||||
entity.getUserCodeExpiresAt());
|
||||
builder.token(userCode, metadata -> metadata.putAll(parseMap(entity.getUserCodeMetadata())));
|
||||
}
|
||||
|
||||
if (entity.getUserCodeValue() != null) {
|
||||
OAuth2DeviceCode deviceCode = new OAuth2DeviceCode(
|
||||
entity.getDeviceCodeValue(),
|
||||
entity.getDeviceCodeIssuedAt(),
|
||||
entity.getDeviceCodeExpiresAt());
|
||||
builder.token(deviceCode, metadata -> metadata.putAll(parseMap(entity.getDeviceCodeMetadata())));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -218,6 +240,26 @@ public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService
|
||||
entity.setOidcIdTokenClaims(writeMap(oidcIdToken.getClaims()));
|
||||
}
|
||||
|
||||
OAuth2Authorization.Token<OAuth2UserCode> userCode =
|
||||
authorization.getToken(OAuth2UserCode.class);
|
||||
setTokenValues(
|
||||
userCode,
|
||||
entity::setUserCodeValue,
|
||||
entity::setUserCodeIssuedAt,
|
||||
entity::setUserCodeExpiresAt,
|
||||
entity::setUserCodeMetadata
|
||||
);
|
||||
|
||||
OAuth2Authorization.Token<OAuth2DeviceCode> deviceCode =
|
||||
authorization.getToken(OAuth2DeviceCode.class);
|
||||
setTokenValues(
|
||||
deviceCode,
|
||||
entity::setDeviceCodeValue,
|
||||
entity::setDeviceCodeIssuedAt,
|
||||
entity::setDeviceCodeExpiresAt,
|
||||
entity::setDeviceCodeMetadata
|
||||
);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -260,6 +302,8 @@ public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService
|
||||
return AuthorizationGrantType.CLIENT_CREDENTIALS;
|
||||
} else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
|
||||
return AuthorizationGrantType.REFRESH_TOKEN;
|
||||
} else if (AuthorizationGrantType.DEVICE_CODE.getValue().equals(authorizationGrantType)) {
|
||||
return AuthorizationGrantType.DEVICE_CODE;
|
||||
}
|
||||
return new AuthorizationGrantType(authorizationGrantType); // Custom authorization grant type
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -109,7 +109,7 @@ public class AuthorizationCodeGrantFlow {
|
||||
* Submit consent for the authorization request and obtain an authorization code.
|
||||
*
|
||||
* @param registeredClient The registered client
|
||||
* @param state The state paramter from the authorization request
|
||||
* @param state The state parameter from the authorization request
|
||||
* @return An authorization code
|
||||
*/
|
||||
public String submitConsent(RegisteredClient registeredClient, String state) throws Exception {
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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 sample;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
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.hamcrest.Matchers.containsString;
|
||||
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.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Helper class that performs steps of the {@code urn:ietf:params:oauth:grant-type:device_code}
|
||||
* flow using {@link MockMvc} for testing.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class DeviceAuthorizationGrantFlow {
|
||||
private static final Pattern HIDDEN_STATE_INPUT_PATTERN = Pattern.compile(".+<input type=\"hidden\" name=\"state\" value=\"([^\"]+)\">.+");
|
||||
private static final TypeReference<Map<String, Object>> JSON_RESPONSE_TYPE_REFERENCE = new TypeReference<>() {
|
||||
};
|
||||
|
||||
private final MockMvc mockMvc;
|
||||
|
||||
private String username = "user";
|
||||
|
||||
private Set<String> scopes = new HashSet<>();
|
||||
|
||||
public DeviceAuthorizationGrantFlow(MockMvc mockMvc) {
|
||||
this.mockMvc = mockMvc;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void addScope(String scope) {
|
||||
this.scopes.add(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the device authorization request and obtain the response
|
||||
* containing a user code and device code.
|
||||
*
|
||||
* @param registeredClient The registered client
|
||||
* @return The device authorization response
|
||||
*/
|
||||
public Map<String, Object> authorize(RegisteredClient registeredClient) throws Exception {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
parameters.set(OAuth2ParameterNames.SCOPE,
|
||||
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
|
||||
|
||||
HttpHeaders basicAuth = new HttpHeaders();
|
||||
basicAuth.setBasicAuth(registeredClient.getClientId(), "secret");
|
||||
|
||||
MvcResult mvcResult = this.mockMvc.perform(post("/oauth2/device_authorization")
|
||||
.params(parameters)
|
||||
.headers(basicAuth))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, containsString(MediaType.APPLICATION_JSON_VALUE)))
|
||||
.andExpect(jsonPath("$.user_code").isNotEmpty())
|
||||
.andExpect(jsonPath("$.device_code").isNotEmpty())
|
||||
.andExpect(jsonPath("$.verification_uri").isNotEmpty())
|
||||
.andExpect(jsonPath("$.verification_uri_complete").isNotEmpty())
|
||||
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||
.andReturn();
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String responseJson = mvcResult.getResponse().getContentAsString();
|
||||
return objectMapper.readValue(responseJson, JSON_RESPONSE_TYPE_REFERENCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the user code and obtain a state parameter from the consent screen.
|
||||
*
|
||||
* @param userCode The user code from the device authorization request
|
||||
* @return The state parameter for submitting consent for authorization
|
||||
*/
|
||||
public String submitCode(String userCode) throws Exception {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.USER_CODE, userCode);
|
||||
|
||||
MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/device_verification")
|
||||
.params(parameters)
|
||||
.with(user(this.username).roles("USER")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE)))
|
||||
.andReturn();
|
||||
String responseHtml = mvcResult.getResponse().getContentAsString();
|
||||
Matcher matcher = HIDDEN_STATE_INPUT_PATTERN.matcher(responseHtml);
|
||||
|
||||
return matcher.matches() ? matcher.group(1) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit consent for the device authorization request.
|
||||
*
|
||||
* @param registeredClient The registered client
|
||||
* @param state The state parameter from the consent screen
|
||||
* @param userCode The user code from the device authorization request
|
||||
*/
|
||||
public void submitConsent(RegisteredClient registeredClient, String state, String userCode) throws Exception {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
parameters.set(OAuth2ParameterNames.STATE, state);
|
||||
for (String scope : this.scopes) {
|
||||
parameters.add(OAuth2ParameterNames.SCOPE, scope);
|
||||
}
|
||||
parameters.set(OAuth2ParameterNames.USER_CODE, userCode);
|
||||
|
||||
MvcResult mvcResult = this.mockMvc.perform(post("/oauth2/device_verification")
|
||||
.params(parameters)
|
||||
.with(user(this.username).roles("USER")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
|
||||
assertThat(redirectedUrl).isNotNull();
|
||||
assertThat(redirectedUrl).isEqualTo("/?success");
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange a device code for an access token.
|
||||
*
|
||||
* @param registeredClient The registered client
|
||||
* @param deviceCode The device code obtained from the device authorization request
|
||||
* @return The token response
|
||||
*/
|
||||
public Map<String, Object> getTokenResponse(RegisteredClient registeredClient, String deviceCode) throws Exception {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue());
|
||||
parameters.set(OAuth2ParameterNames.DEVICE_CODE, deviceCode);
|
||||
|
||||
HttpHeaders basicAuth = new HttpHeaders();
|
||||
basicAuth.setBasicAuth(registeredClient.getClientId(), "secret");
|
||||
|
||||
MvcResult mvcResult = this.mockMvc.perform(post("/oauth2/token")
|
||||
.params(parameters)
|
||||
.headers(basicAuth))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, containsString(MediaType.APPLICATION_JSON_VALUE)))
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.refresh_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").isNotEmpty())
|
||||
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||
.andReturn();
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String responseJson = mvcResult.getResponse().getContentAsString();
|
||||
return objectMapper.readValue(responseJson, JSON_RESPONSE_TYPE_REFERENCE);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.assertj.core.api.ObjectAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import sample.AuthorizationCodeGrantFlow;
|
||||
import sample.DeviceAuthorizationGrantFlow;
|
||||
import sample.test.SpringTestContext;
|
||||
import sample.test.SpringTestContextExtension;
|
||||
|
||||
@@ -116,6 +117,53 @@ public class SecurityConfigTests {
|
||||
StringUtils.delimitedListToStringArray(scopes, " "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceAuthorizationWhenGettingStartedConfigUsedThenSuccess() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfig.class).autowire();
|
||||
assertThat(this.registeredClientRepository).isInstanceOf(InMemoryRegisteredClientRepository.class);
|
||||
assertThat(this.authorizationService).isInstanceOf(InMemoryOAuth2AuthorizationService.class);
|
||||
assertThat(this.authorizationConsentService).isInstanceOf(InMemoryOAuth2AuthorizationConsentService.class);
|
||||
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId("messaging-client");
|
||||
assertThat(registeredClient).isNotNull();
|
||||
|
||||
DeviceAuthorizationGrantFlow deviceAuthorizationGrantFlow = new DeviceAuthorizationGrantFlow(this.mockMvc);
|
||||
deviceAuthorizationGrantFlow.setUsername("user");
|
||||
deviceAuthorizationGrantFlow.addScope("message.read");
|
||||
deviceAuthorizationGrantFlow.addScope("message.write");
|
||||
|
||||
Map<String, Object> deviceAuthorizationResponse = deviceAuthorizationGrantFlow.authorize(registeredClient);
|
||||
String userCode = (String) deviceAuthorizationResponse.get(OAuth2ParameterNames.USER_CODE);
|
||||
assertThatAuthorization(userCode, OAuth2ParameterNames.USER_CODE).isNotNull();
|
||||
assertThatAuthorization(userCode, null).isNotNull();
|
||||
|
||||
String deviceCode = (String) deviceAuthorizationResponse.get(OAuth2ParameterNames.DEVICE_CODE);
|
||||
assertThatAuthorization(deviceCode, OAuth2ParameterNames.DEVICE_CODE).isNotNull();
|
||||
assertThatAuthorization(deviceCode, null).isNotNull();
|
||||
|
||||
String state = deviceAuthorizationGrantFlow.submitCode(userCode);
|
||||
assertThatAuthorization(state, OAuth2ParameterNames.STATE).isNotNull();
|
||||
assertThatAuthorization(state, null).isNotNull();
|
||||
|
||||
deviceAuthorizationGrantFlow.submitConsent(registeredClient, state, userCode);
|
||||
|
||||
Map<String, Object> tokenResponse = deviceAuthorizationGrantFlow.getTokenResponse(registeredClient, deviceCode);
|
||||
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
|
||||
assertThatAuthorization(accessToken, OAuth2ParameterNames.ACCESS_TOKEN).isNotNull();
|
||||
assertThatAuthorization(accessToken, null).isNotNull();
|
||||
|
||||
String refreshToken = (String) tokenResponse.get(OAuth2ParameterNames.REFRESH_TOKEN);
|
||||
assertThatAuthorization(refreshToken, OAuth2ParameterNames.REFRESH_TOKEN).isNotNull();
|
||||
assertThatAuthorization(refreshToken, null).isNotNull();
|
||||
|
||||
String scopes = (String) tokenResponse.get(OAuth2ParameterNames.SCOPE);
|
||||
OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService.findById(
|
||||
registeredClient.getId(), "user");
|
||||
assertThat(authorizationConsent).isNotNull();
|
||||
assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrder(
|
||||
StringUtils.delimitedListToStringArray(scopes, " "));
|
||||
}
|
||||
|
||||
private ObjectAssert<OAuth2Authorization> assertThatAuthorization(String token, String tokenType) {
|
||||
return assertThat(findAuthorization(token, tokenType));
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.assertj.core.api.ObjectAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import sample.AuthorizationCodeGrantFlow;
|
||||
import sample.DeviceAuthorizationGrantFlow;
|
||||
import sample.jose.TestJwks;
|
||||
import sample.jpa.service.authorization.JpaOAuth2AuthorizationService;
|
||||
import sample.jpa.service.authorizationConsent.JpaOAuth2AuthorizationConsentService;
|
||||
@@ -131,6 +132,53 @@ public class JpaTests {
|
||||
StringUtils.delimitedListToStringArray(scopes, " "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceAuthorizationWhenJpaCoreServicesAutowiredThenSuccess() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfig.class).autowire();
|
||||
assertThat(this.registeredClientRepository).isInstanceOf(JpaRegisteredClientRepository.class);
|
||||
assertThat(this.authorizationService).isInstanceOf(JpaOAuth2AuthorizationService.class);
|
||||
assertThat(this.authorizationConsentService).isInstanceOf(JpaOAuth2AuthorizationConsentService.class);
|
||||
|
||||
RegisteredClient registeredClient = messagingClient();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
DeviceAuthorizationGrantFlow deviceAuthorizationGrantFlow = new DeviceAuthorizationGrantFlow(this.mockMvc);
|
||||
deviceAuthorizationGrantFlow.setUsername("user");
|
||||
deviceAuthorizationGrantFlow.addScope("message.read");
|
||||
deviceAuthorizationGrantFlow.addScope("message.write");
|
||||
|
||||
Map<String, Object> deviceAuthorizationResponse = deviceAuthorizationGrantFlow.authorize(registeredClient);
|
||||
String userCode = (String) deviceAuthorizationResponse.get(OAuth2ParameterNames.USER_CODE);
|
||||
assertThatAuthorization(userCode, OAuth2ParameterNames.USER_CODE).isNotNull();
|
||||
assertThatAuthorization(userCode, null).isNotNull();
|
||||
|
||||
String deviceCode = (String) deviceAuthorizationResponse.get(OAuth2ParameterNames.DEVICE_CODE);
|
||||
assertThatAuthorization(deviceCode, OAuth2ParameterNames.DEVICE_CODE).isNotNull();
|
||||
assertThatAuthorization(deviceCode, null).isNotNull();
|
||||
|
||||
String state = deviceAuthorizationGrantFlow.submitCode(userCode);
|
||||
assertThatAuthorization(state, OAuth2ParameterNames.STATE).isNotNull();
|
||||
assertThatAuthorization(state, null).isNotNull();
|
||||
|
||||
deviceAuthorizationGrantFlow.submitConsent(registeredClient, state, userCode);
|
||||
|
||||
Map<String, Object> tokenResponse = deviceAuthorizationGrantFlow.getTokenResponse(registeredClient, deviceCode);
|
||||
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
|
||||
assertThatAuthorization(accessToken, OAuth2ParameterNames.ACCESS_TOKEN).isNotNull();
|
||||
assertThatAuthorization(accessToken, null).isNotNull();
|
||||
|
||||
String refreshToken = (String) tokenResponse.get(OAuth2ParameterNames.REFRESH_TOKEN);
|
||||
assertThatAuthorization(refreshToken, OAuth2ParameterNames.REFRESH_TOKEN).isNotNull();
|
||||
assertThatAuthorization(refreshToken, null).isNotNull();
|
||||
|
||||
String scopes = (String) tokenResponse.get(OAuth2ParameterNames.SCOPE);
|
||||
OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService.findById(
|
||||
registeredClient.getId(), "user");
|
||||
assertThat(authorizationConsent).isNotNull();
|
||||
assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrder(
|
||||
StringUtils.delimitedListToStringArray(scopes, " "));
|
||||
}
|
||||
|
||||
private ObjectAssert<OAuth2Authorization> assertThatAuthorization(String token, String tokenType) {
|
||||
return assertThat(findAuthorization(token, tokenType));
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ public class RegisteredClients {
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
|
||||
.redirectUri("http://127.0.0.1:8080/authorized")
|
||||
.postLogoutRedirectUri("http://127.0.0.1:8080/index")
|
||||
.scope(OidcScopes.OPENID)
|
||||
|
||||
@@ -95,6 +95,14 @@ CREATE TABLE authorization (
|
||||
oidcIdTokenExpiresAt timestamp DEFAULT NULL,
|
||||
oidcIdTokenMetadata varchar(2000) DEFAULT NULL,
|
||||
oidcIdTokenClaims varchar(2000) DEFAULT NULL,
|
||||
userCodeValue varchar(4000) DEFAULT NULL,
|
||||
userCodeIssuedAt timestamp DEFAULT NULL,
|
||||
userCodeExpiresAt timestamp DEFAULT NULL,
|
||||
userCodeMetadata varchar(2000) DEFAULT NULL,
|
||||
deviceCodeValue varchar(4000) DEFAULT NULL,
|
||||
deviceCodeIssuedAt timestamp DEFAULT NULL,
|
||||
deviceCodeExpiresAt timestamp DEFAULT NULL,
|
||||
deviceCodeMetadata varchar(2000) DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
----
|
||||
|
||||
Reference in New Issue
Block a user