This commit is contained in:
Phillip Webb 2017-11-28 16:21:09 -08:00
parent 2319d01feb
commit 960083bd33
10 changed files with 60 additions and 45 deletions

View File

@ -23,7 +23,6 @@ import java.security.PublicKey;
import java.security.Signature; import java.security.Signature;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -71,24 +70,31 @@ class ReactiveTokenValidator {
} }
private Mono<Void> validateKeyIdAndSignature(Token token) { private Mono<Void> validateKeyIdAndSignature(Token token) {
String keyId = token.getKeyId(); return getTokenKey(token).filter((tokenKey) -> hasValidSignature(token, tokenKey))
Map<String, String> localCachedTokenKeys = new HashMap<>(this.cachedTokenKeys);
return Mono.just(localCachedTokenKeys)
.filter((tokenKeys) -> tokenKeys.containsKey(keyId))
.switchIfEmpty(this.securityService.fetchTokenKeys()
.doOnSuccess((fetchedTokenKeys) -> {
this.cachedTokenKeys.clear();
this.cachedTokenKeys.putAll(fetchedTokenKeys);
}).filter((tokenKeys) -> tokenKeys.containsKey(keyId))
.switchIfEmpty((Mono.error(new CloudFoundryAuthorizationException(
Reason.INVALID_KEY_ID,
"Key Id present in token header does not match")))))
.filter((tokenKeys) -> hasValidSignature(token, tokenKeys.get(keyId)))
.switchIfEmpty(Mono.error(new CloudFoundryAuthorizationException( .switchIfEmpty(Mono.error(new CloudFoundryAuthorizationException(
Reason.INVALID_SIGNATURE, "RSA Signature did not match content"))) Reason.INVALID_SIGNATURE, "RSA Signature did not match content")))
.then(); .then();
} }
private Mono<String> getTokenKey(Token token) {
String keyId = token.getKeyId();
String cached = this.cachedTokenKeys.get(keyId);
if (cached != null) {
return Mono.just(cached);
}
return this.securityService.fetchTokenKeys().doOnSuccess(this::cacheTokenKeys)
.filter((tokenKeys) -> tokenKeys.containsKey(keyId))
.map((tokenKeys) -> tokenKeys.get(keyId))
.switchIfEmpty(Mono.error(
new CloudFoundryAuthorizationException(Reason.INVALID_KEY_ID,
"Key Id present in token header does not match")));
}
private void cacheTokenKeys(Map<String, String> tokenKeys) {
this.cachedTokenKeys.clear();
this.cachedTokenKeys.putAll(tokenKeys);
}
private boolean hasValidSignature(Token token, String key) { private boolean hasValidSignature(Token token, String key) {
try { try {
PublicKey publicKey = getPublicKey(key); PublicKey publicKey = getPublicKey(key);

View File

@ -28,8 +28,8 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesC
* @author Jon Schneider * @author Jon Schneider
* @author Phillip Webb * @author Phillip Webb
*/ */
class AtlasPropertiesConfigAdapter extends class AtlasPropertiesConfigAdapter extends PropertiesConfigAdapter<AtlasProperties>
PropertiesConfigAdapter<AtlasProperties> implements AtlasConfig { implements AtlasConfig {
AtlasPropertiesConfigAdapter(AtlasProperties properties) { AtlasPropertiesConfigAdapter(AtlasProperties properties) {
super(properties); super(properties);

View File

@ -30,8 +30,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesC
* @author Jon Schneider * @author Jon Schneider
* @author Phillip Webb * @author Phillip Webb
*/ */
class GangliaPropertiesConfigAdapter class GangliaPropertiesConfigAdapter extends PropertiesConfigAdapter<GangliaProperties>
extends PropertiesConfigAdapter<GangliaProperties>
implements GangliaConfig { implements GangliaConfig {
GangliaPropertiesConfigAdapter(GangliaProperties properties) { GangliaPropertiesConfigAdapter(GangliaProperties properties) {

View File

@ -30,8 +30,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesC
* @author Jon Schneider * @author Jon Schneider
* @author Phillip Webb * @author Phillip Webb
*/ */
class GraphitePropertiesConfigAdapter class GraphitePropertiesConfigAdapter extends PropertiesConfigAdapter<GraphiteProperties>
extends PropertiesConfigAdapter<GraphiteProperties>
implements GraphiteConfig { implements GraphiteConfig {
GraphitePropertiesConfigAdapter(GraphiteProperties properties) { GraphitePropertiesConfigAdapter(GraphiteProperties properties) {

View File

@ -28,9 +28,8 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesC
* @author Jon Schneider * @author Jon Schneider
* @author Phillip Webb * @author Phillip Webb
*/ */
class PrometheusPropertiesConfigAdapter class PrometheusPropertiesConfigAdapter extends
extends PropertiesConfigAdapter<PrometheusProperties> PropertiesConfigAdapter<PrometheusProperties> implements PrometheusConfig {
implements PrometheusConfig {
PrometheusPropertiesConfigAdapter(PrometheusProperties properties) { PrometheusPropertiesConfigAdapter(PrometheusProperties properties) {
super(properties); super(properties);

View File

@ -28,8 +28,8 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesC
* @author Jon Schneider * @author Jon Schneider
* @since 2.0.0 * @since 2.0.0
*/ */
public class SimplePropertiesConfigAdapter extends public class SimplePropertiesConfigAdapter
PropertiesConfigAdapter<SimpleProperties> implements SimpleConfig { extends PropertiesConfigAdapter<SimpleProperties> implements SimpleConfig {
public SimplePropertiesConfigAdapter(SimpleProperties properties) { public SimplePropertiesConfigAdapter(SimpleProperties properties) {
super(properties); super(properties);

View File

@ -29,8 +29,8 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesC
* @author Jon Schneider * @author Jon Schneider
* @since 2.0.0 * @since 2.0.0
*/ */
public class StatsdPropertiesConfigAdapter extends public class StatsdPropertiesConfigAdapter
PropertiesConfigAdapter<StatsdProperties> implements StatsdConfig { extends PropertiesConfigAdapter<StatsdProperties> implements StatsdConfig {
public StatsdPropertiesConfigAdapter(StatsdProperties properties) { public StatsdPropertiesConfigAdapter(StatsdProperties properties) {
super(properties); super(properties);

View File

@ -93,8 +93,10 @@ public class ReactiveTokenValidatorTests {
} }
@Test @Test
public void validateTokenWhenKidValidationFailsTwiceShouldThrowException() throws Exception { public void validateTokenWhenKidValidationFailsTwiceShouldThrowException()
PublisherProbe<Map<String, String>> fetchTokenKeys = PublisherProbe.of(Mono.just(VALID_KEYS)); throws Exception {
PublisherProbe<Map<String, String>> fetchTokenKeys = PublisherProbe
.of(Mono.just(VALID_KEYS));
ReflectionTestUtils.setField(this.tokenValidator, "cachedTokenKeys", VALID_KEYS); ReflectionTestUtils.setField(this.tokenValidator, "cachedTokenKeys", VALID_KEYS);
given(this.securityService.fetchTokenKeys()).willReturn(fetchTokenKeys.mono()); given(this.securityService.fetchTokenKeys()).willReturn(fetchTokenKeys.mono());
given(this.securityService.getUaaUrl()) given(this.securityService.getUaaUrl())
@ -110,15 +112,19 @@ public class ReactiveTokenValidatorTests {
assertThat(((CloudFoundryAuthorizationException) ex).getReason()) assertThat(((CloudFoundryAuthorizationException) ex).getReason())
.isEqualTo(Reason.INVALID_KEY_ID); .isEqualTo(Reason.INVALID_KEY_ID);
}).verify(); }).verify();
Object cachedTokenKeys = ReflectionTestUtils.getField(this.tokenValidator, "cachedTokenKeys"); Object cachedTokenKeys = ReflectionTestUtils.getField(this.tokenValidator,
"cachedTokenKeys");
assertThat(cachedTokenKeys).isEqualTo(VALID_KEYS); assertThat(cachedTokenKeys).isEqualTo(VALID_KEYS);
fetchTokenKeys.assertWasSubscribed(); fetchTokenKeys.assertWasSubscribed();
} }
@Test @Test
public void validateTokenWhenKidValidationSucceedsInTheSecondAttempt() throws Exception { public void validateTokenWhenKidValidationSucceedsInTheSecondAttempt()
PublisherProbe<Map<String, String>> fetchTokenKeys = PublisherProbe.of(Mono.just(VALID_KEYS)); throws Exception {
ReflectionTestUtils.setField(this.tokenValidator, "cachedTokenKeys", INVALID_KEYS); PublisherProbe<Map<String, String>> fetchTokenKeys = PublisherProbe
.of(Mono.just(VALID_KEYS));
ReflectionTestUtils.setField(this.tokenValidator, "cachedTokenKeys",
INVALID_KEYS);
given(this.securityService.fetchTokenKeys()).willReturn(fetchTokenKeys.mono()); given(this.securityService.fetchTokenKeys()).willReturn(fetchTokenKeys.mono());
given(this.securityService.getUaaUrl()) given(this.securityService.getUaaUrl())
.willReturn(Mono.just("http://localhost:8080/uaa")); .willReturn(Mono.just("http://localhost:8080/uaa"));
@ -128,14 +134,16 @@ public class ReactiveTokenValidatorTests {
.create(this.tokenValidator.validate( .create(this.tokenValidator.validate(
new Token(getSignedToken(header.getBytes(), claims.getBytes())))) new Token(getSignedToken(header.getBytes(), claims.getBytes()))))
.verifyComplete(); .verifyComplete();
Object cachedTokenKeys = ReflectionTestUtils.getField(this.tokenValidator, "cachedTokenKeys"); Object cachedTokenKeys = ReflectionTestUtils.getField(this.tokenValidator,
"cachedTokenKeys");
assertThat(cachedTokenKeys).isEqualTo(VALID_KEYS); assertThat(cachedTokenKeys).isEqualTo(VALID_KEYS);
fetchTokenKeys.assertWasSubscribed(); fetchTokenKeys.assertWasSubscribed();
} }
@Test @Test
public void validateTokenWhenCacheIsEmptyShouldFetchTokenKeys() throws Exception { public void validateTokenWhenCacheIsEmptyShouldFetchTokenKeys() throws Exception {
PublisherProbe<Map<String, String>> fetchTokenKeys = PublisherProbe.of(Mono.just(VALID_KEYS)); PublisherProbe<Map<String, String>> fetchTokenKeys = PublisherProbe
.of(Mono.just(VALID_KEYS));
given(this.securityService.fetchTokenKeys()).willReturn(fetchTokenKeys.mono()); given(this.securityService.fetchTokenKeys()).willReturn(fetchTokenKeys.mono());
given(this.securityService.getUaaUrl()) given(this.securityService.getUaaUrl())
.willReturn(Mono.just("http://localhost:8080/uaa")); .willReturn(Mono.just("http://localhost:8080/uaa"));
@ -145,14 +153,17 @@ public class ReactiveTokenValidatorTests {
.create(this.tokenValidator.validate( .create(this.tokenValidator.validate(
new Token(getSignedToken(header.getBytes(), claims.getBytes())))) new Token(getSignedToken(header.getBytes(), claims.getBytes()))))
.verifyComplete(); .verifyComplete();
Object cachedTokenKeys = ReflectionTestUtils.getField(this.tokenValidator, "cachedTokenKeys"); Object cachedTokenKeys = ReflectionTestUtils.getField(this.tokenValidator,
"cachedTokenKeys");
assertThat(cachedTokenKeys).isEqualTo(VALID_KEYS); assertThat(cachedTokenKeys).isEqualTo(VALID_KEYS);
fetchTokenKeys.assertWasSubscribed(); fetchTokenKeys.assertWasSubscribed();
} }
@Test @Test
public void validateTokenWhenCacheEmptyAndInvalidKeyShouldThrowException() throws Exception { public void validateTokenWhenCacheEmptyAndInvalidKeyShouldThrowException()
PublisherProbe<Map<String, String>> fetchTokenKeys = PublisherProbe.of(Mono.just(VALID_KEYS)); throws Exception {
PublisherProbe<Map<String, String>> fetchTokenKeys = PublisherProbe
.of(Mono.just(VALID_KEYS));
given(this.securityService.fetchTokenKeys()).willReturn(fetchTokenKeys.mono()); given(this.securityService.fetchTokenKeys()).willReturn(fetchTokenKeys.mono());
given(this.securityService.getUaaUrl()) given(this.securityService.getUaaUrl())
.willReturn(Mono.just("http://localhost:8080/uaa")); .willReturn(Mono.just("http://localhost:8080/uaa"));
@ -167,7 +178,8 @@ public class ReactiveTokenValidatorTests {
assertThat(((CloudFoundryAuthorizationException) ex).getReason()) assertThat(((CloudFoundryAuthorizationException) ex).getReason())
.isEqualTo(Reason.INVALID_KEY_ID); .isEqualTo(Reason.INVALID_KEY_ID);
}).verify(); }).verify();
Object cachedTokenKeys = ReflectionTestUtils.getField(this.tokenValidator, "cachedTokenKeys"); Object cachedTokenKeys = ReflectionTestUtils.getField(this.tokenValidator,
"cachedTokenKeys");
assertThat(cachedTokenKeys).isEqualTo(VALID_KEYS); assertThat(cachedTokenKeys).isEqualTo(VALID_KEYS);
fetchTokenKeys.assertWasSubscribed(); fetchTokenKeys.assertWasSubscribed();
} }

View File

@ -42,8 +42,8 @@ public class CouchbaseHealthIndicatorTests {
public void couchbaseIsUp() { public void couchbaseIsUp() {
CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class); CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class);
ClusterInfo clusterInfo = mock(ClusterInfo.class); ClusterInfo clusterInfo = mock(ClusterInfo.class);
given(clusterInfo.getAllVersions()).willReturn( given(clusterInfo.getAllVersions())
Arrays.asList(new Version(1, 2, 3))); .willReturn(Arrays.asList(new Version(1, 2, 3)));
given(couchbaseOperations.getCouchbaseClusterInfo()).willReturn(clusterInfo); given(couchbaseOperations.getCouchbaseClusterInfo()).willReturn(clusterInfo);
CouchbaseHealthIndicator healthIndicator = new CouchbaseHealthIndicator( CouchbaseHealthIndicator healthIndicator = new CouchbaseHealthIndicator(
couchbaseOperations); couchbaseOperations);
@ -56,8 +56,8 @@ public class CouchbaseHealthIndicatorTests {
@Test @Test
public void couchbaseIsDown() { public void couchbaseIsDown() {
CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class); CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class);
given(couchbaseOperations.getCouchbaseClusterInfo()).willThrow( given(couchbaseOperations.getCouchbaseClusterInfo())
new IllegalStateException("test, expected")); .willThrow(new IllegalStateException("test, expected"));
CouchbaseHealthIndicator healthIndicator = new CouchbaseHealthIndicator( CouchbaseHealthIndicator healthIndicator = new CouchbaseHealthIndicator(
couchbaseOperations); couchbaseOperations);
Health health = healthIndicator.health(); Health health = healthIndicator.health();

View File

@ -19,7 +19,7 @@ package org.springframework.boot.autoconfigure.data.alt.couchbase;
import org.springframework.boot.autoconfigure.data.couchbase.city.City; import org.springframework.boot.autoconfigure.data.couchbase.city.City;
import org.springframework.data.repository.reactive.ReactiveCrudRepository; import org.springframework.data.repository.reactive.ReactiveCrudRepository;
public interface ReactiveCityCouchbaseRepository extends public interface ReactiveCityCouchbaseRepository
ReactiveCrudRepository<City, Long> { extends ReactiveCrudRepository<City, Long> {
} }