mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 09:12:14 +00:00
Add JwtIssuerReactiveAuthenticationManagerResolver
Fixes gh-7857
This commit is contained in:
parent
8c0b754a49
commit
a90e579350
@ -1005,6 +1005,77 @@ ReactiveOpaqueTokenIntrospector introspector() {
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[oauth2resourceserver-multitenancy]]
|
||||||
|
== Multi-tenancy
|
||||||
|
|
||||||
|
A resource server is considered multi-tenant when there are multiple strategies for verifying a bearer token, keyed by some tenant identifier.
|
||||||
|
|
||||||
|
For example, your resource server may accept bearer tokens from two different authorization servers.
|
||||||
|
Or, your authorization server may represent a multiplicity of issuers.
|
||||||
|
|
||||||
|
In each case, there are two things that need to be done and trade-offs associated with how you choose to do them:
|
||||||
|
|
||||||
|
1. Resolve the tenant
|
||||||
|
2. Propagate the tenant
|
||||||
|
|
||||||
|
=== Resolving the Tenant By Claim
|
||||||
|
|
||||||
|
One way to differentiate tenants is by the issuer claim. Since the issuer claim accompanies signed JWTs, this can be done with the `JwtIssuerReactiveAuthenticationManagerResolver`, like so:
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver
|
||||||
|
("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo");
|
||||||
|
|
||||||
|
http
|
||||||
|
.authorizeRequests(authorize -> authorize
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.oauth2ResourceServer(oauth2 -> oauth2
|
||||||
|
.authenticationManagerResolver(authenticationManagerResolver)
|
||||||
|
);
|
||||||
|
----
|
||||||
|
|
||||||
|
This is nice because the issuer endpoints are loaded lazily.
|
||||||
|
In fact, the corresponding `JwtReactiveAuthenticationManager` is instantiated only when the first request with the corresponding issuer is sent.
|
||||||
|
This allows for an application startup that is independent from those authorization servers being up and available.
|
||||||
|
|
||||||
|
==== Dynamic Tenants
|
||||||
|
|
||||||
|
Of course, you may not want to restart the application each time a new tenant is added.
|
||||||
|
In this case, you can configure the `JwtIssuerReactiveAuthenticationManagerResolver` with a repository of `ReactiveAuthenticationManager` instances, which you can edit at runtime, like so:
|
||||||
|
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
private Mono<ReactiveAuthenticationManager> addManager(
|
||||||
|
Map<String, ReactiveAuthenticationManager> authenticationManagers, String issuer) {
|
||||||
|
|
||||||
|
return Mono.fromCallable(() -> ReactiveJwtDecoders.fromIssuerLocation(issuer))
|
||||||
|
.subscribeOn(Schedulers.boundedElastic())
|
||||||
|
.map(JwtReactiveAuthenticationManager::new)
|
||||||
|
.doOnNext(authenticationManager -> authenticationManagers.put(issuer, authenticationManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =
|
||||||
|
new JwtIssuerReactiveAuthenticationManagerResolver(authenticationManagers::get);
|
||||||
|
|
||||||
|
http
|
||||||
|
.authorizeRequests(authorize -> authorize
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.oauth2ResourceServer(oauth2 -> oauth2
|
||||||
|
.authenticationManagerResolver(authenticationManagerResolver)
|
||||||
|
);
|
||||||
|
----
|
||||||
|
|
||||||
|
In this case, you construct `JwtIssuerReactiveAuthenticationManagerResolver` with a strategy for obtaining the `ReactiveAuthenticationManager` given the issuer.
|
||||||
|
This approach allows us to add and remove elements from the repository (shown as a `Map` in the snippet) at runtime.
|
||||||
|
|
||||||
|
NOTE: It would be unsafe to simply take any issuer and construct an `ReactiveAuthenticationManager` from it.
|
||||||
|
The issuer should be one that the code can verify from a trusted source like a whitelist.
|
||||||
|
|
||||||
== Bearer Token Propagation
|
== Bearer Token Propagation
|
||||||
|
|
||||||
Now that you're in possession of a bearer token, it might be handy to pass that to downstream services.
|
Now that you're in possession of a bearer token, it might be handy to pass that to downstream services.
|
||||||
|
@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-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.resource.authentication;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import com.nimbusds.jwt.JWTParser;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders;
|
||||||
|
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
||||||
|
import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of {@link ReactiveAuthenticationManagerResolver} that resolves a JWT-based
|
||||||
|
* {@link ReactiveAuthenticationManager} based on the
|
||||||
|
* <a href="https://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a> in a
|
||||||
|
* signed JWT (JWS).
|
||||||
|
*
|
||||||
|
* To use, this class must be able to determine whether or not the `iss` claim is trusted. Recall that
|
||||||
|
* anyone can stand up an authorization server and issue valid tokens to a resource server. The simplest way
|
||||||
|
* to achieve this is to supply a whitelist of trusted issuers in the constructor.
|
||||||
|
*
|
||||||
|
* This class derives the Issuer from the `iss` claim found in the {@link ServerWebExchange}'s
|
||||||
|
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer Token</a>.
|
||||||
|
*
|
||||||
|
* @author Josh Cummings
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public final class JwtIssuerReactiveAuthenticationManagerResolver
|
||||||
|
implements ReactiveAuthenticationManagerResolver<ServerWebExchange> {
|
||||||
|
|
||||||
|
private final ReactiveAuthenticationManagerResolver<String> issuerAuthenticationManagerResolver;
|
||||||
|
private final Converter<ServerWebExchange, Mono<String>> issuerConverter = new JwtClaimIssuerConverter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a {@link JwtIssuerReactiveAuthenticationManagerResolver} using the provided parameters
|
||||||
|
*
|
||||||
|
* @param trustedIssuers a whitelist of trusted issuers
|
||||||
|
*/
|
||||||
|
public JwtIssuerReactiveAuthenticationManagerResolver(String... trustedIssuers) {
|
||||||
|
this(Arrays.asList(trustedIssuers));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a {@link JwtIssuerReactiveAuthenticationManagerResolver} using the provided parameters
|
||||||
|
*
|
||||||
|
* @param trustedIssuers a whitelist of trusted issuers
|
||||||
|
*/
|
||||||
|
public JwtIssuerReactiveAuthenticationManagerResolver(Collection<String> trustedIssuers) {
|
||||||
|
Assert.notEmpty(trustedIssuers, "trustedIssuers cannot be empty");
|
||||||
|
this.issuerAuthenticationManagerResolver =
|
||||||
|
new TrustedIssuerJwtAuthenticationManagerResolver
|
||||||
|
(Collections.unmodifiableCollection(trustedIssuers)::contains);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a {@link JwtIssuerReactiveAuthenticationManagerResolver} using the provided parameters
|
||||||
|
*
|
||||||
|
* Note that the {@link ReactiveAuthenticationManagerResolver} provided in this constructor will need to
|
||||||
|
* verify that the issuer is trusted. This should be done via a whitelist.
|
||||||
|
*
|
||||||
|
* One way to achieve this is with a {@link Map} where the keys are the known issuers:
|
||||||
|
* <pre>
|
||||||
|
* Map<String, ReactiveAuthenticationManager> authenticationManagers = new HashMap<>();
|
||||||
|
* authenticationManagers.put("https://issuerOne.example.org", managerOne);
|
||||||
|
* authenticationManagers.put("https://issuerTwo.example.org", managerTwo);
|
||||||
|
* JwtIssuerReactiveAuthenticationManagerResolver resolver = new JwtIssuerReactiveAuthenticationManagerResolver
|
||||||
|
* (issuer -> Mono.justOrEmpty(authenticationManagers.get(issuer));
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* The keys in the {@link Map} are the whitelist.
|
||||||
|
*
|
||||||
|
* @param issuerAuthenticationManagerResolver a strategy for resolving the {@link ReactiveAuthenticationManager}
|
||||||
|
* by the issuer
|
||||||
|
*/
|
||||||
|
public JwtIssuerReactiveAuthenticationManagerResolver
|
||||||
|
(ReactiveAuthenticationManagerResolver<String> issuerAuthenticationManagerResolver) {
|
||||||
|
|
||||||
|
Assert.notNull(issuerAuthenticationManagerResolver, "issuerAuthenticationManagerResolver cannot be null");
|
||||||
|
this.issuerAuthenticationManagerResolver = issuerAuthenticationManagerResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an {@link AuthenticationManager} based off of the `iss` claim found in the request's bearer token
|
||||||
|
*
|
||||||
|
* @throws OAuth2AuthenticationException if the bearer token is malformed or an {@link ReactiveAuthenticationManager}
|
||||||
|
* can't be derived from the issuer
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Mono<ReactiveAuthenticationManager> resolve(ServerWebExchange exchange) {
|
||||||
|
return this.issuerConverter.convert(exchange)
|
||||||
|
.flatMap(issuer ->
|
||||||
|
this.issuerAuthenticationManagerResolver.resolve(issuer).switchIfEmpty(
|
||||||
|
Mono.error(new InvalidBearerTokenException("Invalid issuer " + issuer)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class JwtClaimIssuerConverter
|
||||||
|
implements Converter<ServerWebExchange, Mono<String>> {
|
||||||
|
|
||||||
|
private final ServerBearerTokenAuthenticationConverter converter =
|
||||||
|
new ServerBearerTokenAuthenticationConverter();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<String> convert(@NonNull ServerWebExchange exchange) {
|
||||||
|
return this.converter.convert(exchange)
|
||||||
|
.cast(BearerTokenAuthenticationToken.class)
|
||||||
|
.flatMap(this::issuer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<String> issuer(BearerTokenAuthenticationToken token) {
|
||||||
|
try {
|
||||||
|
String issuer = JWTParser.parse(token.getToken()).getJWTClaimsSet().getIssuer();
|
||||||
|
return Mono.justOrEmpty(issuer).switchIfEmpty(
|
||||||
|
Mono.error(new InvalidBearerTokenException("Missing issuer")));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Mono.error(new InvalidBearerTokenException(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TrustedIssuerJwtAuthenticationManagerResolver
|
||||||
|
implements ReactiveAuthenticationManagerResolver<String> {
|
||||||
|
|
||||||
|
private final Map<String, Mono<? extends ReactiveAuthenticationManager>> authenticationManagers =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
private final Predicate<String> trustedIssuer;
|
||||||
|
|
||||||
|
TrustedIssuerJwtAuthenticationManagerResolver(Predicate<String> trustedIssuer) {
|
||||||
|
this.trustedIssuer = trustedIssuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ReactiveAuthenticationManager> resolve(String issuer) {
|
||||||
|
return Mono.just(issuer)
|
||||||
|
.filter(this.trustedIssuer)
|
||||||
|
.flatMap(iss ->
|
||||||
|
this.authenticationManagers.computeIfAbsent(iss, k ->
|
||||||
|
Mono.fromCallable(() -> ReactiveJwtDecoders.fromIssuerLocation(iss))
|
||||||
|
.subscribeOn(Schedulers.boundedElastic())
|
||||||
|
.map(JwtReactiveAuthenticationManager::new)
|
||||||
|
.cache())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-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.resource.authentication;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.nimbusds.jose.JWSAlgorithm;
|
||||||
|
import com.nimbusds.jose.JWSHeader;
|
||||||
|
import com.nimbusds.jose.JWSObject;
|
||||||
|
import com.nimbusds.jose.Payload;
|
||||||
|
import com.nimbusds.jose.crypto.RSASSASigner;
|
||||||
|
import com.nimbusds.jwt.JWTClaimsSet;
|
||||||
|
import com.nimbusds.jwt.PlainJWT;
|
||||||
|
import net.minidev.json.JSONObject;
|
||||||
|
import okhttp3.mockwebserver.MockResponse;
|
||||||
|
import okhttp3.mockwebserver.MockWebServer;
|
||||||
|
import org.junit.Test;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||||
|
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||||
|
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.jose.TestKeys;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link JwtIssuerReactiveAuthenticationManagerResolver}
|
||||||
|
*/
|
||||||
|
public class JwtIssuerReactiveAuthenticationManagerResolverTests {
|
||||||
|
private static final String DEFAULT_RESPONSE_TEMPLATE = "{\n"
|
||||||
|
+ " \"issuer\": \"%s\", \n"
|
||||||
|
+ " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n"
|
||||||
|
+ "}";
|
||||||
|
|
||||||
|
private String jwt = jwt("iss", "trusted");
|
||||||
|
private String evil = jwt("iss", "\"");
|
||||||
|
private String noIssuer = jwt("sub", "sub");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveWhenUsingTrustedIssuerThenReturnsAuthenticationManager() throws Exception {
|
||||||
|
try (MockWebServer server = new MockWebServer()) {
|
||||||
|
String issuer = server.url("").toString();
|
||||||
|
server.enqueue(new MockResponse()
|
||||||
|
.setResponseCode(200)
|
||||||
|
.setHeader("Content-Type", "application/json")
|
||||||
|
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer)));
|
||||||
|
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
|
||||||
|
new Payload(new JSONObject(Collections.singletonMap(ISS, issuer))));
|
||||||
|
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
|
||||||
|
|
||||||
|
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =
|
||||||
|
new JwtIssuerReactiveAuthenticationManagerResolver(issuer);
|
||||||
|
MockServerWebExchange exchange = withBearerToken(jws.serialize());
|
||||||
|
|
||||||
|
ReactiveAuthenticationManager authenticationManager =
|
||||||
|
authenticationManagerResolver.resolve(exchange).block();
|
||||||
|
assertThat(authenticationManager).isNotNull();
|
||||||
|
|
||||||
|
ReactiveAuthenticationManager cachedAuthenticationManager =
|
||||||
|
authenticationManagerResolver.resolve(exchange).block();
|
||||||
|
assertThat(authenticationManager).isSameAs(cachedAuthenticationManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveWhenUsingUntrustedIssuerThenException() {
|
||||||
|
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =
|
||||||
|
new JwtIssuerReactiveAuthenticationManagerResolver("other", "issuers");
|
||||||
|
MockServerWebExchange exchange = withBearerToken(this.jwt);
|
||||||
|
|
||||||
|
assertThatCode(() -> authenticationManagerResolver.resolve(exchange).block())
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.hasMessageContaining("Invalid issuer");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveWhenUsingCustomIssuerAuthenticationManagerResolverThenUses() {
|
||||||
|
ReactiveAuthenticationManager authenticationManager = mock(ReactiveAuthenticationManager.class);
|
||||||
|
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =
|
||||||
|
new JwtIssuerReactiveAuthenticationManagerResolver(issuer -> Mono.just(authenticationManager));
|
||||||
|
MockServerWebExchange exchange = withBearerToken(this.jwt);
|
||||||
|
|
||||||
|
assertThat(authenticationManagerResolver.resolve(exchange).block())
|
||||||
|
.isSameAs(authenticationManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveWhenUsingExternalSourceThenRespondsToChanges() {
|
||||||
|
MockServerWebExchange exchange = withBearerToken(this.jwt);
|
||||||
|
|
||||||
|
Map<String, ReactiveAuthenticationManager> authenticationManagers = new HashMap<>();
|
||||||
|
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =
|
||||||
|
new JwtIssuerReactiveAuthenticationManagerResolver(issuer -> Mono.justOrEmpty(authenticationManagers.get(issuer)));
|
||||||
|
assertThatCode(() -> authenticationManagerResolver.resolve(exchange).block())
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.hasMessageContaining("Invalid issuer");
|
||||||
|
|
||||||
|
ReactiveAuthenticationManager authenticationManager = mock(ReactiveAuthenticationManager.class);
|
||||||
|
authenticationManagers.put("trusted", authenticationManager);
|
||||||
|
assertThat(authenticationManagerResolver.resolve(exchange).block())
|
||||||
|
.isSameAs(authenticationManager);
|
||||||
|
|
||||||
|
authenticationManagers.clear();
|
||||||
|
assertThatCode(() -> authenticationManagerResolver.resolve(exchange).block())
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.hasMessageContaining("Invalid issuer");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveWhenBearerTokenMalformedThenException() {
|
||||||
|
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =
|
||||||
|
new JwtIssuerReactiveAuthenticationManagerResolver("trusted");
|
||||||
|
MockServerWebExchange exchange = withBearerToken("jwt");
|
||||||
|
assertThatCode(() -> authenticationManagerResolver.resolve(exchange).block())
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.hasMessageNotContaining("Invalid issuer");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveWhenBearerTokenNoIssuerThenException() {
|
||||||
|
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =
|
||||||
|
new JwtIssuerReactiveAuthenticationManagerResolver("trusted");
|
||||||
|
MockServerWebExchange exchange = withBearerToken(this.noIssuer);
|
||||||
|
assertThatCode(() -> authenticationManagerResolver.resolve(exchange).block())
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.hasMessageContaining("Missing issuer");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveWhenBearerTokenEvilThenGenericException() {
|
||||||
|
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =
|
||||||
|
new JwtIssuerReactiveAuthenticationManagerResolver("trusted");
|
||||||
|
MockServerWebExchange exchange = withBearerToken(this.evil);
|
||||||
|
assertThatCode(() -> authenticationManagerResolver.resolve(exchange).block())
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.hasMessage("Invalid token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenNullOrEmptyIssuersThenException() {
|
||||||
|
assertThatCode(() -> new JwtIssuerReactiveAuthenticationManagerResolver((Collection) null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
assertThatCode(() -> new JwtIssuerReactiveAuthenticationManagerResolver(Collections.emptyList()))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenNullAuthenticationManagerResolverThenException() {
|
||||||
|
assertThatCode(() -> new JwtIssuerReactiveAuthenticationManagerResolver((ReactiveAuthenticationManagerResolver) null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String jwt(String claim, String value) {
|
||||||
|
PlainJWT jwt = new PlainJWT(new JWTClaimsSet.Builder().claim(claim, value).build());
|
||||||
|
return jwt.serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockServerWebExchange withBearerToken(String token) {
|
||||||
|
MockServerHttpRequest request = MockServerHttpRequest.get("/")
|
||||||
|
.header("Authorization", "Bearer " + token).build();
|
||||||
|
return MockServerWebExchange.from(request);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user