Skip to content

Commit d66f995

Browse files
singhbaljitjzheaux
authored andcommitted
add factory methods for Jwt issuer resolvers
Closes gh-13427
1 parent 259ff00 commit d66f995

File tree

6 files changed

+613
-30
lines changed

6 files changed

+613
-30
lines changed

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolver.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
6767
* Construct a {@link JwtIssuerAuthenticationManagerResolver} using the provided
6868
* parameters
6969
* @param trustedIssuers an array of trusted issuers
70+
* @deprecated use {@link #fromTrustedIssuers(String...)}
7071
*/
72+
@Deprecated(since = "6.2", forRemoval = true)
7173
public JwtIssuerAuthenticationManagerResolver(String... trustedIssuers) {
7274
this(Set.of(trustedIssuers));
7375
}
@@ -76,13 +78,45 @@ public JwtIssuerAuthenticationManagerResolver(String... trustedIssuers) {
7678
* Construct a {@link JwtIssuerAuthenticationManagerResolver} using the provided
7779
* parameters
7880
* @param trustedIssuers a collection of trusted issuers
81+
* @deprecated use {@link #fromTrustedIssuers(Collection)}
7982
*/
83+
@Deprecated(since = "6.2", forRemoval = true)
8084
public JwtIssuerAuthenticationManagerResolver(Collection<String> trustedIssuers) {
8185
Assert.notEmpty(trustedIssuers, "trustedIssuers cannot be empty");
8286
this.authenticationManager = new ResolvingAuthenticationManager(
8387
new TrustedIssuerJwtAuthenticationManagerResolver(Set.copyOf(trustedIssuers)::contains));
8488
}
8589

90+
/**
91+
* Construct a {@link JwtIssuerAuthenticationManagerResolver} using the provided
92+
* parameters
93+
* @param trustedIssuers an array of trusted issuers
94+
*/
95+
public static JwtIssuerAuthenticationManagerResolver fromTrustedIssuers(String... trustedIssuers) {
96+
return fromTrustedIssuers(Set.of(trustedIssuers));
97+
}
98+
99+
/**
100+
* Construct a {@link JwtIssuerAuthenticationManagerResolver} using the provided
101+
* parameters
102+
* @param trustedIssuers a collection of trusted issuers
103+
*/
104+
public static JwtIssuerAuthenticationManagerResolver fromTrustedIssuers(Collection<String> trustedIssuers) {
105+
Assert.notEmpty(trustedIssuers, "trustedIssuers cannot be empty");
106+
return fromTrustedIssuers(Set.copyOf(trustedIssuers)::contains);
107+
}
108+
109+
/**
110+
* Construct a {@link JwtIssuerAuthenticationManagerResolver} using the provided
111+
* parameters
112+
* @param trustedIssuers a predicate to validate issuers
113+
*/
114+
public static JwtIssuerAuthenticationManagerResolver fromTrustedIssuers(Predicate<String> trustedIssuers) {
115+
Assert.notNull(trustedIssuers, "trustedIssuers cannot be null");
116+
return new JwtIssuerAuthenticationManagerResolver(
117+
new TrustedIssuerJwtAuthenticationManagerResolver(trustedIssuers));
118+
}
119+
86120
/**
87121
* Construct a {@link JwtIssuerAuthenticationManagerResolver} using the provided
88122
* parameters

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolver.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver
7171
* Construct a {@link JwtIssuerReactiveAuthenticationManagerResolver} using the
7272
* provided parameters
7373
* @param trustedIssuers an array of trusted issuers
74+
* @deprecated use {@link #fromTrustedIssuers(String...)}
7475
*/
76+
@Deprecated(since = "6.2", forRemoval = true)
7577
public JwtIssuerReactiveAuthenticationManagerResolver(String... trustedIssuers) {
7678
this(Set.of(trustedIssuers));
7779
}
@@ -80,13 +82,45 @@ public JwtIssuerReactiveAuthenticationManagerResolver(String... trustedIssuers)
8082
* Construct a {@link JwtIssuerReactiveAuthenticationManagerResolver} using the
8183
* provided parameters
8284
* @param trustedIssuers a collection of trusted issuers
85+
* @deprecated use {@link #fromTrustedIssuers(Collection)}
8386
*/
87+
@Deprecated(since = "6.2", forRemoval = true)
8488
public JwtIssuerReactiveAuthenticationManagerResolver(Collection<String> trustedIssuers) {
8589
Assert.notEmpty(trustedIssuers, "trustedIssuers cannot be empty");
8690
this.authenticationManager = new ResolvingAuthenticationManager(
8791
new TrustedIssuerJwtAuthenticationManagerResolver(Set.copyOf(trustedIssuers)::contains));
8892
}
8993

94+
/**
95+
* Construct a {@link JwtIssuerReactiveAuthenticationManagerResolver} using the
96+
* provided parameters
97+
* @param trustedIssuers an array of trusted issuers
98+
*/
99+
public static JwtIssuerReactiveAuthenticationManagerResolver fromTrustedIssuers(String... trustedIssuers) {
100+
return fromTrustedIssuers(Set.of(trustedIssuers));
101+
}
102+
103+
/**
104+
* Construct a {@link JwtIssuerReactiveAuthenticationManagerResolver} using the
105+
* provided parameters
106+
* @param trustedIssuers a collection of trusted issuers
107+
*/
108+
public static JwtIssuerReactiveAuthenticationManagerResolver fromTrustedIssuers(Collection<String> trustedIssuers) {
109+
Assert.notEmpty(trustedIssuers, "trustedIssuers cannot be empty");
110+
return fromTrustedIssuers(Set.copyOf(trustedIssuers)::contains);
111+
}
112+
113+
/**
114+
* Construct a {@link JwtIssuerReactiveAuthenticationManagerResolver} using the
115+
* provided parameters
116+
* @param trustedIssuers a predicate to validate issuers
117+
*/
118+
public static JwtIssuerReactiveAuthenticationManagerResolver fromTrustedIssuers(Predicate<String> trustedIssuers) {
119+
Assert.notNull(trustedIssuers, "trustedIssuers cannot be null");
120+
return new JwtIssuerReactiveAuthenticationManagerResolver(
121+
new TrustedIssuerJwtAuthenticationManagerResolver(trustedIssuers));
122+
}
123+
90124
/**
91125
* Construct a {@link JwtIssuerReactiveAuthenticationManagerResolver} using the
92126
* provided parameters
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.oauth2.server.resource.authentication;
18+
19+
import java.util.Collection;
20+
import java.util.Collections;
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
24+
import com.nimbusds.jose.JWSAlgorithm;
25+
import com.nimbusds.jose.JWSHeader;
26+
import com.nimbusds.jose.JWSObject;
27+
import com.nimbusds.jose.Payload;
28+
import com.nimbusds.jose.crypto.RSASSASigner;
29+
import com.nimbusds.jwt.JWTClaimsSet;
30+
import com.nimbusds.jwt.PlainJWT;
31+
import net.minidev.json.JSONObject;
32+
import okhttp3.mockwebserver.MockResponse;
33+
import okhttp3.mockwebserver.MockWebServer;
34+
import org.junit.jupiter.api.Test;
35+
36+
import org.springframework.security.authentication.AuthenticationManager;
37+
import org.springframework.security.authentication.AuthenticationManagerResolver;
38+
import org.springframework.security.core.Authentication;
39+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
40+
import org.springframework.security.oauth2.jose.TestKeys;
41+
import org.springframework.security.oauth2.jwt.JwtClaimNames;
42+
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver.TrustedIssuerJwtAuthenticationManagerResolver;
43+
44+
import static org.assertj.core.api.Assertions.assertThat;
45+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
46+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
47+
import static org.mockito.BDDMockito.mock;
48+
import static org.mockito.BDDMockito.verify;
49+
50+
/**
51+
* Tests for {@link JwtIssuerAuthenticationManagerResolver}
52+
*/
53+
@Deprecated
54+
public class JwtIssuerAuthenticationManagerResolverDeprecatedTests {
55+
56+
private static final String DEFAULT_RESPONSE_TEMPLATE = "{\n" + " \"issuer\": \"%s\", \n"
57+
+ " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n" + "}";
58+
59+
private static final String JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"n\":\"3FlqJr5TRskIQIgdE3Dd7D9lboWdcTUT8a-fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRvc5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4_1tfRgG6ii4Uhxh6iI8qNMJQX-fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2kJdJ_ZIV-WW4noDdzpKqHcwmB8FsrumlVY_DNVvUSDIipiq9PbP4H99TXN1o746oRaNa07rq1hoCgMSSy-85SagCoxlmyE-D-of9SsMY8Ol9t0rdzpobBuhyJ_o5dfvjKw\"}]}";
60+
61+
private String jwt = jwt("iss", "trusted");
62+
63+
private String evil = jwt("iss", "\"");
64+
65+
private String noIssuer = jwt("sub", "sub");
66+
67+
@Test
68+
public void resolveWhenUsingTrustedIssuerThenReturnsAuthenticationManager() throws Exception {
69+
try (MockWebServer server = new MockWebServer()) {
70+
server.start();
71+
String issuer = server.url("").toString();
72+
// @formatter:off
73+
server.enqueue(new MockResponse().setResponseCode(200)
74+
.setHeader("Content-Type", "application/json")
75+
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer)
76+
));
77+
server.enqueue(new MockResponse().setResponseCode(200)
78+
.setHeader("Content-Type", "application/json")
79+
.setBody(JWK_SET)
80+
);
81+
server.enqueue(new MockResponse().setResponseCode(200)
82+
.setHeader("Content-Type", "application/json")
83+
.setBody(JWK_SET)
84+
);
85+
// @formatter:on
86+
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
87+
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
88+
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
89+
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver(
90+
issuer);
91+
Authentication token = withBearerToken(jws.serialize());
92+
AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null);
93+
assertThat(authenticationManager).isNotNull();
94+
Authentication authentication = authenticationManager.authenticate(token);
95+
assertThat(authentication.isAuthenticated()).isTrue();
96+
}
97+
}
98+
99+
@Test
100+
public void resolveWhednUsingTrustedIssuerThenReturnsAuthenticationManager() throws Exception {
101+
try (MockWebServer server = new MockWebServer()) {
102+
server.start();
103+
String issuer = server.url("").toString();
104+
// @formatter:off
105+
server.enqueue(new MockResponse().setResponseCode(500)
106+
.setHeader("Content-Type", "application/json")
107+
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer))
108+
);
109+
server.enqueue(new MockResponse().setResponseCode(200)
110+
.setHeader("Content-Type", "application/json")
111+
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer))
112+
);
113+
server.enqueue(new MockResponse().setResponseCode(200)
114+
.setHeader("Content-Type", "application/json")
115+
.setBody(JWK_SET)
116+
);
117+
// @formatter:on
118+
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
119+
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
120+
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
121+
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver(
122+
issuer);
123+
Authentication token = withBearerToken(jws.serialize());
124+
AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null);
125+
assertThat(authenticationManager).isNotNull();
126+
assertThatExceptionOfType(IllegalArgumentException.class)
127+
.isThrownBy(() -> authenticationManager.authenticate(token));
128+
Authentication authentication = authenticationManager.authenticate(token);
129+
assertThat(authentication.isAuthenticated()).isTrue();
130+
}
131+
}
132+
133+
@Test
134+
public void resolveWhenUsingSameIssuerThenReturnsSameAuthenticationManager() throws Exception {
135+
try (MockWebServer server = new MockWebServer()) {
136+
String issuer = server.url("").toString();
137+
server.enqueue(new MockResponse().setResponseCode(200).setHeader("Content-Type", "application/json")
138+
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer)));
139+
server.enqueue(new MockResponse().setResponseCode(200).setHeader("Content-Type", "application/json")
140+
.setBody(JWK_SET));
141+
TrustedIssuerJwtAuthenticationManagerResolver resolver = new TrustedIssuerJwtAuthenticationManagerResolver(
142+
(iss) -> iss.equals(issuer));
143+
AuthenticationManager authenticationManager = resolver.resolve(issuer);
144+
AuthenticationManager cachedAuthenticationManager = resolver.resolve(issuer);
145+
assertThat(authenticationManager).isSameAs(cachedAuthenticationManager);
146+
}
147+
}
148+
149+
@Test
150+
public void resolveWhenUsingUntrustedIssuerThenException() {
151+
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver(
152+
"other", "issuers");
153+
Authentication token = withBearerToken(this.jwt);
154+
// @formatter:off
155+
assertThatExceptionOfType(OAuth2AuthenticationException.class)
156+
.isThrownBy(() -> authenticationManagerResolver.resolve(null).authenticate(token))
157+
.withMessageContaining("Invalid issuer");
158+
// @formatter:on
159+
}
160+
161+
@Test
162+
public void resolveWhenUsingCustomIssuerAuthenticationManagerResolverThenUses() {
163+
AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
164+
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver(
165+
(issuer) -> authenticationManager);
166+
Authentication token = withBearerToken(this.jwt);
167+
authenticationManagerResolver.resolve(null).authenticate(token);
168+
verify(authenticationManager).authenticate(token);
169+
}
170+
171+
@Test
172+
public void resolveWhenUsingExternalSourceThenRespondsToChanges() {
173+
Authentication token = withBearerToken(this.jwt);
174+
Map<String, AuthenticationManager> authenticationManagers = new HashMap<>();
175+
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver(
176+
authenticationManagers::get);
177+
// @formatter:off
178+
assertThatExceptionOfType(OAuth2AuthenticationException.class)
179+
.isThrownBy(() -> authenticationManagerResolver.resolve(null).authenticate(token))
180+
.withMessageContaining("Invalid issuer");
181+
// @formatter:on
182+
AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
183+
authenticationManagers.put("trusted", authenticationManager);
184+
authenticationManagerResolver.resolve(null).authenticate(token);
185+
verify(authenticationManager).authenticate(token);
186+
authenticationManagers.clear();
187+
// @formatter:off
188+
assertThatExceptionOfType(OAuth2AuthenticationException.class)
189+
.isThrownBy(() -> authenticationManagerResolver.resolve(null).authenticate(token))
190+
.withMessageContaining("Invalid issuer");
191+
// @formatter:on
192+
}
193+
194+
@Test
195+
public void resolveWhenBearerTokenMalformedThenException() {
196+
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver(
197+
"trusted");
198+
Authentication token = withBearerToken("jwt");
199+
// @formatter:off
200+
assertThatExceptionOfType(OAuth2AuthenticationException.class)
201+
.isThrownBy(() -> authenticationManagerResolver.resolve(null).authenticate(token))
202+
.withMessageNotContaining("Invalid issuer");
203+
// @formatter:on
204+
}
205+
206+
@Test
207+
public void resolveWhenBearerTokenNoIssuerThenException() {
208+
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver(
209+
"trusted");
210+
Authentication token = withBearerToken(this.noIssuer);
211+
// @formatter:off
212+
assertThatExceptionOfType(OAuth2AuthenticationException.class)
213+
.isThrownBy(() -> authenticationManagerResolver.resolve(null).authenticate(token))
214+
.withMessageContaining("Missing issuer");
215+
// @formatter:on
216+
}
217+
218+
@Test
219+
public void resolveWhenBearerTokenEvilThenGenericException() {
220+
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver(
221+
"trusted");
222+
Authentication token = withBearerToken(this.evil);
223+
// @formatter:off
224+
assertThatExceptionOfType(OAuth2AuthenticationException.class)
225+
.isThrownBy(() -> authenticationManagerResolver
226+
.resolve(null).authenticate(token)
227+
)
228+
.withMessage("Invalid issuer");
229+
// @formatter:on
230+
}
231+
232+
@Test
233+
public void constructorWhenNullOrEmptyIssuersThenException() {
234+
assertThatIllegalArgumentException()
235+
.isThrownBy(() -> new JwtIssuerAuthenticationManagerResolver((Collection) null));
236+
assertThatIllegalArgumentException()
237+
.isThrownBy(() -> new JwtIssuerAuthenticationManagerResolver(Collections.emptyList()));
238+
}
239+
240+
@Test
241+
public void constructorWhenNullAuthenticationManagerResolverThenException() {
242+
assertThatIllegalArgumentException()
243+
.isThrownBy(() -> new JwtIssuerAuthenticationManagerResolver((AuthenticationManagerResolver) null));
244+
}
245+
246+
private Authentication withBearerToken(String token) {
247+
return new BearerTokenAuthenticationToken(token);
248+
}
249+
250+
private String jwt(String claim, String value) {
251+
PlainJWT jwt = new PlainJWT(new JWTClaimsSet.Builder().claim(claim, value).build());
252+
return jwt.serialize();
253+
}
254+
255+
}

0 commit comments

Comments
 (0)