Polish JdbcRegisteredClientRepository
Issue gh-291
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user