Skip to content

Commit 68f98df

Browse files
committed
Add BadJwtException
Updated NimbusJwtDecoder and NimbusReactiveJwtDecoder to throw. Updated JwtAuthenticationProvider and JwtReactiveAuthenticationManager to catch. Fixes gh-7885
1 parent 78dedf1 commit 68f98df

File tree

12 files changed

+157
-47
lines changed

12 files changed

+157
-47
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
9595
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
9696
import org.springframework.security.oauth2.jose.TestKeys;
97+
import org.springframework.security.oauth2.jwt.BadJwtException;
9798
import org.springframework.security.oauth2.jwt.Jwt;
9899
import org.springframework.security.oauth2.jwt.JwtDecoder;
99100
import org.springframework.security.oauth2.jwt.JwtException;
@@ -256,7 +257,7 @@ public void getWhenUsingDefaultsWithBadJwkEndpointThenInvalidToken()
256257

257258
this.mvc.perform(get("/").with(bearerToken(token)))
258259
.andExpect(status().isUnauthorized())
259-
.andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt: Malformed Jwk set"));
260+
.andExpect(header().string("WWW-Authenticate", "Bearer"));
260261
}
261262

262263
@Test
@@ -269,7 +270,7 @@ public void getWhenUsingDefaultsWithUnavailableJwkEndpointThenInvalidToken()
269270

270271
this.mvc.perform(get("/").with(bearerToken(token)))
271272
.andExpect(status().isUnauthorized())
272-
.andExpect(invalidTokenHeader("Invalid token"));
273+
.andExpect(header().string("WWW-Authenticate", "Bearer"));
273274
}
274275

275276
@Test
@@ -1099,7 +1100,7 @@ public void requestWhenUsingCustomAuthenticationEventPublisherThenUses() throws
10991100
this.spring.register(CustomAuthenticationEventPublisher.class).autowire();
11001101

11011102
when(bean(JwtDecoder.class).decode(anyString()))
1102-
.thenThrow(new JwtException("problem"));
1103+
.thenThrow(new BadJwtException("problem"));
11031104

11041105
this.mvc.perform(get("/").with(bearerToken("token")));
11051106

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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.jwt;
18+
19+
/**
20+
* An exception similar to {@link org.springframework.security.authentication.BadCredentialsException}
21+
* that indicates a {@link Jwt} that is invalid in some way.
22+
*
23+
* @author Josh Cummings
24+
* @since 5.3
25+
*/
26+
public class BadJwtException extends JwtException {
27+
public BadJwtException(String message) {
28+
super(message);
29+
}
30+
31+
public BadJwtException(String message, Throwable cause) {
32+
super(message, cause);
33+
}
34+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
2929
* @author Josh Cummings
3030
* @since 5.1
3131
*/
32-
public class JwtValidationException extends JwtException {
32+
public class JwtValidationException extends BadJwtException {
3333
private final Collection<OAuth2Error> errors;
3434

3535
/**

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
import java.util.function.Consumer;
3232
import javax.crypto.SecretKey;
3333

34+
import com.nimbusds.jose.JOSEException;
3435
import com.nimbusds.jose.JWSAlgorithm;
3536
import com.nimbusds.jose.RemoteKeySourceException;
3637
import com.nimbusds.jose.jwk.source.JWKSource;
@@ -120,7 +121,7 @@ public void setClaimSetConverter(Converter<Map<String, Object>, Map<String, Obje
120121
public Jwt decode(String token) throws JwtException {
121122
JWT jwt = parse(token);
122123
if (jwt instanceof PlainJWT) {
123-
throw new JwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
124+
throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
124125
}
125126
Jwt createdJwt = createJwt(token, jwt);
126127
return validateJwt(createdJwt);
@@ -130,7 +131,7 @@ private JWT parse(String token) {
130131
try {
131132
return JWTParser.parse(token);
132133
} catch (Exception ex) {
133-
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
134+
throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
134135
}
135136
}
136137

@@ -152,11 +153,13 @@ private Jwt createJwt(String token, JWT parsedJwt) {
152153
} else {
153154
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
154155
}
156+
} catch (JOSEException ex) {
157+
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
155158
} catch (Exception ex) {
156159
if (ex.getCause() instanceof ParseException) {
157-
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed payload"));
160+
throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed payload"));
158161
} else {
159-
throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
162+
throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
160163
}
161164
}
162165
}

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

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -136,7 +136,7 @@ public void setClaimSetConverter(Converter<Map<String, Object>, Map<String, Obje
136136
public Mono<Jwt> decode(String token) throws JwtException {
137137
JWT jwt = parse(token);
138138
if (jwt instanceof PlainJWT) {
139-
throw new JwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
139+
throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
140140
}
141141
return this.decode(jwt);
142142
}
@@ -145,7 +145,7 @@ private JWT parse(String token) {
145145
try {
146146
return JWTParser.parse(token);
147147
} catch (Exception ex) {
148-
throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
148+
throw new BadJwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
149149
}
150150
}
151151

@@ -155,19 +155,25 @@ private Mono<Jwt> decode(JWT parsedToken) {
155155
.map(set -> createJwt(parsedToken, set))
156156
.map(this::validateJwt)
157157
.onErrorMap(e -> !(e instanceof IllegalStateException) && !(e instanceof JwtException), e -> new JwtException("An error occurred while attempting to decode the Jwt: ", e));
158+
} catch (JwtException ex) {
159+
throw ex;
158160
} catch (RuntimeException ex) {
159161
throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
160162
}
161163
}
162164

163165
private Jwt createJwt(JWT parsedJwt, JWTClaimsSet jwtClaimsSet) {
164-
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
165-
Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
166+
try {
167+
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
168+
Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
166169

167-
return Jwt.withTokenValue(parsedJwt.getParsedString())
168-
.headers(h -> h.putAll(headers))
169-
.claims(c -> c.putAll(claims))
170-
.build();
170+
return Jwt.withTokenValue(parsedJwt.getParsedString())
171+
.headers(h -> h.putAll(headers))
172+
.claims(c -> c.putAll(claims))
173+
.build();
174+
} catch (Exception ex) {
175+
throw new BadJwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
176+
}
171177
}
172178

173179
private Jwt validateJwt(Jwt jwt) {
@@ -345,7 +351,7 @@ private Set<JWSAlgorithm> getExpectedJwsAlgorithms(JWSKeySelector<?> jwsKeySelec
345351

346352
private JWKSelector createSelector(Set<JWSAlgorithm> expectedJwsAlgorithms, Header header) {
347353
if (!expectedJwsAlgorithms.contains(header.getAlgorithm())) {
348-
throw new JwtException("Unsupported algorithm of " + header.getAlgorithm());
354+
throw new BadJwtException("Unsupported algorithm of " + header.getAlgorithm());
349355
}
350356

351357
return new JWKSelector(JWKMatcher.forJWSHeader((JWSHeader) header));
@@ -514,7 +520,7 @@ Converter<JWT, Mono<JWTClaimsSet>> processor() {
514520
.collectList()
515521
.map(jwks -> createClaimsSet(jwtProcessor, jwt, new JWKSecurityContext(jwks)));
516522
}
517-
throw new JwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
523+
throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
518524
};
519525
}
520526
}
@@ -524,7 +530,10 @@ private static <C extends SecurityContext> JWTClaimsSet createClaimsSet(JWTProce
524530
try {
525531
return jwtProcessor.process(parsedToken, context);
526532
}
527-
catch (BadJOSEException | JOSEException e) {
533+
catch (BadJOSEException e) {
534+
throw new BadJwtException("Failed to validate the token", e);
535+
}
536+
catch (JOSEException e) {
528537
throw new JwtException("Failed to validate the token", e);
529538
}
530539
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@ final class SingleKeyJWSKeySelector<C extends SecurityContext> implements JWSKey
4747
@Override
4848
public List<? extends Key> selectJWSKeys(JWSHeader header, C context) {
4949
if (!this.expectedJwsAlgorithm.equals(header.getAlgorithm())) {
50-
throw new IllegalArgumentException("Unsupported algorithm of " + header.getAlgorithm());
50+
throw new BadJwtException("Unsupported algorithm of " + header.getAlgorithm());
5151
}
5252
return this.keySet;
5353
}

oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -132,7 +132,7 @@ public void setJwtValidatorWhenNullThenThrowIllegalArgumentException() {
132132
@Test
133133
public void decodeWhenJwtInvalidThenThrowJwtException() {
134134
assertThatThrownBy(() -> this.jwtDecoder.decode("invalid"))
135-
.isInstanceOf(JwtException.class);
135+
.isInstanceOf(BadJwtException.class);
136136
}
137137

138138
// gh-5168
@@ -152,14 +152,14 @@ public void decodeWhenIatClaimNullThenDoesNotThrowException() {
152152
@Test
153153
public void decodeWhenPlainJwtThenExceptionDoesNotMentionClass() {
154154
assertThatCode(() -> this.jwtDecoder.decode(UNSIGNED_JWT))
155-
.isInstanceOf(JwtException.class)
155+
.isInstanceOf(BadJwtException.class)
156156
.hasMessageContaining("Unsupported algorithm of none");
157157
}
158158

159159
@Test
160160
public void decodeWhenJwtIsMalformedThenReturnsStockException() {
161161
assertThatCode(() -> this.jwtDecoder.decode(MALFORMED_JWT))
162-
.isInstanceOf(JwtException.class)
162+
.isInstanceOf(BadJwtException.class)
163163
.hasMessage("An error occurred while attempting to decode the Jwt: Malformed payload");
164164
}
165165

@@ -205,6 +205,18 @@ public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenByClaimSetConverter()
205205
assertThat(jwt.getClaims().get("custom")).isEqualTo("value");
206206
}
207207

208+
// gh-7885
209+
@Test
210+
public void decodeWhenClaimSetConverterFailsThenBadJwtException() {
211+
Converter<Map<String, Object>, Map<String, Object>> claimSetConverter = mock(Converter.class);
212+
this.jwtDecoder.setClaimSetConverter(claimSetConverter);
213+
214+
when(claimSetConverter.convert(any(Map.class))).thenThrow(new IllegalArgumentException("bad conversion"));
215+
216+
assertThatCode(() -> this.jwtDecoder.decode(SIGNED_JWT))
217+
.isInstanceOf(BadJwtException.class);
218+
}
219+
208220
@Test
209221
public void decodeWhenSignedThenOk() {
210222
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withSigning(JWK_SET));
@@ -217,6 +229,7 @@ public void decodeWhenJwkResponseIsMalformedThenReturnsStockException() {
217229
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withSigning(MALFORMED_JWK_SET));
218230
assertThatCode(() -> jwtDecoder.decode(SIGNED_JWT))
219231
.isInstanceOf(JwtException.class)
232+
.isNotInstanceOf(BadJwtException.class)
220233
.hasMessage("An error occurred while attempting to decode the Jwt: Malformed Jwk set");
221234
}
222235

@@ -229,6 +242,7 @@ public void decodeWhenJwkEndpointIsUnresponsiveThenReturnsJwtException() throws
229242
server.shutdown();
230243
assertThatCode(() -> jwtDecoder.decode(SIGNED_JWT))
231244
.isInstanceOf(JwtException.class)
245+
.isNotInstanceOf(BadJwtException.class)
232246
.hasMessageContaining("An error occurred while attempting to decode the Jwt");
233247
}
234248
}
@@ -301,7 +315,7 @@ public void decodeWhenUsingPublicKeyWithKidThenStillUsesKey() throws Exception {
301315
public void decodeWhenSignatureMismatchesAlgorithmThenThrowsException() throws Exception {
302316
NimbusJwtDecoder decoder = withPublicKey(key()).signatureAlgorithm(SignatureAlgorithm.RS512).build();
303317
Assertions.assertThatCode(() -> decoder.decode(RS256_SIGNED_JWT))
304-
.isInstanceOf(JwtException.class);
318+
.isInstanceOf(BadJwtException.class);
305319
}
306320

307321
@Test
@@ -345,7 +359,7 @@ public void decodeWhenUsingSecretKeyAndIncorrectAlgorithmThenThrowsJwtException(
345359
SignedJWT signedJWT = signedJwt(secretKey, macAlgorithm, claimsSet);
346360
NimbusJwtDecoder decoder = withSecretKey(secretKey).macAlgorithm(MacAlgorithm.HS512).build();
347361
assertThatThrownBy(() -> decoder.decode(signedJWT.serialize()))
348-
.isInstanceOf(JwtException.class)
362+
.isInstanceOf(BadJwtException.class)
349363
.hasMessageContaining("Unsupported algorithm of HS256");
350364
}
351365

0 commit comments

Comments
 (0)