KEYCLOAK-12615 HS384 and HS512 support for Client Authentication by Client Secret Signed JWT (#6633)
This commit is contained in:
parent
3beef2a4c0
commit
993ba3179c
17 changed files with 582 additions and 101 deletions
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.authentication;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -11,6 +27,8 @@ import org.keycloak.OAuth2Constants;
|
|||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.JavaAlgorithm;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
|
@ -18,63 +36,95 @@ import org.keycloak.representations.JsonWebToken;
|
|||
* Client authentication based on JWT signed by client secret instead of private key .
|
||||
* See <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">specs</a> for more details.
|
||||
*
|
||||
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
|
||||
*/
|
||||
public class JWTClientSecretCredentialsProvider implements ClientCredentialsProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(JWTClientSecretCredentialsProvider.class);
|
||||
|
||||
|
||||
private static final Logger logger = Logger.getLogger(JWTClientSecretCredentialsProvider.class);
|
||||
|
||||
public static final String PROVIDER_ID = "secret-jwt";
|
||||
|
||||
|
||||
private SecretKey clientSecret;
|
||||
|
||||
|
||||
private String clientSecretJwtAlg = Algorithm.HS256;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init(KeycloakDeployment deployment, Object config) {
|
||||
if (!(config instanceof Map)) {
|
||||
throw new RuntimeException("Configuration of jwt credentials by client secret is missing or incorrect for client '" + deployment.getResourceName() + "'. Check your adapter configuration");
|
||||
}
|
||||
|
||||
|
||||
Map<String, Object> cfg = (Map<String, Object>) config;
|
||||
String clientSecretString = (String) cfg.get("secret");
|
||||
if (clientSecretString == null) {
|
||||
throw new RuntimeException("Missing parameter secret-jwt in configuration of jwt for client " + deployment.getResourceName());
|
||||
}
|
||||
setClientSecret(clientSecretString);
|
||||
|
||||
String clientSecretJwtAlg = (String) cfg.get("algorithm");
|
||||
if (clientSecretJwtAlg == null) {
|
||||
// "algorithm" field is optional. fallback to HS256.
|
||||
setClientSecret(clientSecretString);
|
||||
} else if (isValidClientSecretJwtAlg(clientSecretJwtAlg)) {
|
||||
setClientSecret(clientSecretString, clientSecretJwtAlg);
|
||||
} else {
|
||||
// invalid "algorithm" field
|
||||
throw new RuntimeException("Invalid parameter secret-jwt in configuration of jwt for client " + deployment.getResourceName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isValidClientSecretJwtAlg(String clientSecretJwtAlg) {
|
||||
boolean ret = false;
|
||||
if (Algorithm.HS256.equals(clientSecretJwtAlg) || Algorithm.HS384.equals(clientSecretJwtAlg) || Algorithm.HS512.equals(clientSecretJwtAlg))
|
||||
ret = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
|
||||
String signedToken = createSignedRequestToken(deployment.getResourceName(), deployment.getRealmInfoUrl());
|
||||
formParams.put(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT);
|
||||
formParams.put(OAuth2Constants.CLIENT_ASSERTION, signedToken);
|
||||
}
|
||||
|
||||
|
||||
public void setClientSecret(String clientSecretString) {
|
||||
// Get client secret and validate signature
|
||||
// According to <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OIDC's client authentication spec</a>,
|
||||
// The HMAC (Hash-based Message Authentication Code) is calculated using the octets of the UTF-8 representation of the client_secret as the shared key.
|
||||
// Use "HmacSHA256" consulting <a href="https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Mac.html">java8 api</a>
|
||||
// because it must be implemented in every java platform.
|
||||
clientSecret = new SecretKeySpec(clientSecretString.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
setClientSecret(clientSecretString, Algorithm.HS256);
|
||||
}
|
||||
|
||||
|
||||
public void setClientSecret(String clientSecretString, String algorithm) {
|
||||
clientSecret = new SecretKeySpec(clientSecretString.getBytes(StandardCharsets.UTF_8), JavaAlgorithm.getJavaAlgorithm(algorithm));
|
||||
clientSecretJwtAlg = algorithm;
|
||||
}
|
||||
|
||||
public String createSignedRequestToken(String clientId, String realmInfoUrl) {
|
||||
return createSignedRequestToken(clientId, realmInfoUrl, clientSecretJwtAlg);
|
||||
}
|
||||
|
||||
public String createSignedRequestToken(String clientId, String realmInfoUrl, String algorithm) {
|
||||
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
|
||||
// JOSE header {"alg":"HS256","typ" : "JWT"} no need "kid" due to using only one registered client secret.
|
||||
// Use "HmacSHA256" consulting <a href="https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Mac.html">java8 api</a>.
|
||||
// because it must be implemented in every java platform.
|
||||
return new JWSBuilder().jsonContent(jwt).hmac256(clientSecret);
|
||||
String signedRequestToken = null;
|
||||
if (Algorithm.HS512.equals(algorithm)) {
|
||||
signedRequestToken = new JWSBuilder().jsonContent(jwt).hmac512(clientSecret);
|
||||
} else if (Algorithm.HS384.equals(algorithm)) {
|
||||
signedRequestToken = new JWSBuilder().jsonContent(jwt).hmac384(clientSecret);
|
||||
} else {
|
||||
signedRequestToken = new JWSBuilder().jsonContent(jwt).hmac256(clientSecret);
|
||||
}
|
||||
return signedRequestToken;
|
||||
}
|
||||
|
||||
private JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
|
||||
// According to <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OIDC's client authentication spec</a>,
|
||||
// JWT claims is the same as one by private_key_jwt
|
||||
|
||||
|
||||
JsonWebToken reqToken = new JsonWebToken();
|
||||
reqToken.id(AdapterUtils.generateId());
|
||||
reqToken.issuer(clientId);
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
/*
|
||||
* Copyright 2018 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.authentication.authenticators.client;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -10,8 +25,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
|
@ -21,7 +34,6 @@ import org.keycloak.authentication.AuthenticationFlowError;
|
|||
import org.keycloak.authentication.ClientAuthenticationFlowContext;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.HMACProvider;
|
||||
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
|
||||
import org.keycloak.models.SingleUseTokenStoreProvider;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
|
@ -42,13 +54,12 @@ import org.keycloak.services.Urls;
|
|||
*
|
||||
* TODO: Try to create abstract superclass to be shared with {@link JWTClientAuthenticator}. Most of the code can be reused
|
||||
*
|
||||
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
|
||||
*/
|
||||
public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
||||
private static final Logger logger = Logger.getLogger(JWTClientSecretAuthenticator.class);
|
||||
|
||||
public static final String PROVIDER_ID = "client-secret-jwt";
|
||||
|
||||
private static final Logger logger = Logger.getLogger(JWTClientSecretAuthenticator.class);
|
||||
|
||||
public static final String PROVIDER_ID = "client-secret-jwt";
|
||||
|
||||
@Override
|
||||
public void authenticateClient(ClientAuthenticationFlowContext context) {
|
||||
|
@ -56,7 +67,7 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
|||
|
||||
String clientAssertionType = params.getFirst(OAuth2Constants.CLIENT_ASSERTION_TYPE);
|
||||
String clientAssertion = params.getFirst(OAuth2Constants.CLIENT_ASSERTION);
|
||||
|
||||
|
||||
if (clientAssertionType == null) {
|
||||
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Parameter client_assertion_type is missing");
|
||||
context.challenge(challengeResponse);
|
||||
|
@ -75,7 +86,7 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
|||
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
JWSInput jws = new JWSInput(clientAssertion);
|
||||
JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
|
||||
|
@ -99,22 +110,17 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
|||
context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String clientSecretString = client.getSecret();
|
||||
if (clientSecretString == null) {
|
||||
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get client secret and validate signature
|
||||
// According to <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OIDC's client authentication spec</a>,
|
||||
// The HMAC (Hash-based Message Authentication Code) is calculated using the octets of the UTF-8 representation of the client_secret as the shared key.
|
||||
// Use "HmacSHA256" consulting <a href="https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Mac.html">java8 api</a>.
|
||||
SecretKey clientSecret = new SecretKeySpec(clientSecretString.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
|
||||
boolean signatureValid;
|
||||
try {
|
||||
signatureValid = HMACProvider.verify(jws, clientSecret);
|
||||
JsonWebToken jwt = context.getSession().tokens().decodeClientJWT(clientAssertion, client, JsonWebToken.class);
|
||||
signatureValid = jwt != null;
|
||||
} catch (RuntimeException e) {
|
||||
Throwable cause = e.getCause() != null ? e.getCause() : e;
|
||||
throw new RuntimeException("Signature on JWT token by client secret failed validation", cause);
|
||||
|
@ -124,7 +130,7 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
|||
}
|
||||
// According to <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OIDC's client authentication spec</a>,
|
||||
// JWT contents and verification in client_secret_jwt is the same as in private_key_jwt
|
||||
|
||||
|
||||
// Allow both "issuer" or "token-endpoint" as audience
|
||||
String issuerUrl = Urls.realmIssuer(context.getUriInfo().getBaseUri(), realm.getName());
|
||||
String tokenUrl = OIDCLoginProtocolService.tokenUrl(context.getUriInfo().getBaseUriBuilder()).build(realm.getName()).toString();
|
||||
|
@ -163,7 +169,7 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
|||
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
|
@ -177,14 +183,16 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
|||
|
||||
@Override
|
||||
public Map<String, Object> getAdapterConfiguration(ClientModel client) {
|
||||
// e.g.
|
||||
// "credentials": {
|
||||
// e.g. client adapter's keycloak.json
|
||||
// "credentials": {
|
||||
// "secret-jwt": {
|
||||
// "secret": "234234-234234-234234"
|
||||
// }
|
||||
// "secret": "234234-234234-234234",
|
||||
// "algorithm": "HS256"
|
||||
// }
|
||||
// }
|
||||
Map<String, Object> props = new HashMap<>();
|
||||
props.put("secret", client.getSecret());
|
||||
// "algorithm" field is not saved because keycloak does not manage client's property of which algorithm is used for client secret signed JWT.
|
||||
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
config.put("secret-jwt", props);
|
||||
|
@ -227,6 +235,5 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
|||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2020 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.crypto;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
public class ClientMacSignatureVerifierContext extends MacSignatureVerifierContext {
|
||||
|
||||
public ClientMacSignatureVerifierContext(KeycloakSession session, ClientModel client, String algorithm) throws VerificationException {
|
||||
super(getKey(session, client, algorithm));
|
||||
}
|
||||
|
||||
private static KeyWrapper getKey(KeycloakSession session, ClientModel client, String algorithm) throws VerificationException {
|
||||
if (algorithm == null) algorithm = Algorithm.HS256;
|
||||
String clientSecretString = client.getSecret();
|
||||
SecretKey clientSecret = new SecretKeySpec(clientSecretString.getBytes(StandardCharsets.UTF_8), JavaAlgorithm.getJavaAlgorithm(algorithm));
|
||||
KeyWrapper key = new KeyWrapper();
|
||||
key.setSecretKey(clientSecret);
|
||||
key.setUse(KeyUse.SIG);
|
||||
key.setType(KeyType.OCT);
|
||||
key.setAlgorithm(algorithm);
|
||||
return key;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2020 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.crypto;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
public class HS256ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory {
|
||||
|
||||
public static final String ID = Algorithm.HS256;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSignatureVerifierProvider create(KeycloakSession session) {
|
||||
return new MacSecretClientSignatureVerifierProvider(session, Algorithm.HS256);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2020 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.crypto;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
public class HS384ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory {
|
||||
|
||||
public static final String ID = Algorithm.HS384;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSignatureVerifierProvider create(KeycloakSession session) {
|
||||
return new MacSecretClientSignatureVerifierProvider(session, Algorithm.HS384);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2020 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.crypto;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
public class HS512ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory {
|
||||
|
||||
public static final String ID = Algorithm.HS512;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSignatureVerifierProvider create(KeycloakSession session) {
|
||||
return new MacSecretClientSignatureVerifierProvider(session, Algorithm.HS512);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2020 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.crypto;
|
||||
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
public class MacSecretClientSignatureVerifierProvider implements ClientSignatureVerifierProvider {
|
||||
private final KeycloakSession session;
|
||||
private final String algorithm;
|
||||
|
||||
public MacSecretClientSignatureVerifierProvider(KeycloakSession session, String algorithm) {
|
||||
this.session = session;
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignatureVerifierContext verifier(ClientModel client, JWSInput input) throws VerificationException {
|
||||
return new ClientMacSignatureVerifierContext(session, client, algorithm);
|
||||
}
|
||||
}
|
|
@ -7,3 +7,6 @@ org.keycloak.crypto.ES512ClientSignatureVerifierProviderFactory
|
|||
org.keycloak.crypto.PS256ClientSignatureVerifierProviderFactory
|
||||
org.keycloak.crypto.PS384ClientSignatureVerifierProviderFactory
|
||||
org.keycloak.crypto.PS512ClientSignatureVerifierProviderFactory
|
||||
org.keycloak.crypto.HS256ClientSignatureVerifierProviderFactory
|
||||
org.keycloak.crypto.HS384ClientSignatureVerifierProviderFactory
|
||||
org.keycloak.crypto.HS512ClientSignatureVerifierProviderFactory
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2020 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.testsuite.adapter.page;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
|
||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public class ClientSecretJwtSecurePortalValidAlg extends AbstractPageWithInjectedUrl {
|
||||
|
||||
public static final String DEPLOYMENT_NAME = "client-secret-jwt-secure-portal-valid-alg";
|
||||
|
||||
@ArquillianResource
|
||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
||||
private URL url;
|
||||
|
||||
@Override
|
||||
public URL getInjectedUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
|
@ -39,7 +39,6 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
|||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.VersionRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
@ -49,6 +48,7 @@ import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
|
|||
import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
|
||||
import org.keycloak.testsuite.adapter.page.BasicAuth;
|
||||
import org.keycloak.testsuite.adapter.page.ClientSecretJwtSecurePortal;
|
||||
import org.keycloak.testsuite.adapter.page.ClientSecretJwtSecurePortalValidAlg;
|
||||
import org.keycloak.testsuite.adapter.page.CustomerCookiePortal;
|
||||
import org.keycloak.testsuite.adapter.page.CustomerCookiePortalRoot;
|
||||
import org.keycloak.testsuite.adapter.page.CustomerDb;
|
||||
|
@ -72,6 +72,7 @@ import org.keycloak.testsuite.auth.page.login.OAuthGrant;
|
|||
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
|
||||
import org.keycloak.testsuite.console.page.events.Config;
|
||||
import org.keycloak.testsuite.console.page.events.LoginEvents;
|
||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
||||
import org.keycloak.testsuite.util.FollowRedirectsEngine;
|
||||
import org.keycloak.testsuite.util.JavascriptBrowser;
|
||||
import org.keycloak.testsuite.util.Matchers;
|
||||
|
@ -112,7 +113,6 @@ import static org.hamcrest.Matchers.notNullValue;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
@ -183,6 +183,8 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
@Page
|
||||
private ClientSecretJwtSecurePortal clientSecretJwtSecurePortal;
|
||||
@Page
|
||||
private ClientSecretJwtSecurePortalValidAlg clientSecretJwtSecurePortalValidAlg;
|
||||
@Page
|
||||
private CustomerCookiePortal customerCookiePortal;
|
||||
@Page
|
||||
private CustomerCookiePortalRoot customerCookiePortalRoot;
|
||||
|
@ -270,6 +272,11 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
return servletDeployment(ClientSecretJwtSecurePortal.DEPLOYMENT_NAME, CallAuthenticatedServlet.class);
|
||||
}
|
||||
|
||||
@Deployment(name = ClientSecretJwtSecurePortalValidAlg.DEPLOYMENT_NAME)
|
||||
protected static WebArchive clientSecretSecurePortalValidAlg() {
|
||||
return servletDeployment(ClientSecretJwtSecurePortalValidAlg.DEPLOYMENT_NAME, CallAuthenticatedServlet.class);
|
||||
}
|
||||
|
||||
@Deployment(name = CustomerCookiePortalRoot.DEPLOYMENT_NAME)
|
||||
protected static WebArchive customerCookiePortalRoot() {
|
||||
return servletDeployment(CustomerCookiePortalRoot.DEPLOYMENT_NAME, AdapterActionsFilter.class, CustomerServlet.class, ErrorServlet.class, ServletTestUtils.class);
|
||||
|
@ -1232,48 +1239,60 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
@Test
|
||||
public void testClientAuthenticatedInClientSecretJwt() {
|
||||
// test login to customer-portal which does a bearer request to customer-db
|
||||
// JWS Client Assertion in client_secret_jwt
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
|
||||
// JWS Client Assertion in client_secret_jwt
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
|
||||
String targetClientId = "client-secret-jwt-secure-portal";
|
||||
|
||||
expectResultOfClientAuthenticatedInClientSecretJwt(targetClientId);
|
||||
|
||||
expectResultOfClientAuthenticatedInClientSecretJwt(targetClientId, clientSecretJwtSecurePortal);
|
||||
|
||||
// test logout
|
||||
String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, clientSecretJwtSecurePortal.toString()).build("demo").toString();
|
||||
driver.navigate().to(logoutUri);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testClientNotAuthenticatedInClientSecretJwtBySharedSecretOutOfSync() {
|
||||
// JWS Client Assertion in client_secret_jwt
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
|
||||
String targetClientId = "client-secret-jwt-secure-portal";
|
||||
String expectedErrorString = "invalid_client_credentials";
|
||||
|
||||
// JWS Client Assertion in client_secret_jwt
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
|
||||
String targetClientId = "client-secret-jwt-secure-portal";
|
||||
String expectedErrorString = "invalid_client_credentials";
|
||||
|
||||
ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), targetClientId);
|
||||
ClientRepresentation client = clientResource.toRepresentation();
|
||||
client.setSecret("passwordChanged");
|
||||
clientResource.update(client);
|
||||
|
||||
|
||||
expectResultOfClientNotAuthenticatedInClientSecretJwt(targetClientId, expectedErrorString);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testClientNotAuthenticatedInClientSecretJwtByAuthnMethodOutOfSync() {
|
||||
// JWS Client Assertion in client_secret_jwt
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
|
||||
String targetClientId = "client-secret-jwt-secure-portal";
|
||||
String expectedErrorString = "invalid_client_credentials";
|
||||
|
||||
// JWS Client Assertion in client_secret_jwt
|
||||
// http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
|
||||
String targetClientId = "client-secret-jwt-secure-portal";
|
||||
String expectedErrorString = "invalid_client_credentials";
|
||||
|
||||
ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), targetClientId);
|
||||
ClientRepresentation client = clientResource.toRepresentation();
|
||||
client.setClientAuthenticatorType("client-secret");
|
||||
clientResource.update(client);
|
||||
|
||||
|
||||
expectResultOfClientNotAuthenticatedInClientSecretJwt(targetClientId, expectedErrorString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientAuthenticatedInClientSecretJwtValidAlg() {
|
||||
String targetClientId = "client-secret-jwt-secure-portal-valid-alg";
|
||||
|
||||
expectResultOfClientAuthenticatedInClientSecretJwt(targetClientId, clientSecretJwtSecurePortalValidAlg);
|
||||
|
||||
// test logout
|
||||
String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, clientSecretJwtSecurePortalValidAlg.toString()).build("demo").toString();
|
||||
driver.navigate().to(logoutUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenInCookieSSORoot() {
|
||||
// Login
|
||||
|
@ -1321,20 +1340,20 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
customerCookiePortalRoot.navigateTo();
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
}
|
||||
|
||||
private void expectResultOfClientAuthenticatedInClientSecretJwt(String targetClientId) {
|
||||
|
||||
private void expectResultOfClientAuthenticatedInClientSecretJwt(String targetClientId, AbstractPageWithInjectedUrl portal) {
|
||||
RealmRepresentation realm = testRealmResource().toRepresentation();
|
||||
realm.setEventsEnabled(true);
|
||||
realm.setEnabledEventTypes(Arrays.asList("LOGIN", "CODE_TO_TOKEN"));
|
||||
realm.setEventsListeners(Arrays.asList("jboss-logging", "event-queue"));
|
||||
testRealmResource().update(realm);
|
||||
|
||||
clientSecretJwtSecurePortal.navigateTo();
|
||||
|
||||
portal.navigateTo();
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||
|
||||
|
||||
String userId = ApiUtil.findUserByUsername(testRealmResource(), "bburke@redhat.com").getId();
|
||||
|
||||
|
||||
assertEvents.expectLogin()
|
||||
.realm(realm.getId())
|
||||
.client(targetClientId)
|
||||
|
@ -1342,11 +1361,11 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
.detail(Details.USERNAME, "bburke@redhat.com")
|
||||
.detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED)
|
||||
.detail(Details.REDIRECT_URI,
|
||||
org.hamcrest.Matchers.anyOf(org.hamcrest.Matchers.equalTo(clientSecretJwtSecurePortal.getInjectedUrl().toString()),
|
||||
org.hamcrest.Matchers.equalTo(clientSecretJwtSecurePortal.getInjectedUrl().toString() + "/")))
|
||||
org.hamcrest.Matchers.anyOf(org.hamcrest.Matchers.equalTo(portal.getInjectedUrl().toString()),
|
||||
org.hamcrest.Matchers.equalTo(portal.getInjectedUrl().toString() + "/")))
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.assertEvent();
|
||||
|
||||
|
||||
assertEvents.expectCodeToToken(null, null)
|
||||
.realm(realm.getId())
|
||||
.client(targetClientId)
|
||||
|
@ -1355,18 +1374,18 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
|||
.clearDetails()
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
|
||||
private void expectResultOfClientNotAuthenticatedInClientSecretJwt(String targetClientId, String expectedErrorString) {
|
||||
RealmRepresentation realm = testRealmResource().toRepresentation();
|
||||
realm.setEventsEnabled(true);
|
||||
realm.setEnabledEventTypes(Arrays.asList("LOGIN", "CODE_TO_TOKEN_ERROR"));
|
||||
realm.setEventsListeners(Arrays.asList("jboss-logging", "event-queue"));
|
||||
testRealmResource().update(realm);
|
||||
|
||||
clientSecretJwtSecurePortal.navigateTo();
|
||||
|
||||
clientSecretJwtSecurePortal.navigateTo();
|
||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||
|
||||
|
||||
String userId = ApiUtil.findUserByUsername(testRealmResource(), "bburke@redhat.com").getId();
|
||||
|
||||
assertEvents.expectLogin()
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
* Copyright 2018 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.testsuite.oauth;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -21,6 +37,7 @@ import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenti
|
|||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -31,31 +48,43 @@ import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
|||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
||||
/**
|
||||
* @author Takashi Norimatsu <takashi.norimatsu.ws@hitachi.com>
|
||||
*/
|
||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
||||
public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
||||
private static final Logger logger = Logger.getLogger(ClientAuthSecretSignedJWTTest.class);
|
||||
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ClientAuthSecretSignedJWTTest.class);
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
|
||||
@Override
|
||||
public void beforeAbstractKeycloakTest() throws Exception {
|
||||
super.beforeAbstractKeycloakTest();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = AbstractAdminTest.loadJson(getClass().getResourceAsStream("/client-auth-test/testrealm-jwt-client-secret.json"), RealmRepresentation.class);
|
||||
testRealms.add(realm);
|
||||
}
|
||||
|
||||
|
||||
// TEST SUCCESS
|
||||
|
||||
|
||||
@Test
|
||||
public void testCodeToTokenRequestSuccess() throws Exception {
|
||||
public void testCodeToTokenRequestSuccess() throws Exception {
|
||||
testCodeToTokenRequestSuccess(Algorithm.HS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCodeToTokenRequestSuccessHS384() throws Exception {
|
||||
testCodeToTokenRequestSuccess(Algorithm.HS384);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCodeToTokenRequestSuccessHS512() throws Exception {
|
||||
testCodeToTokenRequestSuccess(Algorithm.HS512);
|
||||
}
|
||||
|
||||
private void testCodeToTokenRequestSuccess(String algorithm) throws Exception {
|
||||
oauth.clientId("test-app");
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
EventRepresentation loginEvent = events.expectLogin()
|
||||
|
@ -63,8 +92,8 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
|||
.assertEvent();
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code, getClientSignedJWT("password", 20));
|
||||
|
||||
OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code, getClientSignedJWT("password", 20, algorithm));
|
||||
|
||||
assertEquals(200, response.getStatusCode());
|
||||
oauth.verifyToken(response.getAccessToken());
|
||||
oauth.parseRefreshToken(response.getRefreshToken());
|
||||
|
@ -73,9 +102,9 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
|||
.detail(Details.CLIENT_AUTH_METHOD, JWTClientSecretAuthenticator.PROVIDER_ID)
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
|
||||
// TEST ERRORS
|
||||
|
||||
|
||||
@Test
|
||||
public void testAssertionInvalidSignature() throws Exception {
|
||||
oauth.clientId("test-app");
|
||||
|
@ -92,7 +121,6 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
|||
assertEquals("unauthorized_client", response.getError());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAssertionReuse() throws Exception {
|
||||
oauth.clientId("test-app");
|
||||
|
@ -132,18 +160,21 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
|||
assertEquals("unauthorized_client", response.getError());
|
||||
}
|
||||
|
||||
|
||||
private String getClientSignedJWT(String secret, int timeout) {
|
||||
JWTClientSecretCredentialsProvider jwtProvider = new JWTClientSecretCredentialsProvider();
|
||||
jwtProvider.setClientSecret(secret);
|
||||
return jwtProvider.createSignedRequestToken(oauth.getClientId(), getRealmInfoUrl());
|
||||
return getClientSignedJWT(secret, timeout, Algorithm.HS256);
|
||||
}
|
||||
|
||||
|
||||
private String getClientSignedJWT(String secret, int timeout, String algorithm) {
|
||||
JWTClientSecretCredentialsProvider jwtProvider = new JWTClientSecretCredentialsProvider();
|
||||
jwtProvider.setClientSecret(secret, algorithm);
|
||||
return jwtProvider.createSignedRequestToken(oauth.getClientId(), getRealmInfoUrl(), algorithm);
|
||||
}
|
||||
|
||||
private String getRealmInfoUrl() {
|
||||
String authServerBaseUrl = UriUtils.getOrigin(oauth.getRedirectUri()) + "/auth";
|
||||
return KeycloakUriBuilder.fromUri(authServerBaseUrl).path(ServiceUrlConstants.REALM_INFO_PATH).build("test").toString();
|
||||
}
|
||||
|
||||
|
||||
private OAuthClient.AccessTokenResponse doAccessTokenRequest(String code, String signedJwt) throws Exception {
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE));
|
||||
|
@ -151,11 +182,11 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
|||
parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, oauth.getRedirectUri()));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt));
|
||||
|
||||
|
||||
CloseableHttpResponse response = sendRequest(oauth.getAccessTokenUrl(), parameters);
|
||||
return new OAuthClient.AccessTokenResponse(response);
|
||||
}
|
||||
|
||||
|
||||
private CloseableHttpResponse sendRequest(String requestUrl, List<NameValuePair> parameters) throws Exception {
|
||||
CloseableHttpClient client = new DefaultHttpClient();
|
||||
try {
|
||||
|
@ -167,4 +198,5 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
|||
oauth.closeClient(client);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
|
|||
// Signature algorithms
|
||||
Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||
Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||
Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512);
|
||||
Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||
|
||||
// Encryption algorithms
|
||||
Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP);
|
||||
|
@ -137,7 +137,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
|
|||
|
||||
// Client authentication
|
||||
Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt", "client_secret_jwt", "tls_client_auth");
|
||||
Assert.assertNames(oidcConfig.getTokenEndpointAuthSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512);
|
||||
Assert.assertNames(oidcConfig.getTokenEndpointAuthSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||
|
||||
// Claims
|
||||
assertContains(oidcConfig.getClaimsSupported(), IDToken.NAME, IDToken.EMAIL, IDToken.PREFERRED_USERNAME, IDToken.FAMILY_NAME, IDToken.ACR);
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<!--
|
||||
~ Copyright 2020 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.
|
||||
-->
|
||||
|
||||
<Context path="/customer-portal">
|
||||
<Valve className="org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve"/>
|
||||
</Context>
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ Copyright 2020 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.
|
||||
-->
|
||||
|
||||
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
|
||||
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
|
||||
<Get name="securityHandler">
|
||||
<Set name="authenticator">
|
||||
<New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
|
||||
<!--
|
||||
<Set name="adapterConfig">
|
||||
<New class="org.keycloak.representations.adapters.config.AdapterConfig">
|
||||
<Set name="realm">tomcat</Set>
|
||||
<Set name="resource">customer-portal</Set>
|
||||
<Set name="authServerUrl">http://localhost:8180/auth</Set>
|
||||
<Set name="sslRequired">external</Set>
|
||||
<Set name="credentials">
|
||||
<Map>
|
||||
<Entry>
|
||||
<Item>secret</Item>
|
||||
<Item>password</Item>
|
||||
</Entry>
|
||||
</Map>
|
||||
</Set>
|
||||
<Set name="realmKey">MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</Set>
|
||||
</New>
|
||||
</Set>
|
||||
-->
|
||||
</New>
|
||||
</Set>
|
||||
</Get>
|
||||
</Configure>
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"realm": "demo",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"ssl-required": "external",
|
||||
"resource": "client-secret-jwt-secure-portal-valid-alg",
|
||||
"credentials": {
|
||||
"secret-jwt": {
|
||||
"secret": "234234-234234-234234",
|
||||
"algorithm": "HS512"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 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.
|
||||
-->
|
||||
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
|
||||
<module-name>client-secret-jwt-secure-portal-valid-alg</module-name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Servlet</servlet-name>
|
||||
<servlet-class>org.keycloak.testsuite.adapter.servlet.CallAuthenticatedServlet</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Servlet</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Permit all</web-resource-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>*</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
|
||||
<login-config>
|
||||
<auth-method>KEYCLOAK</auth-method>
|
||||
<realm-name>demo</realm-name>
|
||||
</login-config>
|
||||
|
||||
<security-role>
|
||||
<role-name>admin</role-name>
|
||||
</security-role>
|
||||
<security-role>
|
||||
<role-name>user</role-name>
|
||||
</security-role>
|
||||
</web-app>
|
|
@ -351,6 +351,17 @@
|
|||
"/client-secret-jwt-secure-portal/*"
|
||||
],
|
||||
"secret": "234234-234234-234234"
|
||||
},
|
||||
{
|
||||
"clientId": "client-secret-jwt-secure-portal-valid-alg",
|
||||
"enabled": true,
|
||||
"adminUrl": "/client-secret-jwt-secure-portal-valid-alg",
|
||||
"baseUrl": "/client-secret-jwt-secure-portal-valid-alg",
|
||||
"clientAuthenticatorType": "client-secret-jwt",
|
||||
"redirectUris": [
|
||||
"/client-secret-jwt-secure-portal-valid-alg/*"
|
||||
],
|
||||
"secret": "234234-234234-234234"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue