KEYCLOAK-1595 Don't send 401 from AuthorizeClientUtil if public client sent client_id parameter together with Authorization: Negotiate
This commit is contained in:
parent
61d5ba6a92
commit
e27a754f5f
3 changed files with 71 additions and 8 deletions
|
@ -22,16 +22,23 @@ import java.util.Map;
|
||||||
public class AuthorizeClientUtil {
|
public class AuthorizeClientUtil {
|
||||||
|
|
||||||
public static ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData, EventBuilder event, RealmModel realm) {
|
public static ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData, EventBuilder event, RealmModel realm) {
|
||||||
String client_id;
|
String client_id = null;
|
||||||
String clientSecret;
|
String clientSecret = null;
|
||||||
if (authorizationHeader != null) {
|
if (authorizationHeader != null) {
|
||||||
String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
|
String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
|
||||||
if (usernameSecret == null) {
|
if (usernameSecret != null) {
|
||||||
throw new UnauthorizedException("Bad Authorization header", Response.status(401).header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"" + realm.getName() + "\"").build());
|
client_id = usernameSecret[0];
|
||||||
|
clientSecret = usernameSecret[1];
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Don't send 401 if client_id parameter was sent in request. For example IE may automatically send "Authorization: Negotiate" in XHR requests even for public clients
|
||||||
|
if (!formData.containsKey(OAuth2Constants.CLIENT_ID)) {
|
||||||
|
throw new UnauthorizedException("Bad Authorization header", Response.status(401).header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"" + realm.getName() + "\"").build());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
client_id = usernameSecret[0];
|
}
|
||||||
clientSecret = usernameSecret[1];
|
|
||||||
} else {
|
if (client_id == null) {
|
||||||
client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
|
client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
|
||||||
clientSecret = formData.getFirst("client_secret");
|
clientSecret = formData.getFirst("client_secret");
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,7 +306,7 @@ public class OAuthClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeClient(CloseableHttpClient client) {
|
public void closeClient(CloseableHttpClient client) {
|
||||||
try {
|
try {
|
||||||
client.close();
|
client.close();
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
|
|
|
@ -21,12 +21,19 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.oauth;
|
package org.keycloak.testsuite.oauth;
|
||||||
|
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.VerificationException;
|
import org.keycloak.VerificationException;
|
||||||
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.enums.SslRequired;
|
import org.keycloak.enums.SslRequired;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
|
@ -46,6 +53,7 @@ import org.keycloak.protocol.oidc.mappers.RoleNameMapper;
|
||||||
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
|
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
|
import org.keycloak.services.managers.ClientManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
|
@ -68,9 +76,11 @@ import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -719,6 +729,52 @@ public class AccessTokenTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEYCLOAK-1595 Assert that public client is able to retrieve token even if header "Authorization: Negotiate something" was used (parameter client_id has preference in this case)
|
||||||
|
@Test
|
||||||
|
public void testAuthorizationNegotiateHeaderIgnored() throws Exception {
|
||||||
|
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
ClientModel client = new ClientManager(manager).createClient(appRealm, "sample-public-client");
|
||||||
|
client.addRedirectUri("http://localhost:8081/app/auth");
|
||||||
|
client.setEnabled(true);
|
||||||
|
client.setPublicClient(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
oauth.clientId("sample-public-client");
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
Event loginEvent = events.expectLogin().client("sample-public-client").assertEvent();
|
||||||
|
|
||||||
|
String sessionId = loginEvent.getSessionId();
|
||||||
|
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||||
|
|
||||||
|
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||||
|
|
||||||
|
CloseableHttpClient client = new DefaultHttpClient();
|
||||||
|
try {
|
||||||
|
HttpPost post = new HttpPost(oauth.getAccessTokenUrl());
|
||||||
|
|
||||||
|
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
||||||
|
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE));
|
||||||
|
parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
|
||||||
|
parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, oauth.getRedirectUri()));
|
||||||
|
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, oauth.getClientId()));
|
||||||
|
post.setHeader("Authorization", "Negotiate something-which-will-be-ignored");
|
||||||
|
|
||||||
|
UrlEncodedFormEntity formEntity = null;
|
||||||
|
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||||
|
post.setEntity(formEntity);
|
||||||
|
|
||||||
|
AccessTokenResponse response = new AccessTokenResponse(client.execute(post));
|
||||||
|
Assert.assertEquals(200, response.getStatusCode());
|
||||||
|
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||||
|
events.expectCodeToToken(codeId, sessionId).client("sample-public-client").assertEvent();
|
||||||
|
} finally {
|
||||||
|
oauth.closeClient(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private IDToken getIdToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws VerificationException {
|
private IDToken getIdToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws VerificationException {
|
||||||
JWSInput input = new JWSInput(tokenResponse.getIdToken());
|
JWSInput input = new JWSInput(tokenResponse.getIdToken());
|
||||||
IDToken idToken = null;
|
IDToken idToken = null;
|
||||||
|
|
Loading…
Reference in a new issue