Provide JDBC implementation of OAuth2AuthorizationService

Add new JDBC implementation of the OAuth2AuthorizationService

Closes gh-245
This commit is contained in:
Ovidiu Popa
2021-05-17 18:29:11 +03:00
committed by Steve Riesenberg
parent f3cb8f758c
commit 552751bd93
5 changed files with 983 additions and 0 deletions

View File

@@ -0,0 +1,397 @@
/*
* Copyright 2020-2021 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;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
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.jdbc.support.lob.DefaultLobHandler;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
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 java.time.Instant;
import java.time.temporal.ChronoUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link JdbcOAuth2AuthorizationService}.
*
* @author Ovidiu Popa
*/
public class JdbcOAuth2AuthorizationServiceTests {
private static final String OAUTH2_AUTHORIZATION_SCHEMA_SQL_RESOURCE = "org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql";
private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE);
private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
private static final String ID = "id";
private static final RegisteredClient REGISTERED_CLIENT = TestRegisteredClients.registeredClient().build();
private static final String PRINCIPAL_NAME = "principal";
private static final AuthorizationGrantType AUTHORIZATION_GRANT_TYPE = AuthorizationGrantType.AUTHORIZATION_CODE;
private static final OAuth2AuthorizationCode AUTHORIZATION_CODE = new OAuth2AuthorizationCode(
"code", Instant.now().truncatedTo(ChronoUnit.MILLIS), Instant.now().plus(5, ChronoUnit.MINUTES).truncatedTo(ChronoUnit.MILLIS));
private EmbeddedDatabase db;
private JdbcOperations jdbcOperations;
private RegisteredClientRepository registeredClientRepository;
private JdbcOAuth2AuthorizationService authorizationService;
@Before
public void setUp() {
this.db = createDb();
this.registeredClientRepository = mock(RegisteredClientRepository.class);
this.jdbcOperations = new JdbcTemplate(this.db);
this.authorizationService = new JdbcOAuth2AuthorizationService(this.jdbcOperations, this.registeredClientRepository);
}
@After
public void tearDown() {
this.db.shutdown();
}
@Test
public void constructorWhenJdbcOperationsIsNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> new JdbcOAuth2AuthorizationService(null, this.registeredClientRepository))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("jdbcOperations cannot be null");
// @formatter:on
}
@Test
public void constructorWhenRegisteredClientRepositoryIsNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> new JdbcOAuth2AuthorizationService(this.jdbcOperations, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("registeredClientRepository cannot be null");
// @formatter:on
}
@Test
public void constructorWhenLobHandlerIsNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> new JdbcOAuth2AuthorizationService(this.jdbcOperations, this.registeredClientRepository, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("lobHandler cannot be null");
// @formatter:on
}
@Test
public void constructorWhenObjectMapperIsNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> new JdbcOAuth2AuthorizationService(this.jdbcOperations, this.registeredClientRepository, new DefaultLobHandler(), null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("objectMapper cannot be null");
// @formatter:on
}
@Test
public void setAuthorizationRowMapperWhenNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> this.authorizationService.setAuthorizationRowMapper(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("authorizationRowMapper cannot be null");
// @formatter:on
}
@Test
public void setAuthorizationParametersMapperWhenNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> this.authorizationService.setAuthorizationParametersMapper(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("authorizationParametersMapper cannot be null");
// @formatter:on
}
@Test
public void saveWhenAuthorizationNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> this.authorizationService.save(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("authorization cannot be null");
// @formatter:on
}
@Test
public void saveWhenAuthorizationNewThenSaved() {
when(registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId())))
.thenReturn(REGISTERED_CLIENT);
OAuth2Authorization expectedAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
.id(ID)
.principalName(PRINCIPAL_NAME)
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
.token(AUTHORIZATION_CODE)
.build();
this.authorizationService.save(expectedAuthorization);
OAuth2Authorization authorization = this.authorizationService.findById(ID);
assertThat(authorization).isEqualTo(expectedAuthorization);
}
@Test
public void saveWhenAuthorizationExistsThenUpdated() {
when(registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId())))
.thenReturn(REGISTERED_CLIENT);
OAuth2Authorization originalAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
.id(ID)
.principalName(PRINCIPAL_NAME)
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
.token(AUTHORIZATION_CODE)
.build();
this.authorizationService.save(originalAuthorization);
OAuth2Authorization authorization = this.authorizationService.findById(
originalAuthorization.getId());
assertThat(authorization).isEqualTo(originalAuthorization);
OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization)
.attribute("custom-name-1", "custom-value-1")
.build();
this.authorizationService.save(updatedAuthorization);
authorization = this.authorizationService.findById(
updatedAuthorization.getId());
assertThat(authorization).isEqualTo(updatedAuthorization);
assertThat(authorization).isNotEqualTo(originalAuthorization);
}
@Test
public void saveLoadAuthorizationWhenCustomStrategiesSetThenCalled() throws Exception {
when(registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId())))
.thenReturn(REGISTERED_CLIENT);
OAuth2Authorization originalAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
.id(ID)
.principalName(PRINCIPAL_NAME)
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
.token(AUTHORIZATION_CODE)
.build();
ObjectMapper objectMapper = new ObjectMapper();
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper authorizationRowMapper = spy(
new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(
this.registeredClientRepository, objectMapper));
this.authorizationService.setAuthorizationRowMapper(authorizationRowMapper);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper authorizationParametersMapper = spy(
new JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper(objectMapper));
this.authorizationService.setAuthorizationParametersMapper(authorizationParametersMapper);
this.authorizationService.save(originalAuthorization);
OAuth2Authorization authorization = this.authorizationService.findById(
originalAuthorization.getId());
assertThat(authorization).isEqualTo(originalAuthorization);
verify(authorizationRowMapper).mapRow(any(), anyInt());
verify(authorizationParametersMapper).apply(any());
}
@Test
public void removeWhenAuthorizationNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> this.authorizationService.remove(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("authorization cannot be null");
// @formatter:on
}
@Test
public void removeWhenAuthorizationProvidedThenRemoved() {
when(registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId())))
.thenReturn(REGISTERED_CLIENT);
OAuth2Authorization expectedAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
.id(ID)
.principalName(PRINCIPAL_NAME)
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
.token(AUTHORIZATION_CODE)
.build();
this.authorizationService.save(expectedAuthorization);
OAuth2Authorization authorization = this.authorizationService.findByToken(
AUTHORIZATION_CODE.getTokenValue(), AUTHORIZATION_CODE_TOKEN_TYPE);
assertThat(authorization).isEqualTo(expectedAuthorization);
this.authorizationService.remove(expectedAuthorization);
authorization = this.authorizationService.findByToken(
AUTHORIZATION_CODE.getTokenValue(), AUTHORIZATION_CODE_TOKEN_TYPE);
assertThat(authorization).isNull();
}
@Test
public void findByIdWhenIdNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> this.authorizationService.findById(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("id cannot be empty");
// @formatter:on
}
@Test
public void findByIdWhenIdEmptyThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> this.authorizationService.findById(" "))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("id cannot be empty");
// @formatter:on
}
@Test
public void findByTokenWhenTokenNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> this.authorizationService.findByToken(null, AUTHORIZATION_CODE_TOKEN_TYPE))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("token cannot be empty");
// @formatter:on
}
@Test
public void findByTokenWhenStateExistsThenFound() {
when(registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId())))
.thenReturn(REGISTERED_CLIENT);
String state = "state";
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
.id(ID)
.principalName(PRINCIPAL_NAME)
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
.attribute(OAuth2ParameterNames.STATE, state)
.build();
this.authorizationService.save(authorization);
OAuth2Authorization result = this.authorizationService.findByToken(
state, STATE_TOKEN_TYPE);
assertThat(authorization).isEqualTo(result);
result = this.authorizationService.findByToken(state, null);
assertThat(authorization).isEqualTo(result);
}
@Test
public void findByTokenWhenAuthorizationCodeExistsThenFound() {
when(registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId())))
.thenReturn(REGISTERED_CLIENT);
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
.id(ID)
.principalName(PRINCIPAL_NAME)
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
.token(AUTHORIZATION_CODE)
.build();
this.authorizationService.save(authorization);
OAuth2Authorization result = this.authorizationService.findByToken(
AUTHORIZATION_CODE.getTokenValue(), AUTHORIZATION_CODE_TOKEN_TYPE);
assertThat(authorization).isEqualTo(result);
result = this.authorizationService.findByToken(AUTHORIZATION_CODE.getTokenValue(), null);
assertThat(authorization).isEqualTo(result);
}
@Test
public void findByTokenWhenAccessTokenExistsThenFound() {
when(registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId())))
.thenReturn(REGISTERED_CLIENT);
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
"access-token", Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS), Instant.now().truncatedTo(ChronoUnit.MILLIS));
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
.id(ID)
.principalName(PRINCIPAL_NAME)
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
.token(AUTHORIZATION_CODE)
.accessToken(accessToken)
.build();
this.authorizationService.save(authorization);
OAuth2Authorization result = this.authorizationService.findByToken(
accessToken.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN);
assertThat(authorization).isEqualTo(result);
result = this.authorizationService.findByToken(accessToken.getTokenValue(), null);
assertThat(authorization).isEqualTo(result);
}
@Test
public void findByTokenWhenRefreshTokenExistsThenFound() {
when(registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId())))
.thenReturn(REGISTERED_CLIENT);
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken2("refresh-token",
Instant.now().truncatedTo(ChronoUnit.MILLIS),
Instant.now().plus(5, ChronoUnit.MINUTES).truncatedTo(ChronoUnit.MILLIS));
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
.id(ID)
.principalName(PRINCIPAL_NAME)
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
.refreshToken(refreshToken)
.build();
this.authorizationService.save(authorization);
OAuth2Authorization result = this.authorizationService.findByToken(
refreshToken.getTokenValue(), OAuth2TokenType.REFRESH_TOKEN);
assertThat(authorization).isEqualTo(result);
result = this.authorizationService.findByToken(refreshToken.getTokenValue(), null);
assertThat(authorization).isEqualTo(result);
}
@Test
public void findByTokenWhenWrongTokenTypeThenNotFound() {
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now().truncatedTo(ChronoUnit.MILLIS));
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT)
.id(ID)
.principalName(PRINCIPAL_NAME)
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE)
.refreshToken(refreshToken)
.build();
this.authorizationService.save(authorization);
OAuth2Authorization result = this.authorizationService.findByToken(
refreshToken.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN);
assertThat(result).isNull();
}
@Test
public void findByTokenWhenTokenDoesNotExistThenNull() {
OAuth2Authorization result = this.authorizationService.findByToken(
"access-token", OAuth2TokenType.ACCESS_TOKEN);
assertThat(result).isNull();
}
private static EmbeddedDatabase createDb() {
return createDb(OAUTH2_AUTHORIZATION_SCHEMA_SQL_RESOURCE);
}
private static EmbeddedDatabase createDb(String schema) {
// @formatter:off
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.HSQL)
.setScriptEncoding("UTF-8")
.addScript(schema)
.build();
// @formatter:on
}
}