Skip to content

Commit d28e32b

Browse files
jzheauxrwinch
authored andcommitted
NimbusJwtDecoder Builder
A Builder to simply common construction patterns for NimbusJwtDecoder Issue: gh-6010
1 parent fbcf48c commit d28e32b

File tree

11 files changed

+344
-137
lines changed

11 files changed

+344
-137
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3131
import org.springframework.security.oauth2.jwt.Jwt;
3232
import org.springframework.security.oauth2.jwt.JwtDecoder;
33+
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
3334
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
3435
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
3536
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
@@ -43,6 +44,8 @@
4344
import org.springframework.security.web.util.matcher.RequestMatcher;
4445
import org.springframework.util.Assert;
4546

47+
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;
48+
4649
/**
4750
*
4851
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Resource Server Support.
@@ -218,7 +221,7 @@ public JwtConfigurer decoder(JwtDecoder decoder) {
218221
}
219222

220223
public JwtConfigurer jwkSetUri(String uri) {
221-
this.decoder = new NimbusJwtDecoderJwkSupport(uri);
224+
this.decoder = new NimbusJwtDecoder(withJwkSetUri(uri).build());
222225
return this;
223226
}
224227

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java

+7-7
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
import org.springframework.security.oauth2.jwt.JwtDecoder;
7777
import org.springframework.security.oauth2.jwt.JwtException;
7878
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
79-
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
79+
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
8080
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
8181
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
8282
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
@@ -109,6 +109,7 @@
109109
import static org.mockito.Mockito.mock;
110110
import static org.mockito.Mockito.verify;
111111
import static org.mockito.Mockito.when;
112+
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;
112113
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
113114
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
114115
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -737,7 +738,7 @@ public void getJwtDecoderWhenConfiguredWithDecoderAndJwkSetUriThenLastOneWins()
737738
jwtConfigurer.decoder(decoder);
738739
jwtConfigurer.jwkSetUri(JWK_SET_URI);
739740

740-
assertThat(jwtConfigurer.getJwtDecoder()).isInstanceOf(NimbusJwtDecoderJwkSupport.class);
741+
assertThat(jwtConfigurer.getJwtDecoder()).isInstanceOf(NimbusJwtDecoder.class);
741742

742743
}
743744

@@ -770,7 +771,7 @@ public void getJwtDecoderWhenContextHasBeanAndUserConfiguresJwkSetUriThenJwkSetU
770771
jwtConfigurer.jwkSetUri(JWK_SET_URI);
771772

772773
assertThat(jwtConfigurer.getJwtDecoder()).isNotEqualTo(decoder);
773-
assertThat(jwtConfigurer.getJwtDecoder()).isInstanceOf(NimbusJwtDecoderJwkSupport.class);
774+
assertThat(jwtConfigurer.getJwtDecoder()).isInstanceOf(NimbusJwtDecoder.class);
774775
}
775776

776777
@Test
@@ -1460,8 +1461,7 @@ static class CustomJwtValidatorConfig extends WebSecurityConfigurerAdapter {
14601461

14611462
@Override
14621463
protected void configure(HttpSecurity http) throws Exception {
1463-
NimbusJwtDecoderJwkSupport jwtDecoder =
1464-
new NimbusJwtDecoderJwkSupport(this.uri);
1464+
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(this.uri).build());
14651465
jwtDecoder.setJwtValidator(this.jwtValidator);
14661466

14671467
// @formatter:off
@@ -1488,7 +1488,7 @@ protected void configure(HttpSecurity http) throws Exception {
14881488
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1));
14891489
jwtValidator.setClock(nearlyAnHourFromTokenExpiry);
14901490

1491-
NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(this.uri);
1491+
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(this.uri).build());
14921492
jwtDecoder.setJwtValidator(jwtValidator);
14931493

14941494
// @formatter:off
@@ -1511,7 +1511,7 @@ protected void configure(HttpSecurity http) throws Exception {
15111511
JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1));
15121512
jwtValidator.setClock(justOverOneHourAfterExpiry);
15131513

1514-
NimbusJwtDecoderJwkSupport jwtDecoder = new NimbusJwtDecoderJwkSupport(this.uri);
1514+
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(this.uri).build());
15151515
jwtDecoder.setJwtValidator(jwtValidator);
15161516

15171517
// @formatter:off

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@
4343
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
4444
import org.springframework.security.oauth2.jwt.Jwt;
4545
import org.springframework.security.oauth2.jwt.JwtDecoder;
46-
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
46+
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
4747
import org.springframework.util.Assert;
4848
import org.springframework.util.StringUtils;
4949

50+
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;
51+
5052
/**
5153
* An implementation of an {@link AuthenticationProvider}
5254
* for the OpenID Connect Core 1.0 Authorization Code Grant Flow.
@@ -209,7 +211,8 @@ private JwtDecoder getJwtDecoder(ClientRegistration clientRegistration) {
209211
);
210212
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
211213
}
212-
jwtDecoder = new NimbusJwtDecoderJwkSupport(clientRegistration.getProviderDetails().getJwkSetUri());
214+
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
215+
jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(jwkSetUri).build());
213216
this.jwtDecoders.put(clientRegistration.getRegistrationId(), jwtDecoder);
214217
}
215218
return jwtDecoder;

oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoder.java

-1
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,4 @@ public interface JwtDecoder {
4444
* @throws JwtException if an error occurs while attempting to decode the JWT
4545
*/
4646
Jwt decode(String token) throws JwtException;
47-
4847
}

oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
*/
1616
package org.springframework.security.oauth2.jwt;
1717

18+
import java.net.URI;
19+
import java.util.Map;
20+
1821
import org.springframework.core.ParameterizedTypeReference;
1922
import org.springframework.http.RequestEntity;
2023
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
2124
import org.springframework.web.client.RestTemplate;
2225
import org.springframework.web.util.UriComponentsBuilder;
2326

24-
import java.net.URI;
25-
import java.util.Map;
27+
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;
2628

2729
/**
2830
* Allows creating a {@link JwtDecoder} from an
@@ -58,8 +60,8 @@ public static JwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation) {
5860
OAuth2TokenValidator<Jwt> jwtValidator =
5961
JwtValidators.createDefaultWithIssuer(oidcIssuerLocation);
6062

61-
NimbusJwtDecoderJwkSupport jwtDecoder =
62-
new NimbusJwtDecoderJwkSupport(openidConfiguration.get("jwks_uri").toString());
63+
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(
64+
withJwkSetUri(openidConfiguration.get("jwks_uri").toString()).build());
6365
jwtDecoder.setJwtValidator(jwtValidator);
6466

6567
return jwtDecoder;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright 2002-2018 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+
* http://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+
package org.springframework.security.oauth2.jwt;
17+
18+
import java.io.IOException;
19+
import java.net.MalformedURLException;
20+
import java.net.URL;
21+
import java.util.Collections;
22+
23+
import com.nimbusds.jose.JWSAlgorithm;
24+
import com.nimbusds.jose.jwk.source.JWKSource;
25+
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
26+
import com.nimbusds.jose.proc.JWSKeySelector;
27+
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
28+
import com.nimbusds.jose.proc.SecurityContext;
29+
import com.nimbusds.jose.util.Resource;
30+
import com.nimbusds.jose.util.ResourceRetriever;
31+
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
32+
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
33+
import com.nimbusds.jwt.proc.JWTProcessor;
34+
35+
import org.springframework.http.HttpHeaders;
36+
import org.springframework.http.HttpMethod;
37+
import org.springframework.http.MediaType;
38+
import org.springframework.http.RequestEntity;
39+
import org.springframework.http.ResponseEntity;
40+
import org.springframework.util.Assert;
41+
import org.springframework.web.client.RestOperations;
42+
import org.springframework.web.client.RestTemplate;
43+
44+
/**
45+
* A collection of builders for creating Nimbus {@link JWTProcessor} instances.
46+
*
47+
* @author Josh Cummings
48+
* @since 5.2
49+
* @see NimbusJwtDecoder
50+
*/
51+
public final class JwtProcessors {
52+
53+
/**
54+
* Use the given
55+
* <a href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a> uri.
56+
*
57+
* @param jwkSetUri the JWK Set uri to use
58+
* @return a {@link JwtProcessors} for further configurations
59+
*/
60+
public static JwkSetUriJwtProcessorBuilder withJwkSetUri(String jwkSetUri) {
61+
return new JwkSetUriJwtProcessorBuilder(jwkSetUri);
62+
}
63+
64+
/**
65+
* A builder for creating Nimbus {@link JWTProcessor} instances based on a
66+
* <a target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a> uri.
67+
*/
68+
public static final class JwkSetUriJwtProcessorBuilder {
69+
private String jwkSetUri;
70+
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
71+
private RestOperations restOperations = new RestTemplate();
72+
73+
private JwkSetUriJwtProcessorBuilder(String jwkSetUri) {
74+
Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
75+
this.jwkSetUri = jwkSetUri;
76+
}
77+
78+
/**
79+
* Use the given signing
80+
* <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target="_blank">algorithm</a>.
81+
*
82+
* @param jwsAlgorithm the algorithm to use
83+
* @return a {@link JwtProcessors} for further configurations
84+
*/
85+
public JwkSetUriJwtProcessorBuilder jwsAlgorithm(String jwsAlgorithm) {
86+
Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty");
87+
this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm);
88+
return this;
89+
}
90+
91+
/**
92+
* Use the given {@link RestOperations} to coordinate with the authorization servers indicated in the
93+
* <a href="https://tools.ietf.org/html/rfc7517#section-5">JWK Set</a> uri
94+
* as well as the
95+
* <a href="http://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a>.
96+
*
97+
* @param restOperations
98+
* @return
99+
*/
100+
public JwkSetUriJwtProcessorBuilder restOperations(RestOperations restOperations) {
101+
Assert.notNull(restOperations, "restOperations cannot be null");
102+
this.restOperations = restOperations;
103+
return this;
104+
}
105+
106+
/**
107+
* Build the configured {@link JwtDecoder}.
108+
*
109+
* @return the configured {@link JwtDecoder}
110+
*/
111+
public JWTProcessor<SecurityContext> build() {
112+
ResourceRetriever jwkSetRetriever = new RestOperationsResourceRetriever(this.restOperations);
113+
JWKSource<SecurityContext> jwkSource = new RemoteJWKSet<>(toURL(this.jwkSetUri), jwkSetRetriever);
114+
JWSKeySelector<SecurityContext> jwsKeySelector =
115+
new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwkSource);
116+
ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
117+
jwtProcessor.setJWSKeySelector(jwsKeySelector);
118+
119+
// Spring Security validates the claim set independent from Nimbus
120+
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
121+
122+
return jwtProcessor;
123+
}
124+
125+
private static URL toURL(String url) {
126+
try {
127+
return new URL(url);
128+
} catch (MalformedURLException ex) {
129+
throw new IllegalArgumentException("Invalid JWK Set URL \"" + url + "\" : " + ex.getMessage(), ex);
130+
}
131+
}
132+
133+
private static class RestOperationsResourceRetriever implements ResourceRetriever {
134+
private final RestOperations restOperations;
135+
136+
RestOperationsResourceRetriever(RestOperations restOperations) {
137+
Assert.notNull(restOperations, "restOperations cannot be null");
138+
this.restOperations = restOperations;
139+
}
140+
141+
@Override
142+
public Resource retrieveResource(URL url) throws IOException {
143+
HttpHeaders headers = new HttpHeaders();
144+
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
145+
146+
ResponseEntity<String> response;
147+
try {
148+
RequestEntity<Void> request = new RequestEntity<>(headers, HttpMethod.GET, url.toURI());
149+
response = this.restOperations.exchange(request, String.class);
150+
} catch (Exception ex) {
151+
throw new IOException(ex);
152+
}
153+
154+
if (response.getStatusCodeValue() != 200) {
155+
throw new IOException(response.toString());
156+
}
157+
158+
return new Resource(response.getBody(), "UTF-8");
159+
}
160+
}
161+
}
162+
}

oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java

+7
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,15 @@
3838
/**
3939
* A low-level Nimbus implementation of {@link JwtDecoder} which takes a raw Nimbus configuration.
4040
*
41+
* It's simple to produce an instance of {@code JWTProcessor} using {@link JwtProcessors}:
42+
* <pre>
43+
* JWTProcessor&lt;SecurityContext&gt; jwtProcessor = JwtProcessors.fromJwkSetUri(uri).build();
44+
* NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(jwtProcessor);
45+
* </pre>
46+
*
4147
* @author Josh Cummings
4248
* @since 5.2
49+
* @see JwtProcessors
4350
*/
4451
public final class NimbusJwtDecoder implements JwtDecoder {
4552
private static final String DECODING_ERROR_MESSAGE_TEMPLATE =

0 commit comments

Comments
 (0)