Polish JdbcRegisteredClientRepository

Issue gh-291
This commit is contained in:
Joe Grandja
2021-07-08 10:21:36 -04:00
parent a11284f0f5
commit 1ae4f7aa13
8 changed files with 403 additions and 408 deletions

View File

@@ -19,18 +19,16 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
@@ -38,24 +36,33 @@ import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* JDBC-backed registered client repository
* A JDBC implementation of a {@link RegisteredClientRepository} that uses a
* {@link JdbcOperations} for {@link RegisteredClient} persistence.
*
* <p>
* <b>NOTE:</b> This {@code RegisteredClientRepository} depends on the table definition described in
* "classpath:org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql" and
* therefore MUST be defined in the database schema.
*
* @author Rafal Lewczuk
* @author Joe Grandja
* @since 0.1.2
* @see RegisteredClientRepository
* @see RegisteredClient
* @see JdbcOperations
* @see RowMapper
*/
public class JdbcRegisteredClientRepository implements RegisteredClientRepository {
private static final Map<String, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_MAP;
private static final Map<String, ClientAuthenticationMethod> CLIENT_AUTHENTICATION_METHOD_MAP;
// @formatter:off
private static final String COLUMN_NAMES = "id, "
+ "client_id, "
+ "client_id_issued_at, "
@@ -68,19 +75,20 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
+ "scopes, "
+ "client_settings,"
+ "token_settings";
// @formatter:on
private static final String TABLE_NAME = "oauth2_registered_client";
private static final String LOAD_REGISTERED_CLIENT_SQL = "SELECT " + COLUMN_NAMES + " FROM " + TABLE_NAME + " WHERE ";
// @formatter:off
private static final String INSERT_REGISTERED_CLIENT_SQL = "INSERT INTO " + TABLE_NAME
+ "(" + COLUMN_NAMES + ") values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private RowMapper<RegisteredClient> registeredClientRowMapper;
private Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper;
+ "(" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
// @formatter:on
private final JdbcOperations jdbcOperations;
private RowMapper<RegisteredClient> registeredClientRowMapper;
private Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper;
/**
* Constructs a {@code JdbcRegisteredClientRepository} using the provided parameters.
@@ -90,55 +98,21 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
public JdbcRegisteredClientRepository(JdbcOperations jdbcOperations) {
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
this.jdbcOperations = jdbcOperations;
this.registeredClientRowMapper = new DefaultRegisteredClientRowMapper();
this.registeredClientParametersMapper = new DefaultRegisteredClientParametersMapper();
}
/**
* Allows changing of {@link RegisteredClient} row mapper implementation
*
* @param registeredClientRowMapper mapper implementation
*/
public final void setRegisteredClientRowMapper(RowMapper<RegisteredClient> registeredClientRowMapper) {
Assert.notNull(registeredClientRowMapper, "registeredClientRowMapper cannot be null");
this.registeredClientRowMapper = registeredClientRowMapper;
}
/**
* Allows changing of SQL parameter mapper for {@link RegisteredClient}
*
* @param registeredClientParametersMapper mapper implementation
*/
public final void setRegisteredClientParametersMapper(Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper) {
Assert.notNull(registeredClientParametersMapper, "registeredClientParameterMapper cannot be null");
this.registeredClientParametersMapper = registeredClientParametersMapper;
}
protected final JdbcOperations getJdbcOperations() {
return this.jdbcOperations;
}
protected final RowMapper<RegisteredClient> getRegisteredClientRowMapper() {
return this.registeredClientRowMapper;
}
protected final Function<RegisteredClient, List<SqlParameterValue>> getRegisteredClientParametersMapper() {
return this.registeredClientParametersMapper;
this.registeredClientRowMapper = new RegisteredClientRowMapper();
this.registeredClientParametersMapper = new RegisteredClientParametersMapper();
}
@Override
public void save(RegisteredClient registeredClient) {
Assert.notNull(registeredClient, "registeredClient cannot be null");
RegisteredClient foundClient = findBy("id = ? OR client_id = ?",
RegisteredClient existingRegisteredClient = findBy("id = ? OR client_id = ?",
registeredClient.getId(), registeredClient.getClientId());
if (foundClient != null) {
Assert.isTrue(!foundClient.getId().equals(registeredClient.getId()),
if (existingRegisteredClient != null) {
Assert.isTrue(!existingRegisteredClient.getId().equals(registeredClient.getId()),
"Registered client must be unique. Found duplicate identifier: " + registeredClient.getId());
Assert.isTrue(!foundClient.getClientId().equals(registeredClient.getClientId()),
Assert.isTrue(!existingRegisteredClient.getClientId().equals(registeredClient.getClientId()),
"Registered client must be unique. Found duplicate client identifier: " + registeredClient.getClientId());
}
List<SqlParameterValue> parameters = this.registeredClientParametersMapper.apply(registeredClient);
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
this.jdbcOperations.update(INSERT_REGISTERED_CLIENT_SQL, pss);
@@ -156,80 +130,95 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
return findBy("client_id = ?", clientId);
}
private RegisteredClient findBy(String condStr, Object... args) {
private RegisteredClient findBy(String filter, Object... args) {
List<RegisteredClient> result = this.jdbcOperations.query(
LOAD_REGISTERED_CLIENT_SQL + condStr,
this.registeredClientRowMapper, args);
LOAD_REGISTERED_CLIENT_SQL + filter, this.registeredClientRowMapper, args);
return !result.isEmpty() ? result.get(0) : null;
}
public static class DefaultRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
/**
* Sets the {@link RowMapper} used for mapping the current row in {@code java.sql.ResultSet} to {@link RegisteredClient}.
* The default is {@link RegisteredClientRowMapper}.
*
* @param registeredClientRowMapper the {@link RowMapper} used for mapping the current row in {@code ResultSet} to {@link RegisteredClient}
*/
public final void setRegisteredClientRowMapper(RowMapper<RegisteredClient> registeredClientRowMapper) {
Assert.notNull(registeredClientRowMapper, "registeredClientRowMapper cannot be null");
this.registeredClientRowMapper = registeredClientRowMapper;
}
/**
* Sets the {@code Function} used for mapping {@link RegisteredClient} to a {@code List} of {@link SqlParameterValue}.
* The default is {@link RegisteredClientParametersMapper}.
*
* @param registeredClientParametersMapper the {@code Function} used for mapping {@link RegisteredClient} to a {@code List} of {@link SqlParameterValue}
*/
public final void setRegisteredClientParametersMapper(Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper) {
Assert.notNull(registeredClientParametersMapper, "registeredClientParametersMapper cannot be null");
this.registeredClientParametersMapper = registeredClientParametersMapper;
}
protected final JdbcOperations getJdbcOperations() {
return this.jdbcOperations;
}
protected final RowMapper<RegisteredClient> getRegisteredClientRowMapper() {
return this.registeredClientRowMapper;
}
protected final Function<RegisteredClient, List<SqlParameterValue>> getRegisteredClientParametersMapper() {
return this.registeredClientParametersMapper;
}
/**
* The default {@link RowMapper} that maps the current row in
* {@code java.sql.ResultSet} to {@link RegisteredClient}.
*/
public static class RegisteredClientRowMapper implements RowMapper<RegisteredClient> {
private ObjectMapper objectMapper = new ObjectMapper();
public RegisteredClientRowMapper() {
ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
public RegisteredClient mapRow(ResultSet rs, int rowNum) throws SQLException {
Set<String> clientScopes = parseList(rs.getString("scopes"));
Set<String> authGrantTypes = parseList(rs.getString("authorization_grant_types"));
Set<String> clientAuthMethods = parseList(rs.getString("client_authentication_methods"));
Set<String> redirectUris = parseList(rs.getString("redirect_uris"));
Timestamp clientIssuedAt = rs.getTimestamp("client_id_issued_at");
Timestamp clientIdIssuedAt = rs.getTimestamp("client_id_issued_at");
Timestamp clientSecretExpiresAt = rs.getTimestamp("client_secret_expires_at");
String clientSecret = rs.getString("client_secret");
RegisteredClient.Builder builder = RegisteredClient
.withId(rs.getString("id"))
Set<String> clientAuthenticationMethods = StringUtils.commaDelimitedListToSet(rs.getString("client_authentication_methods"));
Set<String> authorizationGrantTypes = StringUtils.commaDelimitedListToSet(rs.getString("authorization_grant_types"));
Set<String> redirectUris = StringUtils.commaDelimitedListToSet(rs.getString("redirect_uris"));
Set<String> clientScopes = StringUtils.commaDelimitedListToSet(rs.getString("scopes"));
// @formatter:off
RegisteredClient.Builder builder = RegisteredClient.withId(rs.getString("id"))
.clientId(rs.getString("client_id"))
.clientIdIssuedAt(clientIssuedAt != null ? clientIssuedAt.toInstant() : null)
.clientSecret(clientSecret)
.clientIdIssuedAt(clientIdIssuedAt != null ? clientIdIssuedAt.toInstant() : null)
.clientSecret(rs.getString("client_secret"))
.clientSecretExpiresAt(clientSecretExpiresAt != null ? clientSecretExpiresAt.toInstant() : null)
.clientName(rs.getString("client_name"))
.authorizationGrantTypes((grantTypes) -> authGrantTypes.forEach(authGrantType ->
grantTypes.add(AUTHORIZATION_GRANT_TYPE_MAP.get(authGrantType))))
.clientAuthenticationMethods((authenticationMethods) -> clientAuthMethods.forEach(clientAuthMethod ->
authenticationMethods.add(CLIENT_AUTHENTICATION_METHOD_MAP.get(clientAuthMethod))))
.clientAuthenticationMethods((authenticationMethods) ->
clientAuthenticationMethods.forEach(authenticationMethod ->
authenticationMethods.add(resolveClientAuthenticationMethod(authenticationMethod))))
.authorizationGrantTypes((grantTypes) ->
authorizationGrantTypes.forEach(grantType ->
grantTypes.add(resolveAuthorizationGrantType(grantType))))
.redirectUris((uris) -> uris.addAll(redirectUris))
.scopes((scopes) -> scopes.addAll(clientScopes));
// @formatter:on
RegisteredClient registeredClient = builder.build();
Map<String, Object> clientSettingsMap = parseMap(rs.getString("client_settings"));
builder.clientSettings(clientSettings ->
clientSettings.settings().putAll(clientSettingsMap));
String tokenSettingsJson = rs.getString("token_settings");
if (tokenSettingsJson != null) {
Map<String, Object> settings = parseMap(tokenSettingsJson);
TokenSettings tokenSettings = registeredClient.getTokenSettings();
Map<String, Object> tokenSettingsMap = parseMap(rs.getString("token_settings"));
builder.tokenSettings(tokenSettings ->
tokenSettings.settings().putAll(tokenSettingsMap));
Number accessTokenTTL = (Number) settings.get("access_token_ttl");
if (accessTokenTTL != null) {
tokenSettings.accessTokenTimeToLive(Duration.ofMillis(accessTokenTTL.longValue()));
}
Number refreshTokenTTL = (Number) settings.get("refresh_token_ttl");
if (refreshTokenTTL != null) {
tokenSettings.refreshTokenTimeToLive(Duration.ofMillis(refreshTokenTTL.longValue()));
}
Boolean reuseRefreshTokens = (Boolean) settings.get("reuse_refresh_tokens");
if (reuseRefreshTokens != null) {
tokenSettings.reuseRefreshTokens(reuseRefreshTokens);
}
}
String clientSettingsJson = rs.getString("client_settings");
if (clientSettingsJson != null) {
Map<String, Object> settings = parseMap(clientSettingsJson);
ClientSettings clientSettings = registeredClient.getClientSettings();
Boolean requireProofKey = (Boolean) settings.get("require_proof_key");
if (requireProofKey != null) {
clientSettings.requireProofKey(requireProofKey);
}
Boolean requireUserConsent = (Boolean) settings.get("require_user_consent");
if (requireUserConsent != null) {
clientSettings.requireUserConsent(requireUserConsent);
}
}
return registeredClient;
return builder.build();
}
public final void setObjectMapper(ObjectMapper objectMapper) {
@@ -241,10 +230,6 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
return this.objectMapper;
}
private Set<String> parseList(String s) {
return s != null ? StringUtils.commaDelimitedListToSet(s) : Collections.emptySet();
}
private Map<String, Object> parseMap(String data) {
try {
return this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {});
@@ -253,54 +238,73 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
}
}
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.CLIENT_CREDENTIALS;
} else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.REFRESH_TOKEN;
}
return new AuthorizationGrantType(authorizationGrantType); // Custom authorization grant type
}
private static ClientAuthenticationMethod resolveClientAuthenticationMethod(String clientAuthenticationMethod) {
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
} else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.CLIENT_SECRET_POST;
} else if (ClientAuthenticationMethod.NONE.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.NONE;
}
return new ClientAuthenticationMethod(clientAuthenticationMethod); // Custom client authentication method
}
}
public static class DefaultRegisteredClientParametersMapper implements Function<RegisteredClient, List<SqlParameterValue>> {
/**
* The default {@code Function} that maps {@link RegisteredClient} to a
* {@code List} of {@link SqlParameterValue}.
*/
public static class RegisteredClientParametersMapper implements Function<RegisteredClient, List<SqlParameterValue>> {
private ObjectMapper objectMapper = new ObjectMapper();
public RegisteredClientParametersMapper() {
ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
public List<SqlParameterValue> apply(RegisteredClient registeredClient) {
List<String> clientAuthenticationMethodNames = new ArrayList<>(registeredClient.getClientAuthenticationMethods().size());
for (ClientAuthenticationMethod clientAuthenticationMethod : registeredClient.getClientAuthenticationMethods()) {
clientAuthenticationMethodNames.add(clientAuthenticationMethod.getValue());
}
List<String> authorizationGrantTypeNames = new ArrayList<>(registeredClient.getAuthorizationGrantTypes().size());
for (AuthorizationGrantType authorizationGrantType : registeredClient.getAuthorizationGrantTypes()) {
authorizationGrantTypeNames.add(authorizationGrantType.getValue());
}
Instant issuedAt = registeredClient.getClientIdIssuedAt() != null ?
registeredClient.getClientIdIssuedAt() : Instant.now();
Timestamp clientIdIssuedAt = registeredClient.getClientIdIssuedAt() != null ?
Timestamp.from(registeredClient.getClientIdIssuedAt()) : Timestamp.from(Instant.now());
Timestamp clientSecretExpiresAt = registeredClient.getClientSecretExpiresAt() != null ?
Timestamp.from(registeredClient.getClientSecretExpiresAt()) : null;
Map<String, Object> clientSettings = new HashMap<>();
clientSettings.put("require_proof_key", registeredClient.getClientSettings().requireProofKey());
clientSettings.put("require_user_consent", registeredClient.getClientSettings().requireUserConsent());
String clientSettingsJson = writeMap(clientSettings);
List<String> clientAuthenticationMethods = new ArrayList<>(registeredClient.getClientAuthenticationMethods().size());
registeredClient.getClientAuthenticationMethods().forEach(clientAuthenticationMethod ->
clientAuthenticationMethods.add(clientAuthenticationMethod.getValue()));
Map<String, Object> tokenSettings = new HashMap<>();
tokenSettings.put("access_token_ttl", registeredClient.getTokenSettings().accessTokenTimeToLive().toMillis());
tokenSettings.put("reuse_refresh_tokens", registeredClient.getTokenSettings().reuseRefreshTokens());
tokenSettings.put("refresh_token_ttl", registeredClient.getTokenSettings().refreshTokenTimeToLive().toMillis());
String tokenSettingsJson = writeMap(tokenSettings);
List<String> authorizationGrantTypes = new ArrayList<>(registeredClient.getAuthorizationGrantTypes().size());
registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
authorizationGrantTypes.add(authorizationGrantType.getValue()));
return Arrays.asList(
new SqlParameterValue(Types.VARCHAR, registeredClient.getId()),
new SqlParameterValue(Types.VARCHAR, registeredClient.getClientId()),
new SqlParameterValue(Types.TIMESTAMP, Timestamp.from(issuedAt)),
new SqlParameterValue(Types.TIMESTAMP, clientIdIssuedAt),
new SqlParameterValue(Types.VARCHAR, registeredClient.getClientSecret()),
new SqlParameterValue(Types.TIMESTAMP, clientSecretExpiresAt),
new SqlParameterValue(Types.VARCHAR, registeredClient.getClientName()),
new SqlParameterValue(Types.VARCHAR, StringUtils.collectionToCommaDelimitedString(clientAuthenticationMethodNames)),
new SqlParameterValue(Types.VARCHAR, StringUtils.collectionToCommaDelimitedString(authorizationGrantTypeNames)),
new SqlParameterValue(Types.VARCHAR, StringUtils.collectionToCommaDelimitedString(clientAuthenticationMethods)),
new SqlParameterValue(Types.VARCHAR, StringUtils.collectionToCommaDelimitedString(authorizationGrantTypes)),
new SqlParameterValue(Types.VARCHAR, StringUtils.collectionToCommaDelimitedString(registeredClient.getRedirectUris())),
new SqlParameterValue(Types.VARCHAR, StringUtils.collectionToCommaDelimitedString(registeredClient.getScopes())),
new SqlParameterValue(Types.VARCHAR, clientSettingsJson),
new SqlParameterValue(Types.VARCHAR, tokenSettingsJson));
new SqlParameterValue(Types.VARCHAR, writeMap(registeredClient.getClientSettings().settings())),
new SqlParameterValue(Types.VARCHAR, writeMap(registeredClient.getTokenSettings().settings())));
}
public final void setObjectMapper(ObjectMapper objectMapper) {
@@ -322,25 +326,4 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
}
static {
Map<String, AuthorizationGrantType> am = new HashMap<>();
for (AuthorizationGrantType a : Arrays.asList(
AuthorizationGrantType.AUTHORIZATION_CODE,
AuthorizationGrantType.REFRESH_TOKEN,
AuthorizationGrantType.CLIENT_CREDENTIALS,
AuthorizationGrantType.PASSWORD,
AuthorizationGrantType.IMPLICIT)) {
am.put(a.getValue(), a);
}
AUTHORIZATION_GRANT_TYPE_MAP = Collections.unmodifiableMap(am);
Map<String, ClientAuthenticationMethod> cm = new HashMap<>();
for (ClientAuthenticationMethod c : Arrays.asList(
ClientAuthenticationMethod.NONE,
ClientAuthenticationMethod.BASIC,
ClientAuthenticationMethod.POST)) {
cm.put(c.getValue(), c);
}
CLIENT_AUTHENTICATION_METHOD_MAP = Collections.unmodifiableMap(cm);
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.jackson2;
import java.time.Duration;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* This mixin class is used to serialize/deserialize {@link Duration}.
*
* @author Joe Grandja
* @since 0.1.2
* @see Duration
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE,
creatorVisibility = JsonAutoDetect.Visibility.NONE)
abstract class DurationMixin {
@JsonCreator
static void ofSeconds(@JsonProperty("seconds") long seconds, @JsonProperty("nano") long nanoAdjustment) {
}
@JsonGetter("seconds")
abstract long getSeconds();
@JsonGetter("nano")
abstract int getNano();
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.security.oauth2.server.authorization.jackson2;
import java.time.Duration;
import java.util.Collections;
import java.util.HashSet;
@@ -23,6 +24,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
/**
* Jackson {@code Module} for {@code spring-authorization-server}, that registers the
@@ -32,6 +34,8 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
* <li>{@link UnmodifiableMapMixin}</li>
* <li>{@link HashSetMixin}</li>
* <li>{@link OAuth2AuthorizationRequestMixin}</li>
* <li>{@link DurationMixin}</li>
* <li>{@link SignatureAlgorithmMixin}</li>
* </ul>
*
* If not already enabled, default typing will be automatically enabled as type info is
@@ -52,6 +56,8 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
* @see UnmodifiableMapMixin
* @see HashSetMixin
* @see OAuth2AuthorizationRequestMixin
* @see DurationMixin
* @see SignatureAlgorithmMixin
*/
public class OAuth2AuthorizationServerJackson2Module extends SimpleModule {
@@ -66,6 +72,8 @@ public class OAuth2AuthorizationServerJackson2Module extends SimpleModule {
UnmodifiableMapMixin.class);
context.setMixInAnnotations(HashSet.class, HashSetMixin.class);
context.setMixInAnnotations(OAuth2AuthorizationRequest.class, OAuth2AuthorizationRequestMixin.class);
context.setMixInAnnotations(Duration.class, DurationMixin.class);
context.setMixInAnnotations(SignatureAlgorithm.class, SignatureAlgorithmMixin.class);
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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.jackson2;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
/**
* This mixin class is used to serialize/deserialize {@link SignatureAlgorithm}.
*
* @author Joe Grandja
* @since 0.1.2
* @see SignatureAlgorithm
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class SignatureAlgorithmMixin {
}

View File

@@ -4,12 +4,12 @@ CREATE TABLE oauth2_registered_client (
client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
client_secret varchar(200) DEFAULT NULL,
client_secret_expires_at timestamp DEFAULT NULL,
client_name varchar(200),
client_name varchar(200) NOT NULL,
client_authentication_methods varchar(1000) NOT NULL,
authorization_grant_types varchar(1000) NOT NULL,
redirect_uris varchar(1000) NOT NULL,
redirect_uris varchar(1000) DEFAULT NULL,
scopes varchar(1000) NOT NULL,
client_settings varchar(1000) DEFAULT NULL,
token_settings varchar(1000) DEFAULT NULL,
client_settings varchar(2000) NOT NULL,
token_settings varchar(2000) NOT NULL,
PRIMARY KEY (id)
);

View File

@@ -18,16 +18,13 @@ package org.springframework.security.oauth2.server.authorization.client;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.After;
import org.junit.Before;
@@ -39,57 +36,50 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* JDBC-backed registered client repository tests
* Tests for {@link JdbcRegisteredClientRepository}.
*
* @author Rafal Lewczuk
* @author Steve Riesenberg
* @since 0.1.2
* @author Joe Grandja
*/
public class JdbcRegisteredClientRepositoryTests {
private static final String REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE = "/org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql";
private static final String CUSTOM_REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE = "/org/springframework/security/oauth2/server/authorization/client/custom-oauth2-registered-client-schema.sql";
private DriverManagerDataSource dataSource;
private static final String OAUTH2_REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE = "/org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql";
private static final String OAUTH2_CUSTOM_REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE = "/org/springframework/security/oauth2/server/authorization/client/custom-oauth2-registered-client-schema.sql";
private EmbeddedDatabase db;
private JdbcOperations jdbcOperations;
private JdbcRegisteredClientRepository registeredClientRepository;
private RegisteredClient registeredClient;
private EmbeddedDatabase db;
private JdbcOperations jdbcOperations;
@Before
public void setup() throws Exception {
this.db = createDb(REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE);
public void setUp() {
this.db = createDb(OAUTH2_REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE);
this.jdbcOperations = new JdbcTemplate(this.db);
this.registeredClientRepository = new JdbcRegisteredClientRepository(this.jdbcOperations);
this.registeredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(this.registeredClient);
}
@After
public void destroyDatabase() {
public void tearDown() {
this.db.shutdown();
}
@Test
public void whenJdbcOperationsNullThenThrow() {
public void constructorWhenJdbcOperationsIsNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> new JdbcRegisteredClientRepository(null))
@@ -98,7 +88,7 @@ public class JdbcRegisteredClientRepositoryTests {
}
@Test
public void whenSetNullRegisteredClientRowMapperThenThrow() {
public void setRegisteredClientRowMapperWhenNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.registeredClientRepository.setRegisteredClientRowMapper(null))
@@ -107,28 +97,71 @@ public class JdbcRegisteredClientRepositoryTests {
}
@Test
public void whenSetNullRegisteredClientParameterMapperThenThrow() {
public void setRegisteredClientParametersMapperWhenNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.registeredClientRepository.setRegisteredClientParametersMapper(null))
.withMessage("registeredClientParameterMapper cannot be null");
.withMessage("registeredClientParametersMapper cannot be null");
// @formatter:on
}
@Test
public void findByIdWhenFoundThenFound() {
String id = this.registeredClient.getId();
assertRegisteredClientIsEqualTo(this.registeredClientRepository.findById(id), this.registeredClient);
public void saveWhenRegisteredClientNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.registeredClientRepository.save(null))
.withMessageContaining("registeredClient cannot be null");
}
@Test
public void findByIdWhenNotFoundThenNull() {
RegisteredClient client = this.registeredClientRepository.findById("noooope");
assertThat(client).isNull();
public void saveWhenExistingIdThenThrowIllegalArgumentException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(registeredClient);
assertThatIllegalArgumentException()
.isThrownBy(() -> this.registeredClientRepository.save(registeredClient))
.withMessage("Registered client must be unique. Found duplicate identifier: " + registeredClient.getId());
}
@Test
public void findByIdWhenNullThenThrowIllegalArgumentException() {
public void saveWhenExistingClientIdThenThrowIllegalArgumentException() {
RegisteredClient existingRegisteredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(existingRegisteredClient);
RegisteredClient registeredClient = RegisteredClient.from(existingRegisteredClient)
.id("registration-2")
.build();
assertThatIllegalArgumentException()
.isThrownBy(() -> this.registeredClientRepository.save(registeredClient))
.withMessage("Registered client must be unique. Found duplicate client identifier: " + registeredClient.getClientId());
}
@Test
public void saveWhenNewThenSaved() {
RegisteredClient expectedRegisteredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(expectedRegisteredClient);
RegisteredClient registeredClient = this.registeredClientRepository.findById(expectedRegisteredClient.getId());
assertThat(registeredClient).isEqualTo(expectedRegisteredClient);
}
@Test
public void saveLoadRegisteredClientWhenCustomStrategiesSetThenCalled() throws Exception {
RowMapper<RegisteredClient> registeredClientRowMapper = spy(
new JdbcRegisteredClientRepository.RegisteredClientRowMapper());
this.registeredClientRepository.setRegisteredClientRowMapper(registeredClientRowMapper);
Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper = spy(
new JdbcRegisteredClientRepository.RegisteredClientParametersMapper());
this.registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper);
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(registeredClient);
RegisteredClient result = this.registeredClientRepository.findById(registeredClient.getId());
assertThat(result).isEqualTo(registeredClient);
verify(registeredClientRowMapper).mapRow(any(), anyInt());
verify(registeredClientParametersMapper).apply(any());
}
@Test
public void findByIdWhenIdNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.registeredClientRepository.findById(null))
@@ -137,19 +170,21 @@ public class JdbcRegisteredClientRepositoryTests {
}
@Test
public void findByClientIdWhenFoundThenFound() {
String id = this.registeredClient.getClientId();
assertRegisteredClientIsEqualTo(this.registeredClientRepository.findByClientId(id), this.registeredClient);
public void findByIdWhenExistsThenFound() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(registeredClient);
RegisteredClient result = this.registeredClientRepository.findById(registeredClient.getId());
assertThat(result).isEqualTo(registeredClient);
}
@Test
public void findByClientIdWhenNotFoundThenNull() {
RegisteredClient client = this.registeredClientRepository.findByClientId("noooope");
assertThat(client).isNull();
public void findByIdWhenNotExistsThenNotFound() {
RegisteredClient result = this.registeredClientRepository.findById("not-exists");
assertThat(result).isNull();
}
@Test
public void findByClientIdWhenNullThenThrowIllegalArgumentException() {
public void findByClientIdWhenClientIdNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> this.registeredClientRepository.findByClientId(null))
@@ -158,149 +193,33 @@ public class JdbcRegisteredClientRepositoryTests {
}
@Test
public void saveWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.registeredClientRepository.save(null))
.withMessageContaining("registeredClient cannot be null");
}
@Test
public void saveWhenExistingIdThenThrowIllegalArgumentException() {
RegisteredClient registeredClient = createRegisteredClient(
this.registeredClient.getId(), "client-id-2", "client-secret-2");
assertThatIllegalArgumentException()
.isThrownBy(() -> this.registeredClientRepository.save(registeredClient))
.withMessage("Registered client must be unique. Found duplicate identifier: " + registeredClient.getId());
}
@Test
public void saveWhenExistingClientIdThenThrowIllegalArgumentException() {
RegisteredClient registeredClient = createRegisteredClient(
"client-2", this.registeredClient.getClientId(), "client-secret-2");
assertThatIllegalArgumentException()
.isThrownBy(() -> this.registeredClientRepository.save(registeredClient))
.withMessage("Registered client must be unique. Found duplicate client identifier: " + registeredClient.getClientId());
}
@Test
public void saveWhenExistingClientSecretThenSuccess() {
RegisteredClient registeredClient = createRegisteredClient(
"client-2", "client-id-2", this.registeredClient.getClientSecret());
public void findByClientIdWhenExistsThenFound() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(registeredClient);
RegisteredClient savedClient = this.registeredClientRepository.findById(registeredClient.getId());
assertRegisteredClientIsEqualTo(savedClient, registeredClient);
RegisteredClient result = this.registeredClientRepository.findByClientId(registeredClient.getClientId());
assertThat(result).isEqualTo(registeredClient);
}
@Test
public void saveWhenSavedAndFindByIdThenFound() {
RegisteredClient registeredClient = createRegisteredClient();
this.registeredClientRepository.save(registeredClient);
RegisteredClient savedClient = this.registeredClientRepository.findById(registeredClient.getId());
assertRegisteredClientIsEqualTo(savedClient, registeredClient);
}
@Test
public void saveWhenSavedAndFindByClientIdThenFound() {
RegisteredClient registeredClient = createRegisteredClient();
this.registeredClientRepository.save(registeredClient);
RegisteredClient savedClient = this.registeredClientRepository.findByClientId(registeredClient.getClientId());
assertRegisteredClientIsEqualTo(savedClient, registeredClient);
}
@Test
public void saveWhenPublicClientSavedAndFindByClientIdThenFound() {
RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build();
this.registeredClientRepository.save(registeredClient);
RegisteredClient savedClient = this.registeredClientRepository.findByClientId(registeredClient.getClientId());
assertRegisteredClientIsEqualTo(savedClient, registeredClient);
}
@Test
public void saveWhenMultiplePublicClientsSavedAndFindByIdThenFound() {
RegisteredClient registeredClient1 = TestRegisteredClients.registeredPublicClient()
.id("1").clientId("a").build();
RegisteredClient registeredClient2 = TestRegisteredClients.registeredPublicClient()
.id("2").clientId("b").build();
this.registeredClientRepository.save(registeredClient1);
this.registeredClientRepository.save(registeredClient2);
RegisteredClient savedClient = this.registeredClientRepository.findByClientId(registeredClient2.getClientId());
assertRegisteredClientIsEqualTo(savedClient, registeredClient2);
}
@Test
public void whenSaveRegistrationWithAllAttrsThenSaved() {
Instant issuedAt = Instant.now(), expiresAt = issuedAt.plus(Duration.ofDays(30));
RegisteredClient client = TestRegisteredClients.registeredClient2()
.clientIdIssuedAt(issuedAt)
.clientSecretExpiresAt(expiresAt)
.clientSecret("secret2")
.clientName("some_client_name")
.redirectUri("https://example2.com")
.clientSettings(cs -> {
cs.requireProofKey(true);
cs.requireUserConsent(true);
})
.tokenSettings(ts -> {
ts.accessTokenTimeToLive(Duration.ofMinutes(3));
ts.reuseRefreshTokens(true);
ts.refreshTokenTimeToLive(Duration.ofMinutes(300));
})
.build();
this.registeredClientRepository.save(client);
RegisteredClient retrievedClient = this.registeredClientRepository.findById(client.getId());
assertRegisteredClientIsEqualTo(retrievedClient, client);
public void findByClientIdWhenNotExistsThenNotFound() {
RegisteredClient result = this.registeredClientRepository.findByClientId("not-exists");
assertThat(result).isNull();
}
@Test
public void tableDefinitionWhenCustomThenAbleToOverride() {
EmbeddedDatabase db = createDb(CUSTOM_REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE);
CustomJdbcRegisteredClientRepository registeredClientRepository =
EmbeddedDatabase db = createDb(OAUTH2_CUSTOM_REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE);
RegisteredClientRepository registeredClientRepository =
new CustomJdbcRegisteredClientRepository(new JdbcTemplate(db));
registeredClientRepository.save(this.registeredClient);
RegisteredClient foundClient1 = registeredClientRepository.findById(this.registeredClient.getId());
assertThat(foundClient1).isNotNull();
assertRegisteredClientIsEqualTo(foundClient1, this.registeredClient);
RegisteredClient foundClient2 = registeredClientRepository.findByClientId(this.registeredClient.getClientId());
assertThat(foundClient2).isNotNull();
assertRegisteredClientIsEqualTo(foundClient2, this.registeredClient);
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
registeredClientRepository.save(registeredClient);
RegisteredClient foundRegisteredClient1 = registeredClientRepository.findById(registeredClient.getId());
assertThat(foundRegisteredClient1).isEqualTo(registeredClient);
RegisteredClient foundRegisteredClient2 = registeredClientRepository.findByClientId(registeredClient.getClientId());
assertThat(foundRegisteredClient2).isEqualTo(registeredClient);
db.shutdown();
}
private void assertRegisteredClientIsEqualTo(RegisteredClient rc, RegisteredClient ref) {
assertThat(rc).isNotNull();
assertThat(rc.getId()).isEqualTo(ref.getId());
assertThat(rc.getClientId()).isEqualTo(ref.getClientId());
if (ref.getClientIdIssuedAt() != null) {
// This can be set to default value
Instant inst = ref.getClientIdIssuedAt();
assertThat(rc.getClientIdIssuedAt()).isBetween(inst.minusMillis(1), inst.plusMillis(1));
}
assertThat(rc.getClientSecret()).isEqualTo(ref.getClientSecret());
if (ref.getClientSecretExpiresAt() != null) {
Instant inst = ref.getClientSecretExpiresAt();
assertThat(rc.getClientSecretExpiresAt()).isBetween(inst.minusMillis(1), inst.plusMillis(1));
} else {
assertThat(rc.getClientSecretExpiresAt()).isNull();
}
assertThat(rc.getClientName()).isEqualTo(ref.getClientName());
assertThat(rc.getClientAuthenticationMethods()).isEqualTo(ref.getClientAuthenticationMethods());
assertThat(rc.getAuthorizationGrantTypes()).isEqualTo(ref.getAuthorizationGrantTypes());
assertThat(rc.getRedirectUris()).isEqualTo(ref.getRedirectUris());
assertThat(rc.getScopes()).isEqualTo(ref.getScopes());
assertThat(rc.getClientSettings().requireUserConsent()).isEqualTo(ref.getClientSettings().requireUserConsent());
assertThat(rc.getClientSettings().requireProofKey()).isEqualTo(ref.getClientSettings().requireProofKey());
assertThat(rc.getTokenSettings().reuseRefreshTokens()).isEqualTo(ref.getTokenSettings().reuseRefreshTokens());
assertThat(rc.getTokenSettings().accessTokenTimeToLive()).isEqualTo(ref.getTokenSettings().accessTokenTimeToLive());
assertThat(rc.getTokenSettings().refreshTokenTimeToLive()).isEqualTo(ref.getTokenSettings().refreshTokenTimeToLive());
}
private static EmbeddedDatabase createDb(String schema) {
// @formatter:off
return new EmbeddedDatabaseBuilder()
@@ -312,25 +231,9 @@ public class JdbcRegisteredClientRepositoryTests {
// @formatter:on
}
private static RegisteredClient createRegisteredClient() {
return createRegisteredClient("client-2", "client-id-2", "client-secret-2");
}
private static RegisteredClient createRegisteredClient(String id, String clientId, String clientSecret) {
// @formatter:off
return RegisteredClient.withId(id)
.clientId(clientId)
.clientSecret(clientSecret)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.redirectUri("https://client.example.com")
.scope("scope1")
.build();
// @formatter:on
}
private static final class CustomJdbcRegisteredClientRepository extends JdbcRegisteredClientRepository {
// @formatter:off
private static final String COLUMN_NAMES = "id, "
+ "clientId, "
+ "clientIdIssuedAt, "
@@ -343,15 +246,18 @@ public class JdbcRegisteredClientRepositoryTests {
+ "scopes, "
+ "clientSettings,"
+ "tokenSettings";
// @formatter:on
private static final String TABLE_NAME = "oauth2RegisteredClient";
private static final String LOAD_REGISTERED_CLIENT_SQL = "SELECT " + COLUMN_NAMES + " FROM " + TABLE_NAME + " WHERE ";
// @formatter:off
private static final String INSERT_REGISTERED_CLIENT_SQL = "INSERT INTO " + TABLE_NAME
+ " (" + COLUMN_NAMES + ") values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
// @formatter:on
CustomJdbcRegisteredClientRepository(JdbcOperations jdbcOperations) {
private CustomJdbcRegisteredClientRepository(JdbcOperations jdbcOperations) {
super(jdbcOperations);
setRegisteredClientRowMapper(new CustomRegisteredClientRowMapper());
}
@@ -374,46 +280,56 @@ public class JdbcRegisteredClientRepositoryTests {
}
private RegisteredClient findBy(String filter, Object... args) {
List<RegisteredClient> result = getJdbcOperations()
.query(LOAD_REGISTERED_CLIENT_SQL + filter, getRegisteredClientRowMapper(), args);
List<RegisteredClient> result = getJdbcOperations().query(
LOAD_REGISTERED_CLIENT_SQL + filter, getRegisteredClientRowMapper(), args);
return !result.isEmpty() ? result.get(0) : null;
}
private static final class CustomRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
private static final Map<String, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_MAP;
private static final Map<String, ClientAuthenticationMethod> CLIENT_AUTHENTICATION_METHOD_MAP;
private final ObjectMapper objectMapper = new ObjectMapper();
private CustomRegisteredClientRowMapper() {
ClassLoader classLoader = CustomJdbcRegisteredClientRepository.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
public RegisteredClient mapRow(ResultSet rs, int rowNum) throws SQLException {
Set<String> clientScopes = StringUtils.commaDelimitedListToSet(rs.getString("scopes"));
Set<String> authGrantTypes = StringUtils.commaDelimitedListToSet(rs.getString("authorizationGrantTypes"));
Set<String> clientAuthMethods = StringUtils.commaDelimitedListToSet(rs.getString("clientAuthenticationMethods"));
Set<String> redirectUris = StringUtils.commaDelimitedListToSet(rs.getString("redirectUris"));
Timestamp clientIssuedAt = rs.getTimestamp("clientIdIssuedAt");
Timestamp clientIdIssuedAt = rs.getTimestamp("clientIdIssuedAt");
Timestamp clientSecretExpiresAt = rs.getTimestamp("clientSecretExpiresAt");
String clientSecret = rs.getString("clientSecret");
RegisteredClient.Builder builder = RegisteredClient
.withId(rs.getString("id"))
Set<String> clientAuthenticationMethods = StringUtils.commaDelimitedListToSet(rs.getString("clientAuthenticationMethods"));
Set<String> authorizationGrantTypes = StringUtils.commaDelimitedListToSet(rs.getString("authorizationGrantTypes"));
Set<String> redirectUris = StringUtils.commaDelimitedListToSet(rs.getString("redirectUris"));
Set<String> clientScopes = StringUtils.commaDelimitedListToSet(rs.getString("scopes"));
// @formatter:off
RegisteredClient.Builder builder = RegisteredClient.withId(rs.getString("id"))
.clientId(rs.getString("clientId"))
.clientIdIssuedAt(clientIssuedAt != null ? clientIssuedAt.toInstant() : null)
.clientSecret(clientSecret)
.clientIdIssuedAt(clientIdIssuedAt != null ? clientIdIssuedAt.toInstant() : null)
.clientSecret(rs.getString("clientSecret"))
.clientSecretExpiresAt(clientSecretExpiresAt != null ? clientSecretExpiresAt.toInstant() : null)
.clientName(rs.getString("clientName"))
.authorizationGrantTypes((grantTypes) -> authGrantTypes.forEach(authGrantType ->
grantTypes.add(AUTHORIZATION_GRANT_TYPE_MAP.get(authGrantType))))
.clientAuthenticationMethods((authenticationMethods) -> clientAuthMethods.forEach(clientAuthMethod ->
authenticationMethods.add(CLIENT_AUTHENTICATION_METHOD_MAP.get(clientAuthMethod))))
.clientAuthenticationMethods((authenticationMethods) ->
clientAuthenticationMethods.forEach(authenticationMethod ->
authenticationMethods.add(resolveClientAuthenticationMethod(authenticationMethod))))
.authorizationGrantTypes((grantTypes) ->
authorizationGrantTypes.forEach(grantType ->
grantTypes.add(resolveAuthorizationGrantType(grantType))))
.redirectUris((uris) -> uris.addAll(redirectUris))
.scopes((scopes) -> scopes.addAll(clientScopes));
// @formatter:on
RegisteredClient registeredClient = builder.build();
registeredClient.getClientSettings().settings().putAll(parseMap(rs.getString("clientSettings")));
registeredClient.getTokenSettings().settings().putAll(parseMap(rs.getString("tokenSettings")));
Map<String, Object> clientSettingsMap = parseMap(rs.getString("clientSettings"));
builder.clientSettings(clientSettings ->
clientSettings.settings().putAll(clientSettingsMap));
return registeredClient;
Map<String, Object> tokenSettingsMap = parseMap(rs.getString("tokenSettings"));
builder.tokenSettings(tokenSettings ->
tokenSettings.settings().putAll(tokenSettingsMap));
return builder.build();
}
private Map<String, Object> parseMap(String data) {
@@ -424,26 +340,26 @@ public class JdbcRegisteredClientRepositoryTests {
}
}
static {
Map<String, AuthorizationGrantType> am = new HashMap<>();
for (AuthorizationGrantType a : Arrays.asList(
AuthorizationGrantType.AUTHORIZATION_CODE,
AuthorizationGrantType.REFRESH_TOKEN,
AuthorizationGrantType.CLIENT_CREDENTIALS,
AuthorizationGrantType.PASSWORD,
AuthorizationGrantType.IMPLICIT)) {
am.put(a.getValue(), a);
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.CLIENT_CREDENTIALS;
} else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.REFRESH_TOKEN;
}
AUTHORIZATION_GRANT_TYPE_MAP = Collections.unmodifiableMap(am);
return new AuthorizationGrantType(authorizationGrantType); // Custom authorization grant type
}
Map<String, ClientAuthenticationMethod> cm = new HashMap<>();
for (ClientAuthenticationMethod c : Arrays.asList(
ClientAuthenticationMethod.NONE,
ClientAuthenticationMethod.BASIC,
ClientAuthenticationMethod.POST)) {
cm.put(c.getValue(), c);
private static ClientAuthenticationMethod resolveClientAuthenticationMethod(String clientAuthenticationMethod) {
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
} else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.CLIENT_SECRET_POST;
} else if (ClientAuthenticationMethod.NONE.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.NONE;
}
CLIENT_AUTHENTICATION_METHOD_MAP = Collections.unmodifiableMap(cm);
return new ClientAuthenticationMethod(clientAuthenticationMethod); // Custom client authentication method
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* 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.
@@ -15,6 +15,8 @@
*/
package org.springframework.security.oauth2.server.authorization.client;
import java.time.Instant;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
@@ -26,6 +28,7 @@ public class TestRegisteredClients {
public static RegisteredClient.Builder registeredClient() {
return RegisteredClient.withId("registration-1")
.clientId("client-1")
.clientIdIssuedAt(Instant.now())
.clientSecret("secret")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
@@ -37,6 +40,7 @@ public class TestRegisteredClients {
public static RegisteredClient.Builder registeredClient2() {
return RegisteredClient.withId("registration-2")
.clientId("client-2")
.clientIdIssuedAt(Instant.now())
.clientSecret("secret")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
@@ -51,6 +55,7 @@ public class TestRegisteredClients {
public static RegisteredClient.Builder registeredPublicClient() {
return RegisteredClient.withId("registration-3")
.clientId("client-3")
.clientIdIssuedAt(Instant.now())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.redirectUri("https://example.com")

View File

@@ -4,12 +4,12 @@ CREATE TABLE oauth2RegisteredClient (
clientIdIssuedAt timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
clientSecret varchar(200) DEFAULT NULL,
clientSecretExpiresAt timestamp DEFAULT NULL,
clientName varchar(200),
clientName varchar(200) NOT NULL,
clientAuthenticationMethods varchar(1000) NOT NULL,
authorizationGrantTypes varchar(1000) NOT NULL,
redirectUris varchar(1000) NOT NULL,
redirectUris varchar(1000) DEFAULT NULL,
scopes varchar(1000) NOT NULL,
clientSettings varchar(1000) DEFAULT NULL,
tokenSettings varchar(1000) DEFAULT NULL,
clientSettings varchar(2000) NOT NULL,
tokenSettings varchar(2000) NOT NULL,
PRIMARY KEY (id)
);