Add client configuration settings

Closes gh-117
This commit is contained in:
Joe Grandja 2020-09-10 15:09:03 -04:00
parent 22bf1eb951
commit c3b254579c
8 changed files with 485 additions and 14 deletions

View File

@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.authorization.client;
import org.springframework.security.core.SpringSecurityCoreVersion2;
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.util.Assert;
import org.springframework.util.CollectionUtils;
@ -46,6 +48,8 @@ public class RegisteredClient implements Serializable {
private Set<AuthorizationGrantType> authorizationGrantTypes;
private Set<String> redirectUris;
private Set<String> scopes;
private ClientSettings clientSettings;
private TokenSettings tokenSettings;
protected RegisteredClient() {
}
@ -114,6 +118,24 @@ public class RegisteredClient implements Serializable {
return this.scopes;
}
/**
* Returns the {@link ClientSettings client configuration settings}.
*
* @return the {@link ClientSettings}
*/
public ClientSettings getClientSettings() {
return this.clientSettings;
}
/**
* Returns the {@link TokenSettings token configuration settings}.
*
* @return the {@link TokenSettings}
*/
public TokenSettings getTokenSettings() {
return this.tokenSettings;
}
@Override
public String toString() {
return "RegisteredClient{" +
@ -160,6 +182,8 @@ public class RegisteredClient implements Serializable {
private Set<AuthorizationGrantType> authorizationGrantTypes = new LinkedHashSet<>();
private Set<String> redirectUris = new LinkedHashSet<>();
private Set<String> scopes = new LinkedHashSet<>();
private ClientSettings clientSettings;
private TokenSettings tokenSettings;
protected Builder(String id) {
this.id = id;
@ -181,6 +205,8 @@ public class RegisteredClient implements Serializable {
if (!CollectionUtils.isEmpty(registeredClient.scopes)) {
this.scopes.addAll(registeredClient.scopes);
}
this.clientSettings = new ClientSettings(registeredClient.clientSettings.settings());
this.tokenSettings = new TokenSettings(registeredClient.tokenSettings.settings());
}
/**
@ -310,6 +336,28 @@ public class RegisteredClient implements Serializable {
return this;
}
/**
* Sets the {@link ClientSettings client configuration settings}.
*
* @param clientSettings the client configuration settings
* @return the {@link Builder}
*/
public Builder clientSettings(ClientSettings clientSettings) {
this.clientSettings = clientSettings;
return this;
}
/**
* Sets the {@link TokenSettings token configuration settings}.
*
* @param tokenSettings the token configuration settings
* @return the {@link Builder}
*/
public Builder tokenSettings(TokenSettings tokenSettings) {
this.tokenSettings = tokenSettings;
return this;
}
/**
* Builds a new {@link RegisteredClient}.
*
@ -341,6 +389,8 @@ public class RegisteredClient implements Serializable {
registeredClient.authorizationGrantTypes = Collections.unmodifiableSet(this.authorizationGrantTypes);
registeredClient.redirectUris = Collections.unmodifiableSet(this.redirectUris);
registeredClient.scopes = Collections.unmodifiableSet(this.scopes);
registeredClient.clientSettings = this.clientSettings != null ? this.clientSettings : new ClientSettings();
registeredClient.tokenSettings = this.tokenSettings != null ? this.tokenSettings : new TokenSettings();
return registeredClient;
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.config;
import java.util.HashMap;
import java.util.Map;
/**
* A facility for client configuration settings.
*
* @author Joe Grandja
* @since 0.0.2
* @see Settings
*/
public class ClientSettings extends Settings {
private static final String CLIENT_SETTING_BASE = "spring.security.oauth2.authorization-server.client.";
public static final String REQUIRE_PROOF_KEY = CLIENT_SETTING_BASE.concat("require-proof-key");
/**
* Constructs a {@code ClientSettings}.
*/
public ClientSettings() {
this(defaultSettings());
}
/**
* Constructs a {@code ClientSettings} using the provided parameters.
*
* @param settings the initial settings
*/
public ClientSettings(Map<String, Object> settings) {
super(settings);
}
/**
* Returns {@code true} if the client is required to provide a proof key challenge and verifier
* when performing the Authorization Code Grant flow. The default is {@code false}.
*
* @return {@code true} if the client is required to provide a proof key challenge and verifier, {@code false} otherwise
*/
public boolean requireProofKey() {
return setting(REQUIRE_PROOF_KEY);
}
/**
* Set to {@code true} if the client is required to provide a proof key challenge and verifier
* when performing the Authorization Code Grant flow.
*
* @param requireProofKey {@code true} if the client is required to provide a proof key challenge and verifier, {@code false} otherwise
* @return the {@link ClientSettings}
*/
public ClientSettings requireProofKey(boolean requireProofKey) {
setting(REQUIRE_PROOF_KEY, requireProofKey);
return this;
}
protected static Map<String, Object> defaultSettings() {
Map<String, Object> settings = new HashMap<>();
settings.put(REQUIRE_PROOF_KEY, false);
return settings;
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.config;
import org.springframework.security.core.SpringSecurityCoreVersion2;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
/**
* A facility for configuration settings.
*
* @author Joe Grandja
* @since 0.0.2
*/
public class Settings implements Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion2.SERIAL_VERSION_UID;
private final Map<String, Object> settings;
/**
* Constructs a {@code Settings}.
*/
public Settings() {
this.settings = new HashMap<>();
}
/**
* Constructs a {@code Settings} using the provided parameters.
*
* @param settings the initial settings
*/
public Settings(Map<String, Object> settings) {
Assert.notNull(settings, "settings cannot be null");
this.settings = new HashMap<>(settings);
}
/**
* Returns a configuration setting.
*
* @param name the name of the setting
* @param <T> the type of the setting
* @return the value of the setting, or {@code null} if not available
*/
@SuppressWarnings("unchecked")
public <T> T setting(String name) {
Assert.hasText(name, "name cannot be empty");
return (T) this.settings.get(name);
}
/**
* Sets a configuration setting.
*
* @param name the name of the setting
* @param value the value of the setting
* @return the {@link Settings}
*/
public Settings setting(String name, Object value) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(value, "value cannot be null");
this.settings.put(name, value);
return this;
}
/**
* Returns a {@code Map} of the configuration settings.
*
* @return a {@code Map} of the configuration settings
*/
public Map<String, Object> settings() {
return this.settings;
}
/**
* A {@code Consumer} of the configuration settings {@code Map}
* allowing the ability to add, replace, or remove.
*
* @param settingsConsumer a {@link Consumer} of the configuration settings {@code Map}
* @return the {@link Settings}
*/
public Settings settings(Consumer<Map<String, Object>> settingsConsumer) {
settingsConsumer.accept(this.settings);
return this;
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.config;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* A facility for token configuration settings.
*
* @author Joe Grandja
* @since 0.0.2
* @see Settings
*/
public class TokenSettings extends Settings {
private static final String TOKEN_SETTING_BASE = "spring.security.oauth2.authorization-server.token.";
public static final String ACCESS_TOKEN_TIME_TO_LIVE = TOKEN_SETTING_BASE.concat("access-token-time-to-live");
/**
* Constructs a {@code TokenSettings}.
*/
public TokenSettings() {
this(defaultSettings());
}
/**
* Constructs a {@code TokenSettings} using the provided parameters.
*
* @param settings the initial settings
*/
public TokenSettings(Map<String, Object> settings) {
super(settings);
}
/**
* Returns the time-to-live for an access token. The default is 5 minutes.
*
* @return the time-to-live for an access token
*/
public Duration accessTokenTimeToLive() {
return setting(ACCESS_TOKEN_TIME_TO_LIVE);
}
/**
* Set the time-to-live for an access token.
*
* @param accessTokenTimeToLive the time-to-live for an access token
* @return the {@link TokenSettings}
*/
public TokenSettings accessTokenTimeToLive(Duration accessTokenTimeToLive) {
setting(ACCESS_TOKEN_TIME_TO_LIVE, accessTokenTimeToLive);
return this;
}
protected static Map<String, Object> defaultSettings() {
Map<String, Object> settings = new HashMap<>();
settings.put(ACCESS_TOKEN_TIME_TO_LIVE, Duration.ofMinutes(5));
return settings;
}
}

View File

@ -322,6 +322,9 @@ public class RegisteredClientTests {
RegisteredClient registration = TestRegisteredClients.registeredClient().build();
RegisteredClient updated = RegisteredClient.withRegisteredClient(registration).build();
assertThat(registration.getId()).isEqualTo(updated.getId());
assertThat(registration.getClientId()).isEqualTo(updated.getClientId());
assertThat(registration.getClientSecret()).isEqualTo(updated.getClientSecret());
assertThat(registration.getClientAuthenticationMethods()).isEqualTo(updated.getClientAuthenticationMethods());
assertThat(registration.getClientAuthenticationMethods()).isNotSameAs(updated.getClientAuthenticationMethods());
assertThat(registration.getAuthorizationGrantTypes()).isEqualTo(updated.getAuthorizationGrantTypes());
@ -330,20 +333,10 @@ public class RegisteredClientTests {
assertThat(registration.getRedirectUris()).isNotSameAs(updated.getRedirectUris());
assertThat(registration.getScopes()).isEqualTo(updated.getScopes());
assertThat(registration.getScopes()).isNotSameAs(updated.getScopes());
}
@Test
public void buildWhenRegisteredClientProvidedThenEachPropertyMatches() {
RegisteredClient registration = TestRegisteredClients.registeredClient().build();
RegisteredClient updated = RegisteredClient.withRegisteredClient(registration).build();
assertThat(registration.getId()).isEqualTo(updated.getId());
assertThat(registration.getClientId()).isEqualTo(updated.getClientId());
assertThat(registration.getClientSecret()).isEqualTo(updated.getClientSecret());
assertThat(registration.getClientAuthenticationMethods()).isEqualTo(updated.getClientAuthenticationMethods());
assertThat(registration.getAuthorizationGrantTypes()).isEqualTo(updated.getAuthorizationGrantTypes());
assertThat(registration.getRedirectUris()).isEqualTo(updated.getRedirectUris());
assertThat(registration.getScopes()).isEqualTo(updated.getScopes());
assertThat(registration.getClientSettings().settings()).isEqualTo(updated.getClientSettings().settings());
assertThat(registration.getClientSettings()).isNotSameAs(updated.getClientSettings());
assertThat(registration.getTokenSettings().settings()).isEqualTo(updated.getTokenSettings().settings());
assertThat(registration.getTokenSettings()).isNotSameAs(updated.getTokenSettings());
}
@Test

View File

@ -0,0 +1,49 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.config;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link ClientSettings}.
*
* @author Joe Grandja
*/
public class ClientSettingsTests {
@Test
public void constructorWhenDefaultThenDefaultsAreSet() {
ClientSettings clientSettings = new ClientSettings();
assertThat(clientSettings.settings()).hasSize(1);
assertThat(clientSettings.requireProofKey()).isFalse();
}
@Test
public void constructorWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new ClientSettings(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("settings cannot be null");
}
@Test
public void requireProofKeyWhenTrueThenSet() {
ClientSettings clientSettings = new ClientSettings().requireProofKey(true);
assertThat(clientSettings.requireProofKey()).isTrue();
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.config;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link Settings}.
*
* @author Joe Grandja
*/
public class SettingsTests {
@Test
public void constructorWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new Settings(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("settings cannot be null");
}
@Test
public void constructorWhenSettingsProvidedThenSettingsAreSet() {
Map<String, Object> initialSettings = new HashMap<>();
initialSettings.put("setting1", "value1");
initialSettings.put("setting2", "value2");
Settings settings = new Settings(initialSettings)
.setting("setting3", "value3")
.settings(s -> s.put("setting4", "value4"));
assertThat(settings.settings()).contains(
entry("setting1", "value1"),
entry("setting2", "value2"),
entry("setting3", "value3"),
entry("setting4", "value4"));
}
@Test
public void getSettingWhenNameNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new Settings().setting(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("name cannot be empty");
}
@Test
public void setSettingWhenNameNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new Settings().setting(null, "value"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("name cannot be empty");
}
@Test
public void setSettingWhenValueNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new Settings().setting("setting", null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("value cannot be null");
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.config;
import org.junit.Test;
import java.time.Duration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link TokenSettings}.
*
* @author Joe Grandja
*/
public class TokenSettingsTests {
@Test
public void constructorWhenDefaultThenDefaultsAreSet() {
TokenSettings tokenSettings = new TokenSettings();
assertThat(tokenSettings.settings()).hasSize(1);
assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(5));
}
@Test
public void constructorWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new TokenSettings(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("settings cannot be null");
}
@Test
public void accessTokenTimeToLiveWhenProvidedThenSet() {
Duration accessTokenTimeToLive = Duration.ofMinutes(10);
TokenSettings tokenSettings = new TokenSettings().accessTokenTimeToLive(accessTokenTimeToLive);
assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(accessTokenTimeToLive);
}
}