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;
|
package org.keycloak.adapters.authentication;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -11,6 +27,8 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
import org.keycloak.adapters.AdapterUtils;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.common.util.Time;
|
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.jose.jws.JWSBuilder;
|
||||||
import org.keycloak.representations.JsonWebToken;
|
import org.keycloak.representations.JsonWebToken;
|
||||||
|
|
||||||
|
@ -18,7 +36,6 @@ import org.keycloak.representations.JsonWebToken;
|
||||||
* Client authentication based on JWT signed by client secret instead of private key .
|
* 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.
|
* 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 {
|
public class JWTClientSecretCredentialsProvider implements ClientCredentialsProvider {
|
||||||
|
|
||||||
|
@ -28,6 +45,8 @@ public class JWTClientSecretCredentialsProvider implements ClientCredentialsProv
|
||||||
|
|
||||||
private SecretKey clientSecret;
|
private SecretKey clientSecret;
|
||||||
|
|
||||||
|
private String clientSecretJwtAlg = Algorithm.HS256;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
|
@ -44,7 +63,24 @@ public class JWTClientSecretCredentialsProvider implements ClientCredentialsProv
|
||||||
if (clientSecretString == null) {
|
if (clientSecretString == null) {
|
||||||
throw new RuntimeException("Missing parameter secret-jwt in configuration of jwt for client " + deployment.getResourceName());
|
throw new RuntimeException("Missing parameter secret-jwt in configuration of jwt for client " + deployment.getResourceName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String clientSecretJwtAlg = (String) cfg.get("algorithm");
|
||||||
|
if (clientSecretJwtAlg == null) {
|
||||||
|
// "algorithm" field is optional. fallback to HS256.
|
||||||
setClientSecret(clientSecretString);
|
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
|
@Override
|
||||||
|
@ -60,15 +96,29 @@ public class JWTClientSecretCredentialsProvider implements ClientCredentialsProv
|
||||||
// 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.
|
// 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>
|
// 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.
|
// 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) {
|
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);
|
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
|
||||||
// JOSE header {"alg":"HS256","typ" : "JWT"} no need "kid" due to using only one registered client secret.
|
String signedRequestToken = null;
|
||||||
// Use "HmacSHA256" consulting <a href="https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Mac.html">java8 api</a>.
|
if (Algorithm.HS512.equals(algorithm)) {
|
||||||
// because it must be implemented in every java platform.
|
signedRequestToken = new JWSBuilder().jsonContent(jwt).hmac512(clientSecret);
|
||||||
return new JWSBuilder().jsonContent(jwt).hmac256(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) {
|
private JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
|
||||||
|
|
|
@ -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;
|
package org.keycloak.authentication.authenticators.client;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -10,8 +25,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
@ -21,7 +34,6 @@ import org.keycloak.authentication.AuthenticationFlowError;
|
||||||
import org.keycloak.authentication.ClientAuthenticationFlowContext;
|
import org.keycloak.authentication.ClientAuthenticationFlowContext;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.crypto.HMACProvider;
|
|
||||||
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
|
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
|
||||||
import org.keycloak.models.SingleUseTokenStoreProvider;
|
import org.keycloak.models.SingleUseTokenStoreProvider;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
@ -42,14 +54,13 @@ import org.keycloak.services.Urls;
|
||||||
*
|
*
|
||||||
* TODO: Try to create abstract superclass to be shared with {@link JWTClientAuthenticator}. Most of the code can be reused
|
* 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 {
|
public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(JWTClientSecretAuthenticator.class);
|
private static final Logger logger = Logger.getLogger(JWTClientSecretAuthenticator.class);
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "client-secret-jwt";
|
public static final String PROVIDER_ID = "client-secret-jwt";
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void authenticateClient(ClientAuthenticationFlowContext context) {
|
public void authenticateClient(ClientAuthenticationFlowContext context) {
|
||||||
MultivaluedMap<String, String> params = context.getHttpRequest().getDecodedFormParameters();
|
MultivaluedMap<String, String> params = context.getHttpRequest().getDecodedFormParameters();
|
||||||
|
@ -106,15 +117,10 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
||||||
return;
|
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;
|
boolean signatureValid;
|
||||||
try {
|
try {
|
||||||
signatureValid = HMACProvider.verify(jws, clientSecret);
|
JsonWebToken jwt = context.getSession().tokens().decodeClientJWT(clientAssertion, client, JsonWebToken.class);
|
||||||
|
signatureValid = jwt != null;
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
Throwable cause = e.getCause() != null ? e.getCause() : e;
|
Throwable cause = e.getCause() != null ? e.getCause() : e;
|
||||||
throw new RuntimeException("Signature on JWT token by client secret failed validation", cause);
|
throw new RuntimeException("Signature on JWT token by client secret failed validation", cause);
|
||||||
|
@ -177,14 +183,16 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getAdapterConfiguration(ClientModel client) {
|
public Map<String, Object> getAdapterConfiguration(ClientModel client) {
|
||||||
// e.g.
|
// e.g. client adapter's keycloak.json
|
||||||
// "credentials": {
|
// "credentials": {
|
||||||
// "secret-jwt": {
|
// "secret-jwt": {
|
||||||
// "secret": "234234-234234-234234"
|
// "secret": "234234-234234-234234",
|
||||||
|
// "algorithm": "HS256"
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
Map<String, Object> props = new HashMap<>();
|
Map<String, Object> props = new HashMap<>();
|
||||||
props.put("secret", client.getSecret());
|
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<>();
|
Map<String, Object> config = new HashMap<>();
|
||||||
config.put("secret-jwt", props);
|
config.put("secret-jwt", props);
|
||||||
|
@ -228,5 +236,4 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
||||||
return new LinkedList<>();
|
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.PS256ClientSignatureVerifierProviderFactory
|
||||||
org.keycloak.crypto.PS384ClientSignatureVerifierProviderFactory
|
org.keycloak.crypto.PS384ClientSignatureVerifierProviderFactory
|
||||||
org.keycloak.crypto.PS512ClientSignatureVerifierProviderFactory
|
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.protocol.oidc.OIDCLoginProtocolService;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.VersionRepresentation;
|
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
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.filter.AdapterActionsFilter;
|
||||||
import org.keycloak.testsuite.adapter.page.BasicAuth;
|
import org.keycloak.testsuite.adapter.page.BasicAuth;
|
||||||
import org.keycloak.testsuite.adapter.page.ClientSecretJwtSecurePortal;
|
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.CustomerCookiePortal;
|
||||||
import org.keycloak.testsuite.adapter.page.CustomerCookiePortalRoot;
|
import org.keycloak.testsuite.adapter.page.CustomerCookiePortalRoot;
|
||||||
import org.keycloak.testsuite.adapter.page.CustomerDb;
|
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.auth.page.login.OIDCLogin;
|
||||||
import org.keycloak.testsuite.console.page.events.Config;
|
import org.keycloak.testsuite.console.page.events.Config;
|
||||||
import org.keycloak.testsuite.console.page.events.LoginEvents;
|
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.FollowRedirectsEngine;
|
||||||
import org.keycloak.testsuite.util.JavascriptBrowser;
|
import org.keycloak.testsuite.util.JavascriptBrowser;
|
||||||
import org.keycloak.testsuite.util.Matchers;
|
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.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotEquals;
|
import static org.junit.Assert.assertNotEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
@ -183,6 +183,8 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||||
@Page
|
@Page
|
||||||
private ClientSecretJwtSecurePortal clientSecretJwtSecurePortal;
|
private ClientSecretJwtSecurePortal clientSecretJwtSecurePortal;
|
||||||
@Page
|
@Page
|
||||||
|
private ClientSecretJwtSecurePortalValidAlg clientSecretJwtSecurePortalValidAlg;
|
||||||
|
@Page
|
||||||
private CustomerCookiePortal customerCookiePortal;
|
private CustomerCookiePortal customerCookiePortal;
|
||||||
@Page
|
@Page
|
||||||
private CustomerCookiePortalRoot customerCookiePortalRoot;
|
private CustomerCookiePortalRoot customerCookiePortalRoot;
|
||||||
|
@ -270,6 +272,11 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||||
return servletDeployment(ClientSecretJwtSecurePortal.DEPLOYMENT_NAME, CallAuthenticatedServlet.class);
|
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)
|
@Deployment(name = CustomerCookiePortalRoot.DEPLOYMENT_NAME)
|
||||||
protected static WebArchive customerCookiePortalRoot() {
|
protected static WebArchive customerCookiePortalRoot() {
|
||||||
return servletDeployment(CustomerCookiePortalRoot.DEPLOYMENT_NAME, AdapterActionsFilter.class, CustomerServlet.class, ErrorServlet.class, ServletTestUtils.class);
|
return servletDeployment(CustomerCookiePortalRoot.DEPLOYMENT_NAME, AdapterActionsFilter.class, CustomerServlet.class, ErrorServlet.class, ServletTestUtils.class);
|
||||||
|
@ -1236,7 +1243,7 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||||
// http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
|
// http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
|
||||||
String targetClientId = "client-secret-jwt-secure-portal";
|
String targetClientId = "client-secret-jwt-secure-portal";
|
||||||
|
|
||||||
expectResultOfClientAuthenticatedInClientSecretJwt(targetClientId);
|
expectResultOfClientAuthenticatedInClientSecretJwt(targetClientId, clientSecretJwtSecurePortal);
|
||||||
|
|
||||||
// test logout
|
// test logout
|
||||||
String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder())
|
String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder())
|
||||||
|
@ -1274,6 +1281,18 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||||
expectResultOfClientNotAuthenticatedInClientSecretJwt(targetClientId, expectedErrorString);
|
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
|
@Test
|
||||||
public void testTokenInCookieSSORoot() {
|
public void testTokenInCookieSSORoot() {
|
||||||
// Login
|
// Login
|
||||||
|
@ -1322,14 +1341,14 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectResultOfClientAuthenticatedInClientSecretJwt(String targetClientId) {
|
private void expectResultOfClientAuthenticatedInClientSecretJwt(String targetClientId, AbstractPageWithInjectedUrl portal) {
|
||||||
RealmRepresentation realm = testRealmResource().toRepresentation();
|
RealmRepresentation realm = testRealmResource().toRepresentation();
|
||||||
realm.setEventsEnabled(true);
|
realm.setEventsEnabled(true);
|
||||||
realm.setEnabledEventTypes(Arrays.asList("LOGIN", "CODE_TO_TOKEN"));
|
realm.setEnabledEventTypes(Arrays.asList("LOGIN", "CODE_TO_TOKEN"));
|
||||||
realm.setEventsListeners(Arrays.asList("jboss-logging", "event-queue"));
|
realm.setEventsListeners(Arrays.asList("jboss-logging", "event-queue"));
|
||||||
testRealmResource().update(realm);
|
testRealmResource().update(realm);
|
||||||
|
|
||||||
clientSecretJwtSecurePortal.navigateTo();
|
portal.navigateTo();
|
||||||
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
|
||||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||||
|
|
||||||
|
@ -1342,8 +1361,8 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||||
.detail(Details.USERNAME, "bburke@redhat.com")
|
.detail(Details.USERNAME, "bburke@redhat.com")
|
||||||
.detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED)
|
.detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED)
|
||||||
.detail(Details.REDIRECT_URI,
|
.detail(Details.REDIRECT_URI,
|
||||||
org.hamcrest.Matchers.anyOf(org.hamcrest.Matchers.equalTo(clientSecretJwtSecurePortal.getInjectedUrl().toString()),
|
org.hamcrest.Matchers.anyOf(org.hamcrest.Matchers.equalTo(portal.getInjectedUrl().toString()),
|
||||||
org.hamcrest.Matchers.equalTo(clientSecretJwtSecurePortal.getInjectedUrl().toString() + "/")))
|
org.hamcrest.Matchers.equalTo(portal.getInjectedUrl().toString() + "/")))
|
||||||
.removeDetail(Details.CODE_ID)
|
.removeDetail(Details.CODE_ID)
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
package org.keycloak.testsuite.oauth;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
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.KeycloakUriBuilder;
|
||||||
import org.keycloak.common.util.UriUtils;
|
import org.keycloak.common.util.UriUtils;
|
||||||
import org.keycloak.constants.ServiceUrlConstants;
|
import org.keycloak.constants.ServiceUrlConstants;
|
||||||
|
import org.keycloak.crypto.Algorithm;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
@ -31,11 +48,9 @@ import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Takashi Norimatsu <takashi.norimatsu.ws@hitachi.com>
|
|
||||||
*/
|
|
||||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
@AuthServerContainerExclude(AuthServer.REMOTE)
|
||||||
public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(ClientAuthSecretSignedJWTTest.class);
|
private static final Logger logger = Logger.getLogger(ClientAuthSecretSignedJWTTest.class);
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
|
@ -56,6 +71,20 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
@Test
|
@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.clientId("test-app");
|
||||||
oauth.doLogin("test-user@localhost", "password");
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
EventRepresentation loginEvent = events.expectLogin()
|
EventRepresentation loginEvent = events.expectLogin()
|
||||||
|
@ -63,7 +92,7 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
|
|
||||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
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());
|
assertEquals(200, response.getStatusCode());
|
||||||
oauth.verifyToken(response.getAccessToken());
|
oauth.verifyToken(response.getAccessToken());
|
||||||
|
@ -92,7 +121,6 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
||||||
assertEquals("unauthorized_client", response.getError());
|
assertEquals("unauthorized_client", response.getError());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAssertionReuse() throws Exception {
|
public void testAssertionReuse() throws Exception {
|
||||||
oauth.clientId("test-app");
|
oauth.clientId("test-app");
|
||||||
|
@ -132,11 +160,14 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
||||||
assertEquals("unauthorized_client", response.getError());
|
assertEquals("unauthorized_client", response.getError());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private String getClientSignedJWT(String secret, int timeout) {
|
private String getClientSignedJWT(String secret, int timeout) {
|
||||||
|
return getClientSignedJWT(secret, timeout, Algorithm.HS256);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClientSignedJWT(String secret, int timeout, String algorithm) {
|
||||||
JWTClientSecretCredentialsProvider jwtProvider = new JWTClientSecretCredentialsProvider();
|
JWTClientSecretCredentialsProvider jwtProvider = new JWTClientSecretCredentialsProvider();
|
||||||
jwtProvider.setClientSecret(secret);
|
jwtProvider.setClientSecret(secret, algorithm);
|
||||||
return jwtProvider.createSignedRequestToken(oauth.getClientId(), getRealmInfoUrl());
|
return jwtProvider.createSignedRequestToken(oauth.getClientId(), getRealmInfoUrl(), algorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getRealmInfoUrl() {
|
private String getRealmInfoUrl() {
|
||||||
|
@ -167,4 +198,5 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
|
||||||
oauth.closeClient(client);
|
oauth.closeClient(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
|
||||||
// Signature algorithms
|
// 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.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.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
|
// Encryption algorithms
|
||||||
Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP);
|
Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP);
|
||||||
|
@ -137,7 +137,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
// Client authentication
|
// Client authentication
|
||||||
Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt", "client_secret_jwt", "tls_client_auth");
|
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
|
// Claims
|
||||||
assertContains(oidcConfig.getClaimsSupported(), IDToken.NAME, IDToken.EMAIL, IDToken.PREFERRED_USERNAME, IDToken.FAMILY_NAME, IDToken.ACR);
|
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/*"
|
"/client-secret-jwt-secure-portal/*"
|
||||||
],
|
],
|
||||||
"secret": "234234-234234-234234"
|
"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