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:
parent
28b01bc34d
commit
1a7aeb9b20
3 changed files with 76 additions and 9 deletions
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue