Polish Bearer Token Error Handling

Issue gh-7822
Issue gh-7823
This commit is contained in:
Josh Cummings 2020-01-13 16:03:28 -07:00
parent 1b15f74f57
commit 3e07b35611
No known key found for this signature in database
GPG Key ID: 49EF60DD7FF83443
13 changed files with 46 additions and 152 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -269,7 +269,7 @@ public class OAuth2ResourceServerConfigurerTests {
this.mvc.perform(get("/").with(bearerToken(token)))
.andExpect(status().isUnauthorized())
.andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt"));
.andExpect(invalidTokenHeader("Invalid token"));
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* 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.
@ -18,20 +18,16 @@ package org.springframework.security.oauth2.server.resource.authentication;
import java.util.Collection;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.util.Assert;
/**
@ -63,9 +59,6 @@ public final class JwtAuthenticationProvider implements AuthenticationProvider {
private Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter = new JwtAuthenticationConverter();
private static final OAuth2Error DEFAULT_INVALID_TOKEN =
invalidToken("An error occurred while attempting to decode the Jwt: Invalid token");
public JwtAuthenticationProvider(JwtDecoder jwtDecoder) {
Assert.notNull(jwtDecoder, "jwtDecoder cannot be null");
this.jwtDecoder = jwtDecoder;
@ -88,8 +81,7 @@ public final class JwtAuthenticationProvider implements AuthenticationProvider {
try {
jwt = this.jwtDecoder.decode(bearer.getToken());
} catch (JwtException failed) {
OAuth2Error invalidToken = invalidToken(failed.getMessage());
throw new OAuth2AuthenticationException(invalidToken, invalidToken.getDescription(), failed);
throw new InvalidBearerTokenException(failed.getMessage(), failed);
}
AbstractAuthenticationToken token = this.jwtAuthenticationConverter.convert(jwt);
@ -112,17 +104,4 @@ public final class JwtAuthenticationProvider implements AuthenticationProvider {
Assert.notNull(jwtAuthenticationConverter, "jwtAuthenticationConverter cannot be null");
this.jwtAuthenticationConverter = jwtAuthenticationConverter;
}
private static OAuth2Error invalidToken(String message) {
try {
return new BearerTokenError(
BearerTokenErrorCodes.INVALID_TOKEN,
HttpStatus.UNAUTHORIZED,
message,
"https://tools.ietf.org/html/rfc6750#section-3.1");
} catch (IllegalArgumentException malformed) {
// some third-party library error messages are not suitable for RFC 6750's error message charset
return DEFAULT_INVALID_TOKEN;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -27,16 +27,13 @@ import javax.servlet.http.HttpServletRequest;
import com.nimbusds.jwt.JWTParser;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpStatus;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoders;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.util.Assert;
@ -57,8 +54,6 @@ import org.springframework.util.Assert;
* @since 5.3
*/
public final class JwtIssuerAuthenticationManagerResolver implements AuthenticationManagerResolver<HttpServletRequest> {
private static final OAuth2Error DEFAULT_INVALID_TOKEN = invalidToken("Invalid token");
private final AuthenticationManagerResolver<String> issuerAuthenticationManagerResolver;
private final Converter<HttpServletRequest, String> issuerConverter = new JwtClaimIssuerConverter();
@ -118,7 +113,7 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
String issuer = this.issuerConverter.convert(request);
AuthenticationManager authenticationManager = this.issuerAuthenticationManagerResolver.resolve(issuer);
if (authenticationManager == null) {
throw new OAuth2AuthenticationException(invalidToken("Invalid issuer " + issuer));
throw new InvalidBearerTokenException("Invalid issuer");
}
return authenticationManager;
}
@ -137,9 +132,9 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
return issuer;
}
} catch (Exception e) {
throw new OAuth2AuthenticationException(invalidToken(e.getMessage()));
throw new InvalidBearerTokenException(e.getMessage(), e);
}
throw new OAuth2AuthenticationException(invalidToken("Missing issuer"));
throw new InvalidBearerTokenException("Missing issuer");
}
}
@ -164,17 +159,4 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
return null;
}
}
private static OAuth2Error invalidToken(String message) {
try {
return new BearerTokenError(
BearerTokenErrorCodes.INVALID_TOKEN,
HttpStatus.UNAUTHORIZED,
message,
"https://tools.ietf.org/html/rfc6750#section-3.1");
} catch (IllegalArgumentException malformed) {
// some third-party library error messages are not suitable for RFC 6750's error message charset
return DEFAULT_INVALID_TOKEN;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* 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.
@ -19,18 +19,15 @@ package org.springframework.security.oauth2.server.resource.authentication;
import reactor.core.publisher.Mono;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.util.Assert;
/**
@ -45,9 +42,6 @@ public final class JwtReactiveAuthenticationManager implements ReactiveAuthentic
private Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter
= new ReactiveJwtAuthenticationConverterAdapter(new JwtAuthenticationConverter());
private static final OAuth2Error DEFAULT_INVALID_TOKEN =
invalidToken("An error occurred while attempting to decode the Jwt: Invalid token");
public JwtReactiveAuthenticationManager(ReactiveJwtDecoder jwtDecoder) {
Assert.notNull(jwtDecoder, "jwtDecoder cannot be null");
this.jwtDecoder = jwtDecoder;
@ -78,20 +72,6 @@ public final class JwtReactiveAuthenticationManager implements ReactiveAuthentic
}
private OAuth2AuthenticationException onError(JwtException e) {
OAuth2Error invalidRequest = invalidToken(e.getMessage());
return new OAuth2AuthenticationException(invalidRequest, invalidRequest.getDescription(), e);
}
private static OAuth2Error invalidToken(String message) {
try {
return new BearerTokenError(
BearerTokenErrorCodes.INVALID_TOKEN,
HttpStatus.UNAUTHORIZED,
message,
"https://tools.ietf.org/html/rfc6750#section-3.1");
} catch (IllegalArgumentException malformed) {
// some third-party library error messages are not suitable for RFC 6750's error message charset
return DEFAULT_INVALID_TOKEN;
}
return new InvalidBearerTokenException(e.getMessage(), e);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -18,7 +18,6 @@ package org.springframework.security.oauth2.server.resource.authentication;
import java.time.Instant;
import java.util.Collection;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
@ -26,10 +25,8 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.util.Assert;
@ -60,9 +57,6 @@ import static org.springframework.security.oauth2.server.resource.introspection.
* @see AuthenticationProvider
*/
public final class OpaqueTokenAuthenticationProvider implements AuthenticationProvider {
private static final BearerTokenError DEFAULT_INVALID_TOKEN =
invalidToken("An error occurred while attempting to introspect the token: Invalid token");
private OpaqueTokenIntrospector introspector;
/**
@ -95,8 +89,7 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
try {
principal = this.introspector.introspect(bearer.getToken());
} catch (OAuth2IntrospectionException failed) {
OAuth2Error invalidToken = invalidToken(failed.getMessage());
throw new OAuth2AuthenticationException(invalidToken);
throw new InvalidBearerTokenException(failed.getMessage());
}
AbstractAuthenticationToken result = convert(principal, bearer.getToken());
@ -119,15 +112,4 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
token, iat, exp);
return new BearerTokenAuthentication(principal, accessToken, principal.getAuthorities());
}
private static BearerTokenError invalidToken(String message) {
try {
return new BearerTokenError("invalid_token",
HttpStatus.UNAUTHORIZED, message,
"https://tools.ietf.org/html/rfc7662#section-2.2");
} catch (IllegalArgumentException malformed) {
// some third-party library error messages are not suitable for RFC 6750's error message charset
return DEFAULT_INVALID_TOKEN;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -21,15 +21,13 @@ import java.util.Collection;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
import org.springframework.util.Assert;
@ -60,9 +58,6 @@ import static org.springframework.security.oauth2.server.resource.introspection.
* @see ReactiveAuthenticationManager
*/
public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthenticationManager {
private static final BearerTokenError DEFAULT_INVALID_TOKEN =
invalidToken("An error occurred while attempting to introspect the token: Invalid token");
private ReactiveOpaqueTokenIntrospector introspector;
/**
@ -99,19 +94,7 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
.onErrorMap(OAuth2IntrospectionException.class, this::onError);
}
private static BearerTokenError invalidToken(String message) {
try {
return new BearerTokenError("invalid_token",
HttpStatus.UNAUTHORIZED, message,
"https://tools.ietf.org/html/rfc7662#section-2.2");
} catch (IllegalArgumentException e) {
// some third-party library error messages are not suitable for RFC 6750's error message charset
return DEFAULT_INVALID_TOKEN;
}
}
private OAuth2AuthenticationException onError(OAuth2IntrospectionException e) {
OAuth2Error invalidRequest = invalidToken(e.getMessage());
return new OAuth2AuthenticationException(invalidRequest, e.getMessage());
return new InvalidBearerTokenException(e.getMessage(), e);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* 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.
@ -21,12 +21,13 @@ import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
import org.springframework.util.StringUtils;
import static org.springframework.security.oauth2.server.resource.BearerTokenErrors.invalidRequest;
import static org.springframework.security.oauth2.server.resource.BearerTokenErrors.invalidToken;
/**
* The default {@link BearerTokenResolver} implementation based on RFC 6750.
*
@ -53,10 +54,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
String parameterToken = resolveFromRequestParameters(request);
if (authorizationHeaderToken != null) {
if (parameterToken != null) {
BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_REQUEST,
HttpStatus.BAD_REQUEST,
"Found multiple bearer tokens in the request",
"https://tools.ietf.org/html/rfc6750#section-3.1");
BearerTokenError error = invalidRequest("Found multiple bearer tokens in the request");
throw new OAuth2AuthenticationException(error);
}
return authorizationHeaderToken;
@ -93,10 +91,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
Matcher matcher = authorizationPattern.matcher(authorization);
if (!matcher.matches()) {
BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN,
HttpStatus.UNAUTHORIZED,
"Bearer token is malformed",
"https://tools.ietf.org/html/rfc6750#section-3.1");
BearerTokenError error = invalidToken("Bearer token is malformed");
throw new OAuth2AuthenticationException(error);
}
@ -115,10 +110,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver {
return values[0];
}
BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_REQUEST,
HttpStatus.BAD_REQUEST,
"Found multiple bearer tokens in the request",
"https://tools.ietf.org/html/rfc6750#section-3.1");
BearerTokenError error = invalidRequest("Found multiple bearer tokens in the request");
throw new OAuth2AuthenticationException(error);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* 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.
@ -16,22 +16,24 @@
package org.springframework.security.oauth2.server.resource.web.server;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.springframework.security.oauth2.server.resource.BearerTokenErrors.invalidRequest;
import static org.springframework.security.oauth2.server.resource.BearerTokenErrors.invalidToken;
/**
* A strategy for resolving <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer Token</a>s
@ -65,10 +67,7 @@ public class ServerBearerTokenAuthenticationConverter
String parameterToken = request.getQueryParams().getFirst("access_token");
if (authorizationHeaderToken != null) {
if (parameterToken != null) {
BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_REQUEST,
HttpStatus.BAD_REQUEST,
"Found multiple bearer tokens in the request",
"https://tools.ietf.org/html/rfc6750#section-3.1");
BearerTokenError error = invalidRequest("Found multiple bearer tokens in the request");
throw new OAuth2AuthenticationException(error);
}
return authorizationHeaderToken;
@ -107,10 +106,7 @@ public class ServerBearerTokenAuthenticationConverter
}
private static BearerTokenError invalidTokenError() {
return new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN,
HttpStatus.UNAUTHORIZED,
"Bearer token is malformed",
"https://tools.ietf.org/html/rfc6750#section-3.1");
return invalidToken("Bearer token is malformed");
}
private boolean isParameterTokenSupportedForRequest(ServerHttpRequest request) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* 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.
@ -95,7 +95,7 @@ public class JwtAuthenticationProviderTests {
.isInstanceOf(OAuth2AuthenticationException.class)
.hasFieldOrPropertyWithValue(
"error.description",
"An error occurred while attempting to decode the Jwt: Invalid token");
"Invalid token");
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -162,7 +162,7 @@ public class JwtIssuerAuthenticationManagerResolverTests {
request.addHeader("Authorization", "Bearer " + this.evil);
assertThatCode(() -> authenticationManagerResolver.resolve(request))
.isInstanceOf(OAuth2AuthenticationException.class)
.hasMessage("Invalid token");
.hasMessage("Invalid issuer");
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* 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.
@ -98,7 +98,7 @@ public class JwtReactiveAuthenticationManagerTests {
.isInstanceOf(OAuth2AuthenticationException.class)
.hasFieldOrPropertyWithValue(
"error.description",
"An error occurred while attempting to decode the Jwt: Invalid token");
"Invalid token");
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -111,7 +111,7 @@ public class OpaqueTokenAuthenticationProviderTests {
assertThatCode(() -> provider.authenticate(new BearerTokenAuthenticationToken("token")))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting("error.description")
.isEqualTo("An error occurred while attempting to introspect the token: Invalid token");
.isEqualTo("Invalid token");
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@ -117,7 +117,7 @@ public class OpaqueTokenReactiveAuthenticationManagerTests {
assertThatCode(() -> provider.authenticate(new BearerTokenAuthenticationToken("token")).block())
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting("error.description")
.isEqualTo("An error occurred while attempting to introspect the token: Invalid token");
.isEqualTo("Invalid token");
}
@Test