KEYCLOAK-8249 Improve extraction of Bearer tokens from Authorization headers (#6624)

We now provide a simple way to extract the Bearer token string from
Authorization header with a null fallback.

This allows us to have more fine grained error handling for the
various endpoints.
This commit is contained in:
Thomas Darimont 2020-01-06 13:58:52 +01:00 committed by Marek Posolda
parent 28b01bc34d
commit 1a7aeb9b20
3 changed files with 76 additions and 9 deletions

View file

@ -100,7 +100,7 @@ public class UserInfoEndpoint {
@GET
@NoCache
public Response issueUserInfoGet(@Context final HttpHeaders headers) {
String accessToken = this.appAuthManager.extractAuthorizationHeaderToken(headers);
String accessToken = this.appAuthManager.extractAuthorizationHeaderTokenOrReturnNull(headers);
return issueUserInfo(accessToken);
}
@ -110,7 +110,7 @@ public class UserInfoEndpoint {
public Response issueUserInfoPost() {
// Try header first
HttpHeaders headers = request.getHttpHeaders();
String accessToken = this.appAuthManager.extractAuthorizationHeaderToken(headers);
String accessToken = this.appAuthManager.extractAuthorizationHeaderTokenOrReturnNull(headers);
// Fallback to form parameter
if (accessToken == null) {

View file

@ -17,13 +17,16 @@
package org.keycloak.services.managers;
import javax.ws.rs.NotAuthorizedException;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
import java.util.regex.Pattern;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -31,6 +34,10 @@ import javax.ws.rs.core.UriInfo;
*/
public class AppAuthManager extends AuthenticationManager {
private static final String BEARER = "Bearer";
private static final Pattern WHITESPACES = Pattern.compile("\\s+");
@Override
public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm) {
AuthResult authResult = super.authenticateIdentityCookie(session, realm);
@ -41,14 +48,61 @@ public class AppAuthManager extends AuthenticationManager {
return authResult;
}
public String extractAuthorizationHeaderToken(HttpHeaders headers) {
String tokenString = null;
/**
* Extracts the token string from the given Authorization Bearer header.
*
* @return the token string or {@literal null}
*/
private String extractTokenStringFromAuthHeader(String authHeader) {
if (authHeader == null) {
return null;
}
String[] split = WHITESPACES.split(authHeader.trim());
if (split.length != 2){
return null;
}
String bearerPart = split[0];
if (!bearerPart.equalsIgnoreCase(BEARER)){
return null;
}
String tokenString = split[1];
if (ObjectUtil.isBlank(tokenString)) {
return null;
}
return tokenString;
}
/**
* Extracts the token string from the Authorization Bearer Header.
*
* @param headers
* @return the token string or {@literal null} if the Authorization header is not of type Bearer, or the token string is missing.
*/
public String extractAuthorizationHeaderTokenOrReturnNull(HttpHeaders headers) {
String authHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader != null) {
String[] split = authHeader.trim().split("\\s+");
if (split == null || split.length != 2) throw new NotAuthorizedException("Bearer");
if (!split[0].equalsIgnoreCase("Bearer")) throw new NotAuthorizedException("Bearer");
tokenString = split[1];
return extractTokenStringFromAuthHeader(authHeader);
}
/**
* Extracts the token string from the Authorization Bearer Header.
*
* @param headers
* @return the token string or {@literal null} of the Authorization header is missing
* @throws NotAuthorizedException if the Authorization header is not of type Bearer, or the token string is missing.
*/
public String extractAuthorizationHeaderToken(HttpHeaders headers) {
String authHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader == null) {
return null;
}
String tokenString = extractTokenStringFromAuthHeader(authHeader);
if (tokenString == null ){
throw new NotAuthorizedException(BEARER);
}
return tokenString;
}

View file

@ -448,6 +448,19 @@ public class UserInfoTest extends AbstractKeycloakTest {
}
}
@Test
public void testUnsuccessfulUserInfoRequestWithEmptyAccessToken() {
Client client = ClientBuilder.newClient();
try {
Response response = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, "");
response.close();
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
} finally {
client.close();
}
}
@Test
public void testUserInfoRequestWithSamlClient() throws Exception {
// obtain an access token