Merge pull request #2990 from trex667/feat/keycloak-1733

[KEYCLOAK-1733]: introduce token as query paramter
This commit is contained in:
Marek Posolda 2016-07-20 10:46:11 +02:00 committed by GitHub
commit e03bf6eef6
6 changed files with 181 additions and 24 deletions

View file

@ -35,7 +35,8 @@ public class OIDCAuthenticationError implements AuthenticationError {
CODE_TO_TOKEN_FAILURE, CODE_TO_TOKEN_FAILURE,
INVALID_TOKEN, INVALID_TOKEN,
STALE_TOKEN, STALE_TOKEN,
NO_AUTHORIZATION_HEADER NO_AUTHORIZATION_HEADER,
NO_QUERY_PARAMETER_ACCESS_TOKEN
} }
private Reason reason; private Reason reason;

View file

@ -0,0 +1,54 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
* @version $Revision: 1 $
*/
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
public static final String ACCESS_TOKEN = "access_token";
protected Logger log = Logger.getLogger(QueryParamterTokenRequestAuthenticator.class);
public QueryParamterTokenRequestAuthenticator(KeycloakDeployment deployment) {
super(deployment);
}
public AuthOutcome authenticate(HttpFacade exchange) {
tokenString = null;
tokenString = getAccessTokenFromQueryParamter(exchange);
if (tokenString == null || tokenString.trim().isEmpty()) {
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_QUERY_PARAMETER_ACCESS_TOKEN, null, null);
return AuthOutcome.NOT_ATTEMPTED;
}
return (authenticateToken(exchange, tokenString));
}
String getAccessTokenFromQueryParamter(HttpFacade exchange) {
try {
if (exchange != null && exchange.getRequest() != null) {
return exchange.getRequest().getQueryParamValue(ACCESS_TOKEN);
}
} catch (Exception ignore) {
}
return null;
}
}

View file

@ -74,6 +74,22 @@ public abstract class RequestAuthenticator {
return AuthOutcome.AUTHENTICATED; return AuthOutcome.AUTHENTICATED;
} }
QueryParamterTokenRequestAuthenticator queryParamAuth = createQueryParamterTokenRequestAuthenticator();
if (log.isTraceEnabled()) {
log.trace("try query paramter auth");
}
outcome = queryParamAuth.authenticate(facade);
if (outcome == AuthOutcome.FAILED) {
challenge = queryParamAuth.getChallenge();
log.debug("QueryParamAuth auth FAILED");
return AuthOutcome.FAILED;
} else if (outcome == AuthOutcome.AUTHENTICATED) {
log.debug("QueryParamAuth AUTHENTICATED");
completeAuthentication(queryParamAuth, "KEYCLOAK");
return AuthOutcome.AUTHENTICATED;
}
if (deployment.isEnableBasicAuth()) { if (deployment.isEnableBasicAuth()) {
BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator(); BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator();
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
@ -150,6 +166,10 @@ public abstract class RequestAuthenticator {
return new BasicAuthRequestAuthenticator(deployment); return new BasicAuthRequestAuthenticator(deployment);
} }
protected QueryParamterTokenRequestAuthenticator createQueryParamterTokenRequestAuthenticator() {
return new QueryParamterTokenRequestAuthenticator(deployment);
}
protected void completeAuthentication(OAuthRequestAuthenticator oauth) { protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken()); RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session); final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
@ -158,10 +178,12 @@ public abstract class RequestAuthenticator {
} }
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal); protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method); protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method);
/** /**
* After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation * After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation
*
* @param create * @param create
* @return * @return
*/ */

View file

@ -203,4 +203,17 @@ public class AdapterTest {
testStrategy.testAccountManagementSessionsLogout(); testStrategy.testAccountManagementSessionsLogout();
} }
/**
* KEYCLOAK-1733
*/
@Test
public void testNullQueryParameterAccessToken() throws Exception {
testStrategy.testNullQueryParameterAccessToken();
}
@Test
public void testRestCallWithAccessTokenAsQueryParameter() throws Exception {
testStrategy.testRestCallWithAccessTokenAsQueryParameter();
}
} }

View file

@ -17,34 +17,29 @@
package org.keycloak.testsuite.adapter; package org.keycloak.testsuite.adapter;
import org.apache.http.conn.params.ConnManagerParams; import org.apache.http.conn.params.ConnManagerParams;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Assert; import org.junit.Assert;
import org.junit.rules.ExternalResource; import org.junit.rules.ExternalResource;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.OIDCAuthenticationError; import org.keycloak.adapters.OIDCAuthenticationError;
import org.keycloak.common.Version;
import org.keycloak.representations.VersionRepresentation;
import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.Keycloak;
import org.keycloak.common.Version;
import org.keycloak.common.util.Time;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.models.ClientModel; import org.keycloak.models.*;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.VersionRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AccountSessionsPage; import org.keycloak.testsuite.pages.AccountSessionsPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule; import org.keycloak.testsuite.rule.*;
import org.keycloak.testsuite.rule.ErrorServlet;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.util.BasicAuthHelper; import org.keycloak.util.BasicAuthHelper;
import org.keycloak.common.util.Time;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import javax.ws.rs.client.Client; import javax.ws.rs.client.Client;
@ -59,7 +54,6 @@ import java.net.URI;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.keycloak.representations.idm.UserRepresentation;
/** /**
* Tests Undertow Adapter * Tests Undertow Adapter
@ -144,7 +138,8 @@ public class AdapterTestStrategy extends ExternalResource {
System.out.println("insecure: "); System.out.println("insecure: ");
System.out.println(driver.getPageSource()); System.out.println(driver.getPageSource());
Assert.assertTrue(driver.getPageSource().contains("Insecure Page")); Assert.assertTrue(driver.getPageSource().contains("Insecure Page"));
if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertTrue(driver.getPageSource().contains("UserPrincipal")); if (System.getProperty("insecure.user.principal.unsupported") == null)
Assert.assertTrue(driver.getPageSource().contains("UserPrincipal"));
// test logout // test logout
@ -384,6 +379,26 @@ public class AdapterTestStrategy extends ExternalResource {
} }
/**
* KEYCLOAK-1733
*
* @throws Exception
*/
public void testNullQueryParameterAccessToken() throws Exception {
Client client = ClientBuilder.newClient();
WebTarget target = client.target(APP_SERVER_BASE_URL + "/customer-db/");
Response response = target.request().get();
Assert.assertEquals(401, response.getStatus());
response.close();
target = client.target(APP_SERVER_BASE_URL + "/customer-db?access_token=");
response = target.request().get();
Assert.assertEquals(401, response.getStatus());
response.close();
client.close();
}
/** /**
* KEYCLOAK-1368 * KEYCLOAK-1368
* @throws Exception * @throws Exception
@ -406,7 +421,7 @@ public class AdapterTestStrategy extends ExternalResource {
Assert.assertTrue(errorPageResponse.contains("Error Page")); Assert.assertTrue(errorPageResponse.contains("Error Page"));
response.close(); response.close();
Assert.assertNotNull(ErrorServlet.authError); Assert.assertNotNull(ErrorServlet.authError);
OIDCAuthenticationError error = (OIDCAuthenticationError)ErrorServlet.authError; OIDCAuthenticationError error = (OIDCAuthenticationError) ErrorServlet.authError;
Assert.assertEquals(OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, error.getReason()); Assert.assertEquals(OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, error.getReason());
ErrorServlet.authError = null; ErrorServlet.authError = null;
@ -422,7 +437,7 @@ public class AdapterTestStrategy extends ExternalResource {
Assert.assertTrue(errorPageResponse.contains("Error Page")); Assert.assertTrue(errorPageResponse.contains("Error Page"));
response.close(); response.close();
Assert.assertNotNull(ErrorServlet.authError); Assert.assertNotNull(ErrorServlet.authError);
error = (OIDCAuthenticationError)ErrorServlet.authError; error = (OIDCAuthenticationError) ErrorServlet.authError;
Assert.assertEquals(OIDCAuthenticationError.Reason.INVALID_TOKEN, error.getReason()); Assert.assertEquals(OIDCAuthenticationError.Reason.INVALID_TOKEN, error.getReason());
client.close(); client.close();
@ -496,7 +511,6 @@ public class AdapterTestStrategy extends ExternalResource {
} }
public void testAuthenticated() throws Exception { public void testAuthenticated() throws Exception {
// test login to customer-portal which does a bearer request to customer-db // test login to customer-portal which does a bearer request to customer-db
driver.navigate().to(APP_SERVER_BASE_URL + "/secure-portal"); driver.navigate().to(APP_SERVER_BASE_URL + "/secure-portal");
@ -521,6 +535,53 @@ public class AdapterTestStrategy extends ExternalResource {
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
} }
/**
* KEYCLOAK-1733
*
* @throws Exception
*/
public void testRestCallWithAccessTokenAsQueryParameter() throws Exception {
String accessToken = getAccessToken();
Client client = ClientBuilder.newClient();
try {
// test without token
Response response = client.target(APP_SERVER_BASE_URL + "/customer-db").request().get();
Assert.assertEquals(401, response.getStatus());
response.close();
// test with access_token as QueryParamter
response = client.target(APP_SERVER_BASE_URL + "/customer-db").queryParam("access_token", accessToken).request().get();
Assert.assertEquals(200, response.getStatus());
response.close();
} finally {
client.close();
}
}
private String getAccessToken() throws JSONException {
String tokenUrl = AUTH_SERVER_URL + "/realms/demo/protocol/openid-connect/token";
Client client = ClientBuilder.newClient();
try {
WebTarget webTarget = client.target(tokenUrl);
Form form = new Form();
form.param("grant_type", "password");
form.param("client_id", "customer-portal-public");
form.param("username", "bburke@redhat.com");
form.param("password", "password");
Response response = webTarget.request().post(Entity.form(form));
Assert.assertEquals(200, response.getStatus());
JSONObject jsonObject = new JSONObject(response.readEntity(String.class));
System.out.println(jsonObject);
response.close();
return jsonObject.getString("access_token");
} finally {
client.close();
}
}
/** /**
* KEYCLOAK-732 * KEYCLOAK-732
* *

View file

@ -156,6 +156,12 @@
"http://localhost" "http://localhost"
] ]
}, },
{
"name": "customer-portal-public",
"enabled": true,
"publicClient": true,
"directAccessGrantsEnabled": true
},
{ {
"name": "product-portal", "name": "product-portal",
"enabled": true, "enabled": true,