Compare commits
276
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
242dc980fc | ||
|
|
b61ffa95cc | ||
|
|
a809fd2973 | ||
|
|
da82baec4a | ||
|
|
141ffc0ee0 | ||
|
|
0828136f01 | ||
|
|
6e9b9bedbb | ||
|
|
57bf9254e1 | ||
|
|
f0ac725984 | ||
|
|
c0e03c05c7 | ||
|
|
14cf2c9aba | ||
|
|
0781f3b33d | ||
|
|
b683e43ea7 | ||
|
|
51e4beccf4 | ||
|
|
2b84b8255e | ||
|
|
0cad34abbe | ||
|
|
389e2b42f1 | ||
|
|
4e43518b2a | ||
|
|
cc27dd485b | ||
|
|
b78cc63f0d | ||
|
|
b84507507d | ||
|
|
fbcfcfa088 | ||
|
|
0809033924 | ||
|
|
d6a0c9e186 | ||
|
|
7c2ea4db98 | ||
|
|
4b417c5ae8 | ||
|
|
2913ee8e23 | ||
|
|
eedb40bbe0 | ||
|
|
f68b28db20 | ||
|
|
c83e192a6f | ||
|
|
d7615d6a68 | ||
|
|
9c0e7cb4a5 | ||
|
|
f1f2040910 | ||
|
|
0ccb98a81f | ||
|
|
5b5cfb01b9 | ||
|
|
b3131bf679 | ||
|
|
79c9078caa | ||
|
|
4b01c8e4dc | ||
|
|
a3c57f4326 | ||
|
|
ab58779d22 | ||
|
|
38ae6c981b | ||
|
|
e678a0362b | ||
|
|
8000baeb1f | ||
|
|
cf7f28d97e | ||
|
|
84f5df4814 | ||
|
|
d081f967ea | ||
|
|
54ee055bd8 | ||
|
|
e48340121b | ||
|
|
b81891f89b | ||
|
|
1385d3c219 | ||
|
|
49547ccfbc | ||
|
|
e8a65017fa | ||
|
|
5e57e84384 | ||
|
|
5972c94dc8 | ||
|
|
c40d79afb0 | ||
|
|
d05a894831 | ||
|
|
dfec691de0 | ||
|
|
03e6c0884a | ||
|
|
e0f753bcf5 | ||
|
|
1fd5af840b | ||
|
|
8dc65cff0a | ||
|
|
3dbd0d5063 | ||
|
|
6c09df0cae | ||
|
|
09bc16e6c5 | ||
|
|
f92ad70ff0 | ||
|
|
15f1b948bf | ||
|
|
95fc8f79dd | ||
|
|
f50e08d111 | ||
|
|
257a973995 | ||
|
|
606c385f26 | ||
|
|
e6486ab1c1 | ||
|
|
b45089f5f0 | ||
|
|
e85d8774da | ||
|
|
62000e042d | ||
|
|
fa1b998802 | ||
|
|
df163d86e8 | ||
|
|
70a3dd1e4a | ||
|
|
80a80512ea | ||
|
|
4f66919474 | ||
|
|
bf69bc94bb | ||
|
|
585653b88e | ||
|
|
4d5fd0b75e | ||
|
|
2a8b2aabb9 | ||
|
|
1d03eb5f2b | ||
|
|
4da07474fa | ||
|
|
14b109da18 | ||
|
|
7de0b329a2 | ||
|
|
81123e0eea | ||
|
|
8919015f74 | ||
|
|
b72166ebf1 | ||
|
|
10009868a3 | ||
|
|
0a0d2174e4 | ||
|
|
038c4765a3 | ||
|
|
c7a97cfd21 | ||
|
|
0b21ed3e0e | ||
|
|
906739f8f3 | ||
|
|
c9630157e8 | ||
|
|
51eef9b8b3 | ||
|
|
330663dfd3 | ||
|
|
e54c1d7de1 | ||
|
|
da6781dd91 | ||
|
|
4f771ac51a | ||
|
|
5ccf412fe2 | ||
|
|
ac6eb79b53 | ||
|
|
a415ea3670 | ||
|
|
32b62b2a70 | ||
|
|
4344ceefc2 | ||
|
|
fa97c2419e | ||
|
|
964693077a | ||
|
|
2a1a19f290 | ||
|
|
2336e97bf0 | ||
|
|
b6978a424f | ||
|
|
8e877a7f6c | ||
|
|
886528dab8 | ||
|
|
9a2d36aa9d | ||
|
|
ea9c663ae1 | ||
|
|
eec712a259 | ||
|
|
54ebc1918c | ||
|
|
d60dcb4c62 | ||
|
|
8e64bc3e4d | ||
|
|
cf1e8d1dd8 | ||
|
|
0b8fc3d6e1 | ||
|
|
55f747ecd0 | ||
|
|
a011f44d39 | ||
|
|
00b832b1cd | ||
|
|
8840bc073f | ||
|
|
bfd76d7813 | ||
|
|
2066259518 | ||
|
|
9bca7fb7c5 | ||
|
|
cf095766c7 | ||
|
|
c73de4f2b9 | ||
|
|
4daafa56fc | ||
|
|
16430791bb | ||
|
|
5e2446f838 | ||
|
|
b18c8c92c5 | ||
|
|
e8bbfd9012 | ||
|
|
94d375886f | ||
|
|
f9c017efaf | ||
|
|
0db2640076 | ||
|
|
ebabc6fd8a | ||
|
|
56320cc023 | ||
|
|
e9d22f3506 | ||
|
|
83d0f313ce | ||
|
|
cac19c8a8f | ||
|
|
9452d37926 | ||
|
|
2e284bdd9b | ||
|
|
22dc6ffcf3 | ||
|
|
54cd41c955 | ||
|
|
7cf8bd2dcf | ||
|
|
6a959b32fc | ||
|
|
3ce0c57e17 | ||
|
|
0fd11d16ee | ||
|
|
6ca5b7de03 | ||
|
|
c198f4ffa7 | ||
|
|
665bcab7b7 | ||
|
|
13afc0147e | ||
|
|
26db0dd1fc | ||
|
|
1f50421a54 | ||
|
|
1b3549f828 | ||
|
|
d635e7aadd | ||
|
|
201d2c6aac | ||
|
|
31074c3c8d | ||
|
|
2876a98a97 | ||
|
|
af4c74f1d9 | ||
|
|
e622ea1eba | ||
|
|
0269db0e85 | ||
|
|
859b575a12 | ||
|
|
f0b44ea93b | ||
|
|
802f648757 | ||
|
|
b834f6ebf9 | ||
|
|
6201257f76 | ||
|
|
670103356f | ||
|
|
a82278dcbf | ||
|
|
4845286e04 | ||
|
|
ad1ca78034 | ||
|
|
30b785c60d | ||
|
|
b4c5eb8354 | ||
|
|
91c3977d71 | ||
|
|
bb0b3c6470 | ||
|
|
8036f12114 | ||
|
|
457f183d46 | ||
|
|
eaf386f1d2 | ||
|
|
04182d1d44 | ||
|
|
5e360cf919 | ||
|
|
f371a00e98 | ||
|
|
e9f9f90b35 | ||
|
|
971f053f13 | ||
|
|
0482ec40fd | ||
|
|
f857625d07 | ||
|
|
01e7d79b1f | ||
|
|
d264a823d9 | ||
|
|
bfaaf2b8fa | ||
|
|
ca6d8c9dbe | ||
|
|
0e8337ba8e | ||
|
|
838a045239 | ||
|
|
33a0dc880f | ||
|
|
2d40c8d8c2 | ||
|
|
5d573b1b3e | ||
|
|
642aeeb937 | ||
|
|
e8e1cce14a | ||
|
|
6c8b207bf5 | ||
|
|
a183d50ad2 | ||
|
|
d26f2b44bd | ||
|
|
3bb587530b | ||
|
|
438ec8d245 | ||
|
|
bc05010ece | ||
|
|
0180d54dd9 | ||
|
|
45ea3c8b47 | ||
|
|
ef3aef9381 | ||
|
|
93c51c5aad | ||
|
|
6b21b4d87b | ||
|
|
9303a9c7d0 | ||
|
|
66c13b4a72 | ||
|
|
36fc643bfb | ||
|
|
0197600565 | ||
|
|
ed67dcc712 | ||
|
|
4b813ea1d1 | ||
|
|
4aa996c2db | ||
|
|
b1535d4751 | ||
|
|
71f0c01d4f | ||
|
|
f554fd90db | ||
|
|
e5a2642e62 | ||
|
|
8c2e756732 | ||
|
|
d8e98d1de6 | ||
|
|
dd8a64f30c | ||
|
|
3ce8da0126 | ||
|
|
30d7a5b01f | ||
|
|
7968394537 | ||
|
|
6a093abfdb | ||
|
|
d0b3cb32f0 | ||
|
|
5d028205bf | ||
|
|
0b2896458a | ||
|
|
e74f037732 | ||
|
|
ef7cb1e0cb | ||
|
|
49e43e59c4 | ||
|
|
996f60ff57 | ||
|
|
90c4de27e5 | ||
|
|
815e9e8e02 | ||
|
|
d1e71acf10 | ||
|
|
fc009969c9 | ||
|
|
63e8e7f842 | ||
|
|
210143738e | ||
|
|
deae380941 | ||
|
|
1fccff7e7c | ||
|
|
275e871618 | ||
|
|
c41bc65bf8 | ||
|
|
975dfe9489 | ||
|
|
5cf6438a45 | ||
|
|
05a8fffdbf | ||
|
|
c517565cc5 | ||
|
|
0a8ca19944 | ||
|
|
7c1eb5582a | ||
|
|
61bd9bb58c | ||
|
|
e2970fcf8a | ||
|
|
8b8b5174eb | ||
|
|
b30b96b9a1 | ||
|
|
dce2b1dfde | ||
|
|
258af94889 | ||
|
|
dabd7c0b27 | ||
|
|
f6786e29c6 | ||
|
|
eb852265ef | ||
|
|
b2f10359c8 | ||
|
|
3551912506 | ||
|
|
a87ee04024 | ||
|
|
cda563e672 | ||
|
|
a434e0540b | ||
|
|
d0992ef9bd | ||
|
|
a250f08b6c | ||
|
|
b782892769 | ||
|
|
fb507048f5 | ||
|
|
beee0c1e67 | ||
|
|
669b0143af | ||
|
|
1a6bb2fedb | ||
|
|
6bab704bba | ||
|
|
fe5fe4c968 | ||
|
|
88bfa563df |
No files matched your search
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+13
@@ -85,6 +85,9 @@ public class KeycloakDeployment {
|
||||
protected int publicKeyCacheTtl;
|
||||
private PolicyEnforcer policyEnforcer;
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636
|
||||
protected boolean pkce = false;
|
||||
|
||||
public KeycloakDeployment() {
|
||||
}
|
||||
|
||||
@@ -414,4 +417,14 @@ public class KeycloakDeployment {
|
||||
public PolicyEnforcer getPolicyEnforcer() {
|
||||
return policyEnforcer;
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636
|
||||
public boolean isPkce() {
|
||||
return pkce;
|
||||
}
|
||||
|
||||
public void setPkce(boolean pkce) {
|
||||
this.pkce = pkce;
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -98,6 +98,11 @@ public class KeycloakDeploymentBuilder {
|
||||
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636
|
||||
if (adapterConfig.isPkce()) {
|
||||
deployment.setPkce(true);
|
||||
}
|
||||
|
||||
deployment.setBearerOnly(adapterConfig.isBearerOnly());
|
||||
deployment.setAutodetectBearerOnly(adapterConfig.isAutodetectBearerOnly());
|
||||
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
|
||||
|
||||
@@ -33,6 +33,8 @@ import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -46,6 +48,8 @@ import java.util.List;
|
||||
*/
|
||||
public class ServerRequest {
|
||||
|
||||
private static Logger logger = Logger.getLogger(ServerRequest.class);
|
||||
|
||||
public static class HttpFailure extends Exception {
|
||||
private int status;
|
||||
private String error;
|
||||
@@ -136,6 +140,62 @@ public class ServerRequest {
|
||||
}
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636#section-4
|
||||
public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId, String codeVerifier) throws IOException, HttpFailure {
|
||||
List<NameValuePair> formparams = new ArrayList<>();
|
||||
redirectUri = stripOauthParametersFromRedirect(redirectUri);
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code"));
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
|
||||
if (sessionId != null) {
|
||||
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_STATE, sessionId));
|
||||
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_HOST, HostUtils.getHostName()));
|
||||
}
|
||||
// https://tools.ietf.org/html/rfc7636#section-4
|
||||
if (codeVerifier != null) {
|
||||
logger.debugf("add to POST parameters of Token Request, codeVerifier = %s", codeVerifier);
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE_VERIFIER, codeVerifier));
|
||||
} else {
|
||||
logger.debug("add to POST parameters of Token Request without codeVerifier");
|
||||
}
|
||||
|
||||
HttpPost post = new HttpPost(deployment.getTokenUrl());
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
post.setEntity(form);
|
||||
HttpResponse response = deployment.getClient().execute(post);
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (status != 200) {
|
||||
error(status, entity);
|
||||
}
|
||||
if (entity == null) {
|
||||
throw new HttpFailure(status, null);
|
||||
}
|
||||
InputStream is = entity.getContent();
|
||||
try {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
int c;
|
||||
while ((c = is.read()) != -1) {
|
||||
os.write(c);
|
||||
}
|
||||
byte[] bytes = os.toByteArray();
|
||||
String json = new String(bytes);
|
||||
try {
|
||||
return JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new IOException(json, e);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
|
||||
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
|
||||
|
||||
+22
-42
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -29,10 +28,8 @@ import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Response;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
||||
@@ -56,7 +53,7 @@ public abstract class AbstractPolicyEnforcer {
|
||||
this.policyEnforcer = policyEnforcer;
|
||||
this.enforcerConfig = policyEnforcer.getEnforcerConfig();
|
||||
this.authzClient = policyEnforcer.getClient();
|
||||
this.pathMatcher = new PathMatcher();
|
||||
this.pathMatcher = policyEnforcer.getPathMatcher();
|
||||
this.paths = policyEnforcer.getPaths();
|
||||
}
|
||||
|
||||
@@ -74,7 +71,6 @@ public abstract class AbstractPolicyEnforcer {
|
||||
|
||||
if (accessToken != null) {
|
||||
Request request = httpFacade.getRequest();
|
||||
Response response = httpFacade.getResponse();
|
||||
String path = getPath(request);
|
||||
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
||||
|
||||
@@ -86,7 +82,12 @@ public abstract class AbstractPolicyEnforcer {
|
||||
}
|
||||
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", path);
|
||||
response.sendError(403, "Could not find a configuration for path [" + path + "].");
|
||||
|
||||
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
|
||||
return createAuthorizationContext(accessToken);
|
||||
}
|
||||
|
||||
handleAccessDenied(httpFacade);
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
@@ -95,20 +96,21 @@ public abstract class AbstractPolicyEnforcer {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
|
||||
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
|
||||
Set<String> requiredScopes = getRequiredScopes(pathConfig, request);
|
||||
|
||||
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||
if (isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
|
||||
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
|
||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||
response.sendError(403, "Authorization failed.");
|
||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||
|
||||
if (!challenge(pathConfig, requiredScopes, httpFacade)) {
|
||||
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
|
||||
handleAccessDenied(httpFacade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,6 +171,10 @@ public abstract class AbstractPolicyEnforcer {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void handleAccessDenied(OIDCHttpFacade httpFacade) {
|
||||
httpFacade.getResponse().sendError(403);
|
||||
}
|
||||
|
||||
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
|
||||
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
||||
|
||||
@@ -198,7 +204,7 @@ public abstract class AbstractPolicyEnforcer {
|
||||
}
|
||||
|
||||
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
|
||||
return new AuthorizationContext() {
|
||||
return new ClientAuthorizationContext(authzClient) {
|
||||
@Override
|
||||
public boolean hasPermission(String resourceName, String scopeName) {
|
||||
return granted;
|
||||
@@ -226,32 +232,6 @@ public abstract class AbstractPolicyEnforcer {
|
||||
};
|
||||
}
|
||||
|
||||
private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
|
||||
String path = getPath(request);
|
||||
|
||||
if (originalConfig.hasPattern()) {
|
||||
ProtectedResource resource = this.authzClient.protection().resource();
|
||||
Set<String> search = resource.findByFilter("uri=" + path);
|
||||
|
||||
if (!search.isEmpty()) {
|
||||
// resource does exist on the server, cache it
|
||||
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
|
||||
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
|
||||
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setParentConfig(originalConfig);
|
||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||
|
||||
this.policyEnforcer.addPath(config);
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return originalConfig;
|
||||
}
|
||||
|
||||
private String getPath(Request request) {
|
||||
return request.getRelativePath();
|
||||
}
|
||||
@@ -273,7 +253,7 @@ public abstract class AbstractPolicyEnforcer {
|
||||
}
|
||||
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
|
||||
return new AuthorizationContext(accessToken, this.paths);
|
||||
return new ClientAuthorizationContext(accessToken, this.paths, authzClient);
|
||||
}
|
||||
|
||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
|
||||
+6
@@ -56,6 +56,9 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\"");
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending Entitlement challenge");
|
||||
}
|
||||
}
|
||||
|
||||
private void challengeUmaAuthentication(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
||||
@@ -66,6 +69,9 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\"");
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending UMA challenge");
|
||||
}
|
||||
}
|
||||
|
||||
private String getPermissionTicket(PathConfig pathConfig, Set<String> requiredScopes, AuthzClient authzClient) {
|
||||
|
||||
+6
-2
@@ -83,6 +83,12 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
|
||||
@Override
|
||||
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
||||
handleAccessDenied(facade);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleAccessDenied(OIDCHttpFacade facade) {
|
||||
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
|
||||
@@ -92,8 +98,6 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
} else {
|
||||
response.sendError(403);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade httpFacade) {
|
||||
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PathCache {
|
||||
|
||||
/**
|
||||
* The load factor.
|
||||
*/
|
||||
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
|
||||
|
||||
private final Map<String, CacheEntry> cache;
|
||||
|
||||
private final AtomicBoolean writing = new AtomicBoolean(false);
|
||||
|
||||
private final long maxAge;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param maxEntries the maximum number of entries to keep in the cache
|
||||
*/
|
||||
public PathCache(int maxEntries) {
|
||||
this(maxEntries, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param maxEntries the maximum number of entries to keep in the cache
|
||||
* @param maxAge the time in milliseconds that an entry can stay in the cache. If {@code -1}, entries never expire
|
||||
*/
|
||||
public PathCache(final int maxEntries, long maxAge) {
|
||||
cache = new LinkedHashMap<String, CacheEntry>(16, DEFAULT_LOAD_FACTOR, true) {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry eldest) {
|
||||
return cache.size() > maxEntries;
|
||||
}
|
||||
};
|
||||
this.maxAge = maxAge;
|
||||
}
|
||||
|
||||
public void put(String uri, PathConfig newValue) {
|
||||
try {
|
||||
if (parkForWriteAndCheckInterrupt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CacheEntry cacheEntry = cache.get(uri);
|
||||
|
||||
if (cacheEntry == null) {
|
||||
cache.put(uri, new CacheEntry(uri, newValue, maxAge));
|
||||
}
|
||||
} finally {
|
||||
writing.lazySet(false);
|
||||
}
|
||||
}
|
||||
|
||||
public PathConfig get(String uri) {
|
||||
if (parkForReadAndCheckInterrupt()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CacheEntry cached = cache.get(uri);
|
||||
|
||||
if (cached != null) {
|
||||
return removeIfExpired(cached);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
try {
|
||||
if (parkForWriteAndCheckInterrupt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.remove(key);
|
||||
} finally {
|
||||
writing.lazySet(false);
|
||||
}
|
||||
}
|
||||
|
||||
private PathConfig removeIfExpired(CacheEntry cached) {
|
||||
if (cached == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cached.isExpired()) {
|
||||
remove(cached.key());
|
||||
return null;
|
||||
}
|
||||
|
||||
return cached.value();
|
||||
}
|
||||
|
||||
private boolean parkForWriteAndCheckInterrupt() {
|
||||
while (!writing.compareAndSet(false, true)) {
|
||||
LockSupport.parkNanos(1L);
|
||||
if (Thread.interrupted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean parkForReadAndCheckInterrupt() {
|
||||
while (writing.get()) {
|
||||
LockSupport.parkNanos(1L);
|
||||
if (Thread.interrupted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final class CacheEntry {
|
||||
|
||||
final String key;
|
||||
final PathConfig value;
|
||||
final long expiration;
|
||||
|
||||
CacheEntry(String key, PathConfig value, long maxAge) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
if(maxAge == -1) {
|
||||
expiration = -1;
|
||||
} else {
|
||||
expiration = System.currentTimeMillis() + maxAge;
|
||||
}
|
||||
}
|
||||
|
||||
String key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
PathConfig value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
boolean isExpired() {
|
||||
return expiration != -1 ? System.currentTimeMillis() > expiration : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+193
-60
@@ -17,93 +17,226 @@
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
class PathMatcher {
|
||||
|
||||
private static final String ANY_RESOURCE_PATTERN = "/*";
|
||||
private static final char WILDCARD = '*';
|
||||
private final AuthzClient authzClient;
|
||||
// TODO: make this configurable
|
||||
private PathCache cache = new PathCache(100, 30000);
|
||||
|
||||
PathConfig matches(final String requestedUri, Map<String, PathConfig> paths) {
|
||||
PathConfig pathConfig = paths.get(requestedUri);
|
||||
public PathMatcher(AuthzClient authzClient) {
|
||||
this.authzClient = authzClient;
|
||||
}
|
||||
|
||||
public PathConfig matches(final String targetUri, Map<String, PathConfig> paths) {
|
||||
PathConfig pathConfig = paths.get(targetUri) == null ? cache.get(targetUri) : paths.get(targetUri);
|
||||
|
||||
if (pathConfig != null) {
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
PathConfig actualConfig = null;
|
||||
PathConfig matchingAnyPath = null;
|
||||
PathConfig matchingAnySuffixPath = null;
|
||||
|
||||
for (PathConfig entry : paths.values()) {
|
||||
String protectedUri = entry.getPath();
|
||||
String selectedUri = null;
|
||||
String expectedUri = entry.getPath();
|
||||
String matchingUri = null;
|
||||
|
||||
if (protectedUri.equals(ANY_RESOURCE_PATTERN) && actualConfig == null) {
|
||||
selectedUri = protectedUri;
|
||||
if (exactMatch(expectedUri, targetUri, expectedUri)) {
|
||||
matchingUri = expectedUri;
|
||||
}
|
||||
|
||||
int suffixIndex = protectedUri.indexOf(ANY_RESOURCE_PATTERN + ".");
|
||||
if (isTemplate(expectedUri)) {
|
||||
String templateUri = buildUriFromTemplate(expectedUri, targetUri);
|
||||
|
||||
if (suffixIndex != -1) {
|
||||
String protectedSuffix = protectedUri.substring(suffixIndex + ANY_RESOURCE_PATTERN.length());
|
||||
|
||||
if (requestedUri.endsWith(protectedSuffix)) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
}
|
||||
|
||||
if (protectedUri.equals(requestedUri)) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
|
||||
if (protectedUri.endsWith(ANY_RESOURCE_PATTERN)) {
|
||||
String formattedPattern = removeWildCardsFromUri(protectedUri);
|
||||
|
||||
if (!formattedPattern.equals("/") && requestedUri.startsWith(formattedPattern)) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
|
||||
if (!formattedPattern.equals("/") && formattedPattern.endsWith("/") && formattedPattern.substring(0, formattedPattern.length() - 1).equals(requestedUri)) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
}
|
||||
|
||||
int startRegex = protectedUri.indexOf('{');
|
||||
|
||||
if (startRegex != -1) {
|
||||
String prefix = protectedUri.substring(0, startRegex);
|
||||
|
||||
if (requestedUri.startsWith(prefix)) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedUri != null) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
|
||||
if (selectedUri != null) {
|
||||
if (actualConfig == null) {
|
||||
actualConfig = entry;
|
||||
} else {
|
||||
if (actualConfig.equals(ANY_RESOURCE_PATTERN)) {
|
||||
actualConfig = entry;
|
||||
if (templateUri != null) {
|
||||
if (exactMatch(expectedUri, targetUri, templateUri)) {
|
||||
matchingUri = templateUri;
|
||||
entry = resolvePathConfig(entry, targetUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (protectedUri.startsWith(removeWildCardsFromUri(actualConfig.getPath()))) {
|
||||
actualConfig = entry;
|
||||
if (matchingUri != null) {
|
||||
StringBuilder path = new StringBuilder(expectedUri);
|
||||
int patternIndex = path.indexOf("/" + WILDCARD);
|
||||
|
||||
if (patternIndex != -1) {
|
||||
path.delete(patternIndex, path.length());
|
||||
}
|
||||
|
||||
patternIndex = path.indexOf("{");
|
||||
|
||||
if (patternIndex != -1) {
|
||||
path.delete(patternIndex, path.length());
|
||||
}
|
||||
|
||||
String pathString = path.toString();
|
||||
|
||||
if ("".equals(pathString)) {
|
||||
pathString = "/";
|
||||
}
|
||||
|
||||
if (matchingUri.equals(targetUri)) {
|
||||
cache.put(targetUri, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
if (WILDCARD == expectedUri.charAt(expectedUri.length() - 1)) {
|
||||
matchingAnyPath = entry;
|
||||
} else {
|
||||
int suffixIndex = expectedUri.indexOf(WILDCARD + ".");
|
||||
|
||||
if (suffixIndex != -1) {
|
||||
String protectedSuffix = expectedUri.substring(suffixIndex + 1);
|
||||
|
||||
if (targetUri.endsWith(protectedSuffix)) {
|
||||
matchingAnySuffixPath = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actualConfig;
|
||||
if (matchingAnySuffixPath != null) {
|
||||
cache.put(targetUri, matchingAnySuffixPath);
|
||||
return matchingAnySuffixPath;
|
||||
}
|
||||
|
||||
if (matchingAnyPath != null) {
|
||||
cache.put(targetUri, matchingAnyPath);
|
||||
}
|
||||
|
||||
return matchingAnyPath;
|
||||
}
|
||||
|
||||
private String removeWildCardsFromUri(String protectedUri) {
|
||||
return protectedUri.replaceAll("/[*]", "/");
|
||||
private boolean exactMatch(String expectedUri, String targetUri, String value) {
|
||||
if (targetUri.equals(value)) {
|
||||
return value.equals(targetUri);
|
||||
}
|
||||
|
||||
if (endsWithWildcard(expectedUri)) {
|
||||
return targetUri.startsWith(expectedUri.substring(0, expectedUri.length() - 2));
|
||||
}
|
||||
|
||||
String suffix = "/*.";
|
||||
int suffixIndex = expectedUri.indexOf(suffix);
|
||||
|
||||
if (suffixIndex != -1) {
|
||||
return targetUri.endsWith(expectedUri.substring(suffixIndex + suffix.length() - 1));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public String buildUriFromTemplate(String expectedUri, String targetUri) {
|
||||
int patternStartIndex = expectedUri.indexOf("{");
|
||||
|
||||
if (patternStartIndex == -1 || patternStartIndex >= targetUri.length()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (expectedUri.split("/").length > targetUri.split("/").length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
char[] expectedUriChars = expectedUri.toCharArray();
|
||||
char[] matchingUri = Arrays.copyOfRange(expectedUriChars, 0, patternStartIndex);
|
||||
int matchingUriLastIndex = matchingUri.length;
|
||||
String targetUriParams = targetUri.substring(patternStartIndex);
|
||||
|
||||
if (Arrays.equals(matchingUri, Arrays.copyOf(targetUri.toCharArray(), matchingUri.length))) {
|
||||
matchingUri = Arrays.copyOf(matchingUri, targetUri.length());
|
||||
int paramIndex = 0;
|
||||
|
||||
for (int i = patternStartIndex; i < expectedUriChars.length; i++) {
|
||||
if (matchingUriLastIndex >= matchingUri.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
char c = expectedUriChars[i];
|
||||
|
||||
if (c == '{' || c == '*') {
|
||||
String[] params = targetUriParams.split("/");
|
||||
|
||||
for (int k = paramIndex; k <= (c == '*' ? params.length : paramIndex); k++) {
|
||||
if (k == params.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
int paramLength = params[k].length();
|
||||
|
||||
if (matchingUriLastIndex + paramLength > matchingUri.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int j = 0; j < paramLength; j++) {
|
||||
matchingUri[matchingUriLastIndex++] = params[k].charAt(j);
|
||||
}
|
||||
|
||||
if (c == '*' && matchingUriLastIndex < matchingUri.length) {
|
||||
matchingUri[matchingUriLastIndex++] = '/';
|
||||
}
|
||||
}
|
||||
|
||||
i = expectedUri.indexOf('}', i);
|
||||
} else {
|
||||
if (c == '/') {
|
||||
paramIndex++;
|
||||
}
|
||||
matchingUri[matchingUriLastIndex++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingUri[matchingUri.length - 1] == '\u0000') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return String.valueOf(matchingUri);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean endsWithWildcard(String expectedUri) {
|
||||
return WILDCARD == expectedUri.charAt(expectedUri.length() - 1);
|
||||
}
|
||||
|
||||
private boolean isTemplate(String uri) {
|
||||
return uri.indexOf("{") != -1;
|
||||
}
|
||||
|
||||
private PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
|
||||
if (originalConfig.hasPattern()) {
|
||||
ProtectedResource resource = this.authzClient.protection().resource();
|
||||
Set<String> search = resource.findByFilter("uri=" + path);
|
||||
|
||||
if (!search.isEmpty()) {
|
||||
// resource does exist on the server, cache it
|
||||
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
|
||||
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
|
||||
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setParentConfig(originalConfig);
|
||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return originalConfig;
|
||||
}
|
||||
}
|
||||
+22
-2
@@ -21,7 +21,9 @@ import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.ClientAuthenticator;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.representation.RegistrationResponse;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
@@ -51,11 +53,18 @@ public class PolicyEnforcer {
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final PathMatcher pathMatcher;
|
||||
|
||||
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
|
||||
this.deployment = deployment;
|
||||
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
||||
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
|
||||
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()), new ClientAuthenticator() {
|
||||
@Override
|
||||
public void configureClientCredentials(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders) {
|
||||
ClientCredentialsProviderUtils.setClientCredentials(PolicyEnforcer.this.deployment, requestHeaders, requestParams);
|
||||
}
|
||||
});
|
||||
this.pathMatcher = new PathMatcher(this.authzClient);
|
||||
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
@@ -218,7 +227,14 @@ public class PolicyEnforcer {
|
||||
|
||||
pathConfig.setId(resourceDescription.getId());
|
||||
pathConfig.setName(resourceDescription.getName());
|
||||
pathConfig.setPath(resourceDescription.getUri());
|
||||
|
||||
String uri = resourceDescription.getUri();
|
||||
|
||||
if (uri == null || "".equals(uri.trim())) {
|
||||
throw new RuntimeException("Failed to configure paths. Resource [" + resourceDescription.getName() + "] has an invalid or empty URI [" + uri + "].");
|
||||
}
|
||||
|
||||
pathConfig.setPath(uri);
|
||||
|
||||
List<String> scopeNames = new ArrayList<>();
|
||||
|
||||
@@ -231,4 +247,8 @@ public class PolicyEnforcer {
|
||||
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
public PathMatcher getPathMatcher() {
|
||||
return pathMatcher;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -88,7 +88,6 @@
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>3.1.0.GA</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -104,7 +103,6 @@ projects that depend on this project.-->
|
||||
<dependency>
|
||||
<groupId>org.jboss.msc</groupId>
|
||||
<artifactId>jboss-msc</artifactId>
|
||||
<version>1.0.2.GA</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
|
||||
@@ -30,6 +30,18 @@
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.as</groupId>
|
||||
<artifactId>jboss-as-parent</artifactId>
|
||||
<version>${jboss.as.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<modules>
|
||||
<module>as7-adapter-spi</module>
|
||||
<module>as7-adapter</module>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak Jetty Integration</name>
|
||||
@@ -33,9 +33,22 @@
|
||||
<modules>
|
||||
<module>jetty-core</module>
|
||||
<module>jetty8.1</module>
|
||||
<module>jetty9.1</module>
|
||||
<module>jetty9.2</module>
|
||||
<module>jetty9.3</module>
|
||||
<module>jetty9.4</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>community</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!product</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>jetty9.1</module>
|
||||
<module>jetty9.4</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
declare module KeycloakModule {
|
||||
|
||||
export interface Promise {
|
||||
success(callback: Function): Promise;
|
||||
error(callback: Function): Promise;
|
||||
}
|
||||
|
||||
export type ResponseModes = "query" | "fragment";
|
||||
export type Flows = "standard" | "implicit" | "hybrid";
|
||||
|
||||
export interface InitOptions {
|
||||
checkLoginIframe?: boolean;
|
||||
checkLoginIframeInterval?: number;
|
||||
onLoad?: string;
|
||||
adapter?: string;
|
||||
responseMode?: ResponseModes;
|
||||
flow?: Flows;
|
||||
token?: string;
|
||||
refreshToken?: string;
|
||||
idToken?: string;
|
||||
timeSkew?: number;
|
||||
}
|
||||
|
||||
export interface LoginOptions {
|
||||
redirectUri?: string;
|
||||
prompt?: string;
|
||||
maxAge?: number;
|
||||
loginHint?: string;
|
||||
action?: string;
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export interface RedirectUriOptions {
|
||||
redirectUri?: string;
|
||||
}
|
||||
|
||||
export interface KeycloakClient {
|
||||
init(options?: InitOptions): Promise;
|
||||
login(options?: LoginOptions): Promise;
|
||||
createLoginUrl(options?: LoginOptions): string;
|
||||
logout(options?: RedirectUriOptions): Promise;
|
||||
createLogoutUrl(options?: RedirectUriOptions): string;
|
||||
register(options?: LoginOptions): Promise;
|
||||
createRegisterUrl(options?: RedirectUriOptions): string;
|
||||
accountManagement(): Promise;
|
||||
createAccountUrl(options?: RedirectUriOptions): string;
|
||||
hasRealmRole(role: string): boolean;
|
||||
hasResourceRole(role: string, resource?: string): boolean;
|
||||
loadUserProfile(): Promise;
|
||||
isTokenExpired(minValidity: number): boolean;
|
||||
updateToken(minValidity: number): Promise;
|
||||
clearToken(): any;
|
||||
|
||||
realm: string;
|
||||
clientId: string;
|
||||
authServerUrl: string;
|
||||
|
||||
token: string;
|
||||
tokenParsed: any;
|
||||
refreshToken: string;
|
||||
refreshTokenParsed: any;
|
||||
idToken: string;
|
||||
idTokenParsed: any;
|
||||
realmAccess: any;
|
||||
resourceAccess: any;
|
||||
authenticated: boolean;
|
||||
subject: string;
|
||||
timeSkew: number;
|
||||
responseMode: ResponseModes;
|
||||
flow: Flows;
|
||||
responseType: string;
|
||||
|
||||
onReady: Function;
|
||||
onAuthSuccess: Function;
|
||||
onAuthError: Function;
|
||||
onAuthRefreshSuccess: Function;
|
||||
onAuthRefreshError: Function;
|
||||
onAuthLogout: Function;
|
||||
onTokenExpired: Function;
|
||||
}
|
||||
}
|
||||
|
||||
declare var Keycloak: {
|
||||
new(config?: any): KeycloakModule.KeycloakClient;
|
||||
};
|
||||
@@ -157,7 +157,7 @@
|
||||
processCallback(callback, initPromise);
|
||||
return;
|
||||
} else if (initOptions) {
|
||||
if (initOptions.refreshToken) {
|
||||
if (initOptions.token && initOptions.refreshToken) {
|
||||
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
|
||||
|
||||
if (loginIframe.enable) {
|
||||
@@ -832,11 +832,16 @@
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
var messageCallback = function(event) {
|
||||
if (event.origin !== loginIframe.iframeOrigin) {
|
||||
if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data != "unchanged") {
|
||||
if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (event.data != 'unchanged') {
|
||||
kc.clearToken();
|
||||
}
|
||||
|
||||
@@ -844,7 +849,7 @@
|
||||
|
||||
for (var i = callbacks.length - 1; i >= 0; --i) {
|
||||
var promise = callbacks[i];
|
||||
if (event.data == "unchanged") {
|
||||
if (event.data == 'unchanged') {
|
||||
promise.setSuccess();
|
||||
} else {
|
||||
promise.setError();
|
||||
@@ -1223,7 +1228,7 @@
|
||||
break;
|
||||
default:
|
||||
if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
|
||||
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
|
||||
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + encodeURIComponent(queryParams[param]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<script>
|
||||
function getCookie(cname)
|
||||
{
|
||||
var name = cname + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for(var i=0; i<ca.length; i++)
|
||||
{
|
||||
var c = ca[i].trim();
|
||||
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function receiveMessage(event)
|
||||
{
|
||||
if (event.origin !== "ORIGIN") {
|
||||
console.log(event.origin + " does not match built origin");
|
||||
return;
|
||||
|
||||
}
|
||||
var data = JSON.parse(event.data);
|
||||
data.loggedIn = false;
|
||||
var cookie = getCookie('KEYCLOAK_SESSION');
|
||||
if (cookie) {
|
||||
data.loggedIn = true;
|
||||
data.session = cookie;
|
||||
}
|
||||
|
||||
event.source.postMessage(JSON.stringify(data),
|
||||
event.origin);
|
||||
}
|
||||
window.addEventListener("message", receiveMessage, false);
|
||||
</script>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak OIDC Client Adapter Modules</name>
|
||||
@@ -41,9 +41,11 @@
|
||||
<module>servlet-filter</module>
|
||||
<module>servlet-oauth-client</module>
|
||||
<module>spring-boot</module>
|
||||
<module>spring-boot-container-bundle</module>
|
||||
<module>spring-security</module>
|
||||
<module>tomcat</module>
|
||||
<module>undertow</module>
|
||||
<module>wildfly</module>
|
||||
<module>wildfly-elytron</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+63
-1
@@ -41,12 +41,60 @@ import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636#section-4
|
||||
private String codeVerifier;
|
||||
private String codeChallenge;
|
||||
private String codeChallengeMethod = OAuth2Constants.PKCE_METHOD_S256;
|
||||
private static Logger logger = Logger.getLogger(ServletOAuthClient.class);
|
||||
|
||||
public static String generateSecret() {
|
||||
return generateSecret(32);
|
||||
}
|
||||
|
||||
public static String generateSecret(int bytes) {
|
||||
byte[] buf = new byte[bytes];
|
||||
new SecureRandom().nextBytes(buf);
|
||||
return Base64Url.encode(buf);
|
||||
}
|
||||
|
||||
private void setCodeVerifier() {
|
||||
codeVerifier = generateSecret();
|
||||
logger.debugf("Generated codeVerifier = %s", codeVerifier);
|
||||
return;
|
||||
}
|
||||
|
||||
private void setCodeChallenge() {
|
||||
try {
|
||||
if (codeChallengeMethod.equals(OAuth2Constants.PKCE_METHOD_S256)) {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(codeVerifier.getBytes());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : md.digest()) {
|
||||
String hex = String.format("%02x", b);
|
||||
sb.append(hex);
|
||||
}
|
||||
codeChallenge = Base64Url.encode(sb.toString().getBytes());
|
||||
} else {
|
||||
codeChallenge = Base64Url.encode(codeVerifier.getBytes());
|
||||
}
|
||||
logger.debugf("Encode codeChallenge = %s, codeChallengeMethod = %s", codeChallenge, codeChallengeMethod);
|
||||
} catch (Exception e) {
|
||||
logger.info("PKCE client side unknown hash algorithm");
|
||||
codeChallenge = Base64Url.encode(codeVerifier.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* closes client
|
||||
*/
|
||||
@@ -57,7 +105,15 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
|
||||
private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure {
|
||||
// Don't send sessionId in oauth clients for now
|
||||
KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
|
||||
return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null);
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636#section-4
|
||||
if (codeVerifier != null) {
|
||||
logger.debugf("Before sending Token Request, codeVerifier = %s", codeVerifier);
|
||||
return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null, codeVerifier);
|
||||
} else {
|
||||
logger.debug("Before sending Token Request without codeVerifier");
|
||||
return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,6 +150,12 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
|
||||
String authUrl = resolvedDeployment.getAuthUrl().clone().build().toString();
|
||||
String scopeParam = TokenUtil.attachOIDCScope(scope);
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636#section-4
|
||||
if (resolvedDeployment.isPkce()) {
|
||||
setCodeVerifier();
|
||||
setCodeChallenge();
|
||||
}
|
||||
|
||||
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(authUrl)
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, getClientId())
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-boot-container-bundle</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-tomcat8-adapter</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-undertow-adapter</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jetty93-adapter</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.4.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>org.keycloak:keycloak-tomcat8-adapter</include>
|
||||
<include>org.keycloak:keycloak-undertow-adapter</include>
|
||||
<include>org.keycloak:keycloak-jetty93-adapter</include>
|
||||
<include>org.keycloak:keycloak-tomcat-core-adapter</include>
|
||||
<include>org.keycloak:keycloak-tomcat-adapter-spi</include>
|
||||
<include>org.keycloak:keycloak-undertow-adapter</include>
|
||||
<include>org.keycloak:keycloak-undertow-adapter-spi</include>
|
||||
<include>org.keycloak:keycloak-jetty-core</include>
|
||||
<include>org.keycloak:keycloak-jetty-adapter-spi</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
<createSourcesJar>true</createSourcesJar>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -43,75 +43,61 @@
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>spring-boot-container-bundle</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<optional>true</optional>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-security-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-tomcat8-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-undertow-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jetty92-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<scope>provided</scope>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.undertow</groupId>
|
||||
<artifactId>undertow-servlet</artifactId>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>provided</scope>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>provided</scope>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>provided</scope>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.undertow</groupId>
|
||||
<artifactId>undertow-servlet</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.undertow</groupId>
|
||||
<artifactId>undertow-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-undertow-adapter-spi</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
@@ -125,11 +111,10 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+65
-37
@@ -60,7 +60,7 @@ import java.util.Set;
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication
|
||||
@EnableConfigurationProperties(KeycloakSpringBootProperties.class)
|
||||
public class KeycloakSpringBootConfiguration {
|
||||
public class KeycloakAutoConfiguration {
|
||||
|
||||
private KeycloakSpringBootProperties keycloakProperties;
|
||||
|
||||
@@ -70,6 +70,7 @@ public class KeycloakSpringBootConfiguration {
|
||||
KeycloakSpringBootConfigResolver.setAdapterConfig(keycloakProperties);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public EmbeddedServletContainerCustomizer getKeycloakContainerCustomizer() {
|
||||
return new EmbeddedServletContainerCustomizer() {
|
||||
@@ -141,10 +142,10 @@ public class KeycloakSpringBootConfiguration {
|
||||
List<io.undertow.servlet.api.SecurityConstraint> undertowSecurityConstraints = new ArrayList<io.undertow.servlet.api.SecurityConstraint>();
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) {
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityCollection collectionDefinition : constraintDefinition.getSecurityCollections()) {
|
||||
io.undertow.servlet.api.SecurityConstraint undertowSecurityConstraint = new io.undertow.servlet.api.SecurityConstraint();
|
||||
undertowSecurityConstraint.addRolesAllowed(constraintDefinition.getAuthRoles());
|
||||
|
||||
io.undertow.servlet.api.SecurityConstraint undertowSecurityConstraint = new io.undertow.servlet.api.SecurityConstraint();
|
||||
undertowSecurityConstraint.addRolesAllowed(collectionDefinition.getAuthRoles());
|
||||
for (KeycloakSpringBootProperties.SecurityCollection collectionDefinition : constraintDefinition.getSecurityCollections()) {
|
||||
|
||||
WebResourceCollection webResourceCollection = new WebResourceCollection();
|
||||
webResourceCollection.addHttpMethods(collectionDefinition.getMethods());
|
||||
@@ -153,8 +154,9 @@ public class KeycloakSpringBootConfiguration {
|
||||
|
||||
undertowSecurityConstraint.addWebResourceCollections(webResourceCollection);
|
||||
|
||||
undertowSecurityConstraints.add(undertowSecurityConstraint);
|
||||
}
|
||||
|
||||
undertowSecurityConstraints.add(undertowSecurityConstraint);
|
||||
}
|
||||
return undertowSecurityConstraints;
|
||||
}
|
||||
@@ -174,42 +176,70 @@ public class KeycloakSpringBootConfiguration {
|
||||
KeycloakJettyAuthenticator keycloakJettyAuthenticator = new KeycloakJettyAuthenticator();
|
||||
keycloakJettyAuthenticator.setConfigResolver(new KeycloakSpringBootConfigResolver());
|
||||
|
||||
/* see org.eclipse.jetty.webapp.StandardDescriptorProcessor#visitSecurityConstraint for an example
|
||||
on how to map servlet spec to Constraints */
|
||||
|
||||
List<ConstraintMapping> jettyConstraintMappings = new ArrayList<ConstraintMapping>();
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) {
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityCollection securityCollectionDefinition : constraintDefinition
|
||||
.getSecurityCollections()) {
|
||||
|
||||
// securityCollection matches servlet spec's web-resource-collection
|
||||
Constraint jettyConstraint = new Constraint();
|
||||
|
||||
if (constraintDefinition.getAuthRoles().size() > 0) {
|
||||
jettyConstraint.setAuthenticate(true);
|
||||
jettyConstraint.setRoles(constraintDefinition.getAuthRoles().toArray(new String[0]));
|
||||
}
|
||||
|
||||
jettyConstraint.setName(securityCollectionDefinition.getName());
|
||||
jettyConstraint.setAuthenticate(true);
|
||||
|
||||
if (securityCollectionDefinition.getName() != null) {
|
||||
jettyConstraint.setName(securityCollectionDefinition.getName());
|
||||
// according to the servlet spec each security-constraint has at least one URL pattern
|
||||
for(String pattern : securityCollectionDefinition.getPatterns()) {
|
||||
|
||||
/* the following code is asymmetric as Jetty's ConstraintMapping accepts only one allowed HTTP method,
|
||||
but multiple omitted methods. Therefore we add one ConstraintMapping for each allowed
|
||||
mapping but only one mapping in the cases of omitted methods or no methods.
|
||||
*/
|
||||
|
||||
if (securityCollectionDefinition.getMethods().size() > 0) {
|
||||
// according to the servlet spec we have either methods ...
|
||||
for(String method : securityCollectionDefinition.getMethods()) {
|
||||
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
|
||||
jettyConstraintMappings.add(jettyConstraintMapping);
|
||||
|
||||
jettyConstraintMapping.setConstraint(jettyConstraint);
|
||||
jettyConstraintMapping.setPathSpec(pattern);
|
||||
jettyConstraintMapping.setMethod(method);
|
||||
}
|
||||
} else if (securityCollectionDefinition.getOmittedMethods().size() > 0){
|
||||
// ... omitted methods ...
|
||||
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
|
||||
jettyConstraintMappings.add(jettyConstraintMapping);
|
||||
|
||||
jettyConstraintMapping.setConstraint(jettyConstraint);
|
||||
jettyConstraintMapping.setPathSpec(pattern);
|
||||
jettyConstraintMapping.setMethodOmissions(
|
||||
securityCollectionDefinition.getOmittedMethods().toArray(new String[0]));
|
||||
} else {
|
||||
// ... or no methods at all
|
||||
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
|
||||
jettyConstraintMappings.add(jettyConstraintMapping);
|
||||
|
||||
jettyConstraintMapping.setConstraint(jettyConstraint);
|
||||
jettyConstraintMapping.setPathSpec(pattern);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
jettyConstraint.setRoles(securityCollectionDefinition.getAuthRoles().toArray(new String[0]));
|
||||
|
||||
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
|
||||
if (securityCollectionDefinition.getPatterns().size() > 0) {
|
||||
//First pattern wins
|
||||
jettyConstraintMapping.setPathSpec(securityCollectionDefinition.getPatterns().get(0));
|
||||
jettyConstraintMapping.setConstraint(jettyConstraint);
|
||||
}
|
||||
|
||||
if (securityCollectionDefinition.getMethods().size() > 0) {
|
||||
//First method wins
|
||||
jettyConstraintMapping.setMethod(securityCollectionDefinition.getMethods().get(0));
|
||||
}
|
||||
|
||||
jettyConstraintMapping.setMethodOmissions(
|
||||
securityCollectionDefinition.getOmittedMethods().toArray(new String[0]));
|
||||
|
||||
jettyConstraintMappings.add(jettyConstraintMapping);
|
||||
}
|
||||
}
|
||||
|
||||
WebAppContext webAppContext = server.getBean(WebAppContext.class);
|
||||
//if not found as registered bean let's try the handler
|
||||
if(webAppContext==null){
|
||||
webAppContext = (WebAppContext) server.getHandler();
|
||||
}
|
||||
|
||||
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
|
||||
securityHandler.setConstraintMappings(jettyConstraintMappings);
|
||||
@@ -235,12 +265,10 @@ public class KeycloakSpringBootConfiguration {
|
||||
|
||||
Set<String> authRoles = new HashSet<String>();
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
||||
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
|
||||
for (String authRole : collection.getAuthRoles()) {
|
||||
if (!authRoles.contains(authRole)) {
|
||||
context.addSecurityRole(authRole);
|
||||
authRoles.add(authRole);
|
||||
}
|
||||
for (String authRole : constraint.getAuthRoles()) {
|
||||
if (!authRoles.contains(authRole)) {
|
||||
context.addSecurityRole(authRole);
|
||||
authRoles.add(authRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,6 +276,10 @@ public class KeycloakSpringBootConfiguration {
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
||||
SecurityConstraint tomcatConstraint = new SecurityConstraint();
|
||||
|
||||
for (String authRole : constraint.getAuthRoles()) {
|
||||
tomcatConstraint.addAuthRole(authRole);
|
||||
}
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
|
||||
SecurityCollection tomcatSecCollection = new SecurityCollection();
|
||||
|
||||
@@ -258,10 +290,6 @@ public class KeycloakSpringBootConfiguration {
|
||||
tomcatSecCollection.setDescription(collection.getDescription());
|
||||
}
|
||||
|
||||
for (String authRole : collection.getAuthRoles()) {
|
||||
tomcatConstraint.addAuthRole(authRole);
|
||||
}
|
||||
|
||||
for (String pattern : collection.getPatterns()) {
|
||||
tomcatSecCollection.addPattern(pattern);
|
||||
}
|
||||
+17
-12
@@ -43,12 +43,20 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
|
||||
*/
|
||||
private List<SecurityConstraint> securityConstraints = new ArrayList<SecurityConstraint>();
|
||||
|
||||
/**
|
||||
* This matches security-constraint of the servlet spec
|
||||
*/
|
||||
@ConfigurationProperties()
|
||||
public static class SecurityConstraint {
|
||||
/**
|
||||
* A list of security collections
|
||||
*/
|
||||
private List<SecurityCollection> securityCollections = new ArrayList<SecurityCollection>();
|
||||
private List<String> authRoles = new ArrayList<String>();
|
||||
|
||||
public List<String> getAuthRoles() {
|
||||
return authRoles;
|
||||
}
|
||||
|
||||
public List<SecurityCollection> getSecurityCollections() {
|
||||
return securityCollections;
|
||||
@@ -57,7 +65,16 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
|
||||
public void setSecurityCollections(List<SecurityCollection> securityCollections) {
|
||||
this.securityCollections = securityCollections;
|
||||
}
|
||||
|
||||
public void setAuthRoles(List<String> authRoles) {
|
||||
this.authRoles = authRoles;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This matches web-resource-collection of the servlet spec
|
||||
*/
|
||||
@ConfigurationProperties()
|
||||
public static class SecurityCollection {
|
||||
/**
|
||||
@@ -68,10 +85,6 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
|
||||
* The description of your security collection
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* A list of roles that applies for this security collection
|
||||
*/
|
||||
private List<String> authRoles = new ArrayList<String>();
|
||||
/**
|
||||
* A list of URL patterns that should match to apply the security collection
|
||||
*/
|
||||
@@ -85,10 +98,6 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
|
||||
*/
|
||||
private List<String> omittedMethods = new ArrayList<String>();
|
||||
|
||||
public List<String> getAuthRoles() {
|
||||
return authRoles;
|
||||
}
|
||||
|
||||
public List<String> getPatterns() {
|
||||
return patterns;
|
||||
}
|
||||
@@ -117,10 +126,6 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void setAuthRoles(List<String> authRoles) {
|
||||
this.authRoles = authRoles;
|
||||
}
|
||||
|
||||
public void setPatterns(List<String> patterns) {
|
||||
this.patterns = patterns;
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
|
||||
org.keycloak.adapters.springboot.KeycloakAutoConfiguration
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -59,12 +59,14 @@
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
<version>${spring-security.version}</version>
|
||||
<optional>true</optional>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-web</artifactId>
|
||||
<version>${spring-security.version}</version>
|
||||
<optional>true</optional>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak Tomcat Integration</name>
|
||||
@@ -32,8 +32,21 @@
|
||||
|
||||
<modules>
|
||||
<module>tomcat-core</module>
|
||||
<module>tomcat6</module>
|
||||
<module>tomcat7</module>
|
||||
<module>tomcat8</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>community</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!product</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>tomcat6</module>
|
||||
<module>tomcat7</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -29,8 +29,6 @@
|
||||
<artifactId>keycloak-tomcat-core-adapter</artifactId>
|
||||
<name>Keycloak Tomcat Core Integration</name>
|
||||
<properties>
|
||||
<!-- <tomcat.version>8.0.14</tomcat.version> -->
|
||||
<!-- <tomcat.version>7.0.52</tomcat.version> -->
|
||||
<tomcat.version>6.0.41</tomcat.version>
|
||||
</properties>
|
||||
<description />
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ JBoss, Home of Professional Open Source.
|
||||
~ Copyright 2016 Red Hat, Inc., and individual 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-wildfly-elytron-oidc-adapter</artifactId>
|
||||
<name>Keycloak Wildfly Elytron OIDC Adapter</name>
|
||||
<description/>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.wildfly.common</groupId>
|
||||
<artifactId>wildfly-common</artifactId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wildfly.security</groupId>
|
||||
<artifactId>wildfly-elytron</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wildfly.security.elytron-web</groupId>
|
||||
<artifactId>undertow-server</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-spi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.wildfly.security.auth.server.SecurityIdentity;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import java.security.Principal;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ElytronAccount implements OidcKeycloakAccount {
|
||||
|
||||
protected static Logger log = Logger.getLogger(ElytronAccount.class);
|
||||
|
||||
private final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal;
|
||||
|
||||
public ElytronAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
|
||||
this.principal = principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
|
||||
return principal.getKeycloakSecurityContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRoles() {
|
||||
Set<String> roles = new HashSet<>();
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
|
||||
principal.getKeycloakSecurityContext().setCurrentRequestInfo(deployment, tokenStore);
|
||||
}
|
||||
|
||||
public boolean checkActive() {
|
||||
RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
|
||||
|
||||
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
|
||||
log.debug("session is active");
|
||||
return true;
|
||||
}
|
||||
|
||||
log.debug("session not active");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean tryRefresh(CallbackHandler callbackHandler) {
|
||||
log.debug("Trying to refresh");
|
||||
|
||||
RefreshableKeycloakSecurityContext securityContext = getKeycloakSecurityContext();
|
||||
|
||||
if (securityContext == null) {
|
||||
log.debug("No security context. Aborting refresh.");
|
||||
}
|
||||
|
||||
if (securityContext.refreshExpiredToken(false)) {
|
||||
SecurityIdentity securityIdentity = SecurityIdentityUtil.authorize(callbackHandler, principal);
|
||||
|
||||
if (securityIdentity != null) {
|
||||
log.debug("refresh succeeded");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return checkActive();
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.CookieTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.wildfly.security.http.HttpScope;
|
||||
import org.wildfly.security.http.Scope;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ElytronCookieTokenStore implements ElytronTokeStore {
|
||||
|
||||
protected static Logger log = Logger.getLogger(ElytronCookieTokenStore.class);
|
||||
|
||||
private final ElytronHttpFacade httpFacade;
|
||||
private final CallbackHandler callbackHandler;
|
||||
|
||||
public ElytronCookieTokenStore(ElytronHttpFacade httpFacade, CallbackHandler callbackHandler) {
|
||||
this.httpFacade = httpFacade;
|
||||
this.callbackHandler = callbackHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCurrentToken() {
|
||||
KeycloakDeployment deployment = httpFacade.getDeployment();
|
||||
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, httpFacade, this);
|
||||
|
||||
if (principal == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
|
||||
|
||||
if (securityContext.isActive() && !securityContext.getDeployment().isAlwaysRefreshToken()) return;
|
||||
|
||||
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
|
||||
// not be updated
|
||||
boolean success = securityContext.refreshExpiredToken(false);
|
||||
if (success && securityContext.isActive()) return;
|
||||
|
||||
saveAccountInfo(new ElytronAccount(principal));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCached(RequestAuthenticator authenticator) {
|
||||
KeycloakDeployment deployment = httpFacade.getDeployment();
|
||||
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, httpFacade, this);
|
||||
if (principal == null) {
|
||||
log.debug("Account was not in cookie or was invalid, returning null");
|
||||
return false;
|
||||
}
|
||||
ElytronAccount account = new ElytronAccount(principal);
|
||||
|
||||
if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
|
||||
log.debug("Account in session belongs to a different realm than for this request.");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean active = account.checkActive();
|
||||
|
||||
if (!active) {
|
||||
active = account.tryRefresh(this.callbackHandler);
|
||||
}
|
||||
|
||||
if (active) {
|
||||
log.debug("Cached account found");
|
||||
restoreRequest();
|
||||
httpFacade.authenticationComplete(account, true);
|
||||
return true;
|
||||
} else {
|
||||
log.debug("Account was not active, removing cookie and returning false");
|
||||
CookieTokenStore.removeCookie(httpFacade);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAccountInfo(OidcKeycloakAccount account) {
|
||||
RefreshableKeycloakSecurityContext secContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
|
||||
CookieTokenStore.setTokenCookie(this.httpFacade.getDeployment(), this.httpFacade, secContext);
|
||||
HttpScope exchange = this.httpFacade.getScope(Scope.EXCHANGE);
|
||||
|
||||
exchange.registerForNotification(httpServerScopes -> logout());
|
||||
|
||||
exchange.setAttachment(ElytronAccount.class.getName(), account);
|
||||
exchange.setAttachment(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
|
||||
|
||||
restoreRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout() {
|
||||
logout(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
|
||||
CookieTokenStore.setTokenCookie(this.httpFacade.getDeployment(), httpFacade, securityContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveRequest() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean restoreRequest() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(boolean glo) {
|
||||
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(this.httpFacade.getDeployment(), this.httpFacade, this);
|
||||
|
||||
if (principal == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
CookieTokenStore.removeCookie(this.httpFacade);
|
||||
|
||||
if (glo) {
|
||||
KeycloakSecurityContext ksc = (KeycloakSecurityContext) principal.getKeycloakSecurityContext();
|
||||
|
||||
if (ksc == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeycloakDeployment deployment = httpFacade.getDeployment();
|
||||
|
||||
if (!deployment.isBearerOnly() && ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
|
||||
((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+394
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
|
||||
|
||||
import org.bouncycastle.asn1.cmp.Challenge;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthenticationError;
|
||||
import org.keycloak.adapters.spi.LogoutError;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.wildfly.security.auth.server.SecurityIdentity;
|
||||
import org.wildfly.security.http.HttpAuthenticationException;
|
||||
import org.wildfly.security.http.HttpScope;
|
||||
import org.wildfly.security.http.HttpServerCookie;
|
||||
import org.wildfly.security.http.HttpServerMechanismsResponder;
|
||||
import org.wildfly.security.http.HttpServerRequest;
|
||||
import org.wildfly.security.http.HttpServerResponse;
|
||||
import org.wildfly.security.http.Scope;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.cert.X509Certificate;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
class ElytronHttpFacade implements OIDCHttpFacade {
|
||||
|
||||
private final HttpServerRequest request;
|
||||
private final CallbackHandler callbackHandler;
|
||||
private final AdapterTokenStore tokenStore;
|
||||
private final AdapterDeploymentContext deploymentContext;
|
||||
private Consumer<HttpServerResponse> responseConsumer;
|
||||
private ElytronAccount account;
|
||||
private SecurityIdentity securityIdentity;
|
||||
private boolean restored;
|
||||
|
||||
public ElytronHttpFacade(HttpServerRequest request, AdapterDeploymentContext deploymentContext, CallbackHandler handler) {
|
||||
this.request = request;
|
||||
this.deploymentContext = deploymentContext;
|
||||
this.callbackHandler = handler;
|
||||
this.tokenStore = createTokenStore();
|
||||
this.responseConsumer = response -> {};
|
||||
}
|
||||
|
||||
void authenticationComplete(ElytronAccount account, boolean storeToken) {
|
||||
this.securityIdentity = SecurityIdentityUtil.authorize(this.callbackHandler, account.getPrincipal());
|
||||
|
||||
if (securityIdentity != null) {
|
||||
this.account = account;
|
||||
RefreshableKeycloakSecurityContext keycloakSecurityContext = account.getKeycloakSecurityContext();
|
||||
account.setCurrentRequestInfo(keycloakSecurityContext.getDeployment(), this.tokenStore);
|
||||
if (storeToken) {
|
||||
this.tokenStore.saveAccountInfo(account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void authenticationComplete() {
|
||||
if (securityIdentity != null) {
|
||||
this.request.authenticationComplete(response -> {
|
||||
if (!restored) {
|
||||
responseConsumer.accept(response);
|
||||
}
|
||||
}, () -> ((ElytronTokeStore) tokenStore).logout(true));
|
||||
}
|
||||
}
|
||||
|
||||
void authenticationFailed() {
|
||||
this.request.authenticationFailed("Authentication Failed", response -> responseConsumer.accept(response));
|
||||
}
|
||||
|
||||
void noAuthenticationInProgress() {
|
||||
this.request.noAuthenticationInProgress();
|
||||
}
|
||||
|
||||
void noAuthenticationInProgress(AuthChallenge challenge) {
|
||||
if (challenge != null) {
|
||||
challenge.challenge(this);
|
||||
}
|
||||
this.request.noAuthenticationInProgress(response -> responseConsumer.accept(response));
|
||||
}
|
||||
|
||||
void authenticationInProgress() {
|
||||
this.request.authenticationInProgress(response -> responseConsumer.accept(response));
|
||||
}
|
||||
|
||||
HttpScope getScope(Scope scope) {
|
||||
return request.getScope(scope);
|
||||
}
|
||||
|
||||
HttpScope getScope(Scope scope, String id) {
|
||||
return request.getScope(scope, id);
|
||||
}
|
||||
|
||||
Collection<String> getScopeIds(Scope scope) {
|
||||
return request.getScopeIds(scope);
|
||||
}
|
||||
|
||||
AdapterTokenStore getTokenStore() {
|
||||
return this.tokenStore;
|
||||
}
|
||||
|
||||
KeycloakDeployment getDeployment() {
|
||||
return deploymentContext.resolveDeployment(this);
|
||||
}
|
||||
|
||||
private AdapterTokenStore createTokenStore() {
|
||||
KeycloakDeployment deployment = getDeployment();
|
||||
|
||||
if (TokenStore.SESSION.equals(deployment.getTokenStore())) {
|
||||
return new ElytronSessionTokenStore(this, this.callbackHandler);
|
||||
} else {
|
||||
return new ElytronCookieTokenStore(this, this.callbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getRequest() {
|
||||
return new Request() {
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return request.getRequestMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURI() {
|
||||
try {
|
||||
return URLDecoder.decode(request.getRequestURI().toString(), "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("Failed to decode request URI", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRelativePath() {
|
||||
return request.getRequestPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return request.getRequestURI().getScheme().equals("https");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstParam(String param) {
|
||||
throw new RuntimeException("Not implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryParamValue(String param) {
|
||||
URI requestURI = request.getRequestURI();
|
||||
String query = requestURI.getQuery();
|
||||
if (query != null) {
|
||||
String[] parameters = query.split("&");
|
||||
for (String parameter : parameters) {
|
||||
String[] keyValue = parameter.split("=");
|
||||
if (keyValue[0].equals(param)) {
|
||||
return keyValue[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cookie getCookie(final String cookieName) {
|
||||
List<HttpServerCookie> cookies = request.getCookies();
|
||||
|
||||
if (cookies != null) {
|
||||
for (HttpServerCookie cookie : cookies) {
|
||||
if (cookie.getName().equals(cookieName)) {
|
||||
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
return request.getFirstRequestHeaderValue(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name) {
|
||||
return request.getRequestHeaderValues(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return request.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
InetSocketAddress sourceAddress = request.getSourceAddress();
|
||||
if (sourceAddress == null) {
|
||||
return "";
|
||||
}
|
||||
InetAddress address = sourceAddress.getAddress();
|
||||
if (address == null) {
|
||||
// this is unresolved, so we just return the host name not exactly spec, but if the name should be
|
||||
// resolved then a PeerNameResolvingHandler should be used and this is probably better than just
|
||||
// returning null
|
||||
return sourceAddress.getHostString();
|
||||
}
|
||||
return address.getHostAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setError(AuthenticationError error) {
|
||||
request.getScope(Scope.EXCHANGE).setAttachment(AuthenticationError.class.getName(), error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setError(LogoutError error) {
|
||||
request.getScope(Scope.EXCHANGE).setAttachment(LogoutError.class.getName(), error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response getResponse() {
|
||||
return new Response() {
|
||||
@Override
|
||||
public void setStatus(final int status) {
|
||||
responseConsumer = responseConsumer.andThen(response -> response.setStatusCode(status));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(final String name, final String value) {
|
||||
responseConsumer = responseConsumer.andThen(response -> response.addResponseHeader(name, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value) {
|
||||
addHeader(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetCookie(final String name, final String path) {
|
||||
responseConsumer = responseConsumer.andThen(response -> setCookie(name, "", path, null, 0, false, false, response));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, final boolean secure, final boolean httpOnly) {
|
||||
responseConsumer = responseConsumer.andThen(response -> setCookie(name, value, path, domain, maxAge, secure, httpOnly, response));
|
||||
}
|
||||
|
||||
private void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, final boolean secure, final boolean httpOnly, HttpServerResponse response) {
|
||||
response.setResponseCookie(new HttpServerCookie() {
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxAge() {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return secure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHttpOnly() {
|
||||
return httpOnly;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
responseConsumer = responseConsumer.andThen(new Consumer<HttpServerResponse>() {
|
||||
@Override
|
||||
public void accept(HttpServerResponse httpServerResponse) {
|
||||
try {
|
||||
httpServerResponse.getOutputStream().write(stream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to write to response output stream", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int code) {
|
||||
setStatus(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(final int code, final String message) {
|
||||
responseConsumer = responseConsumer.andThen(response -> {
|
||||
response.setStatusCode(code);
|
||||
response.addResponseHeader("Content-Type", "text/html");
|
||||
try {
|
||||
response.getOutputStream().write(message.getBytes());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSecurityContext getSecurityContext() {
|
||||
if (account == null) {
|
||||
return null;
|
||||
}
|
||||
return this.account.getKeycloakSecurityContext();
|
||||
}
|
||||
|
||||
public boolean restoreRequest() {
|
||||
restored = this.request.resumeRequest();
|
||||
return restored;
|
||||
}
|
||||
|
||||
public void suspendRequest() {
|
||||
responseConsumer = responseConsumer.andThen(httpServerResponse -> request.suspendRequest());
|
||||
}
|
||||
|
||||
public boolean isAuthorized() {
|
||||
return this.securityIdentity != null;
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
|
||||
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.wildfly.security.http.HttpScope;
|
||||
import org.wildfly.security.http.Scope;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ElytronRequestAuthenticator extends RequestAuthenticator {
|
||||
|
||||
public ElytronRequestAuthenticator(CallbackHandler callbackHandler, ElytronHttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort) {
|
||||
super(facade, deployment, facade.getTokenStore(), sslRedirectPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthOutcome authenticate() {
|
||||
AuthOutcome authenticate = super.authenticate();
|
||||
|
||||
if (AuthOutcome.AUTHENTICATED.equals(authenticate)) {
|
||||
if (!getElytronHttpFacade().isAuthorized()) {
|
||||
return AuthOutcome.FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
return authenticate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
|
||||
return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
|
||||
getElytronHttpFacade().authenticationComplete(new ElytronAccount(principal), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
|
||||
getElytronHttpFacade().authenticationComplete(new ElytronAccount(principal), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String changeHttpSessionId(boolean create) {
|
||||
HttpScope session = getElytronHttpFacade().getScope(Scope.SESSION);
|
||||
|
||||
if (create) {
|
||||
if (!session.exists()) {
|
||||
session.create();
|
||||
}
|
||||
}
|
||||
|
||||
return session != null ? session.getID() : null;
|
||||
}
|
||||
|
||||
private ElytronHttpFacade getElytronHttpFacade() {
|
||||
return (ElytronHttpFacade) facade;
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.wildfly.security.http.HttpScope;
|
||||
import org.wildfly.security.http.HttpScopeNotification;
|
||||
import org.wildfly.security.http.Scope;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ElytronSessionTokenStore implements ElytronTokeStore {
|
||||
|
||||
private static Logger log = Logger.getLogger(ElytronSessionTokenStore.class);
|
||||
|
||||
private final ElytronHttpFacade httpFacade;
|
||||
private final CallbackHandler callbackHandler;
|
||||
|
||||
public ElytronSessionTokenStore(ElytronHttpFacade httpFacade, CallbackHandler callbackHandler) {
|
||||
this.httpFacade = httpFacade;
|
||||
this.callbackHandler = callbackHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCurrentToken() {
|
||||
HttpScope session = httpFacade.getScope(Scope.SESSION);
|
||||
if (!session.exists()) return;
|
||||
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) session.getAttachment(KeycloakSecurityContext.class.getName());
|
||||
if (securityContext == null) return;
|
||||
|
||||
// just in case session got serialized
|
||||
if (securityContext.getDeployment() == null) securityContext.setCurrentRequestInfo(httpFacade.getDeployment(), this);
|
||||
|
||||
if (securityContext.isActive() && !securityContext.getDeployment().isAlwaysRefreshToken()) return;
|
||||
|
||||
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
|
||||
// not be updated
|
||||
boolean success = securityContext.refreshExpiredToken(false);
|
||||
if (success && securityContext.isActive()) return;
|
||||
|
||||
// Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
|
||||
session.setAttachment(KeycloakSecurityContext.class.getName(), null);
|
||||
session.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCached(RequestAuthenticator authenticator) {
|
||||
HttpScope session = this.httpFacade.getScope(Scope.SESSION);
|
||||
|
||||
if (session == null) {
|
||||
log.debug("session was null, returning null");
|
||||
return false;
|
||||
}
|
||||
|
||||
ElytronAccount account;
|
||||
|
||||
try {
|
||||
account = (ElytronAccount) session.getAttachment(ElytronAccount.class.getName());
|
||||
} catch (IllegalStateException e) {
|
||||
log.debug("session was invalidated. Return false.");
|
||||
return false;
|
||||
}
|
||||
if (account == null) {
|
||||
log.debug("Account was not in session, returning null");
|
||||
return false;
|
||||
}
|
||||
|
||||
KeycloakDeployment deployment = httpFacade.getDeployment();
|
||||
|
||||
if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
|
||||
log.debug("Account in session belongs to a different realm than for this request.");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean active = account.checkActive();
|
||||
|
||||
if (!active) {
|
||||
active = account.tryRefresh(this.callbackHandler);
|
||||
}
|
||||
|
||||
if (active) {
|
||||
log.debug("Cached account found");
|
||||
restoreRequest();
|
||||
httpFacade.authenticationComplete(account, true);
|
||||
return true;
|
||||
} else {
|
||||
log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
|
||||
try {
|
||||
session.setAttachment(KeycloakSecurityContext.class.getName(), null);
|
||||
session.setAttachment(ElytronAccount.class.getName(), null);
|
||||
session.invalidate();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to invalidate session, might already be invalidated");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAccountInfo(OidcKeycloakAccount account) {
|
||||
HttpScope session = this.httpFacade.getScope(Scope.SESSION);
|
||||
|
||||
if (!session.exists()) {
|
||||
session.create();
|
||||
}
|
||||
|
||||
session.setAttachment(ElytronAccount.class.getName(), account);
|
||||
session.setAttachment(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
|
||||
|
||||
session.registerForNotification(httpScopeNotification -> {
|
||||
if (!httpScopeNotification.isOfType(HttpScopeNotification.SessionNotificationType.UNDEPLOY)) {
|
||||
logout();
|
||||
}
|
||||
});
|
||||
|
||||
HttpScope scope = this.httpFacade.getScope(Scope.EXCHANGE);
|
||||
|
||||
scope.setAttachment(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout() {
|
||||
logout(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
|
||||
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(this.httpFacade.getDeployment(), securityContext.getToken()), securityContext);
|
||||
saveAccountInfo(new ElytronAccount(principal));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveRequest() {
|
||||
this.httpFacade.suspendRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean restoreRequest() {
|
||||
return this.httpFacade.restoreRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(boolean glo) {
|
||||
HttpScope session = this.httpFacade.getScope(Scope.SESSION);
|
||||
|
||||
if (!session.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (glo) {
|
||||
KeycloakSecurityContext ksc = (KeycloakSecurityContext) session.getAttachment(KeycloakSecurityContext.class.getName());
|
||||
|
||||
if (ksc == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeycloakDeployment deployment = httpFacade.getDeployment();
|
||||
|
||||
if (!deployment.isBearerOnly() && ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
|
||||
((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
|
||||
}
|
||||
}
|
||||
|
||||
session.setAttachment(KeycloakSecurityContext.class.getName(), null);
|
||||
session.setAttachment(ElytronAccount.class.getName(), null);
|
||||
session.invalidate();
|
||||
} catch (IllegalStateException ise) {
|
||||
// Session may be already logged-out in case that app has adminUrl
|
||||
log.debugf("Session %s logged-out already", session.getID());
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.elytron;
|
||||
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ElytronTokeStore extends AdapterTokenStore {
|
||||
void logout(boolean glo);
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
|
||||
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* <p>A {@link ServletContextListener} that parses the keycloak adapter configuration and set the same configuration
|
||||
* as a {@link ServletContext} attribute in order to provide to {@link KeycloakHttpServerAuthenticationMechanism} a way
|
||||
* to obtain the configuration when processing requests.
|
||||
*
|
||||
* <p>This listener should be automatically registered to a deployment using the subsystem.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class KeycloakConfigurationServletListener implements ServletContextListener {
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
ServletContext servletContext = sce.getServletContext();
|
||||
String configResolverClass = servletContext.getInitParameter("keycloak.config.resolver");
|
||||
KeycloakConfigResolver configResolver;
|
||||
AdapterDeploymentContext deploymentContext;
|
||||
|
||||
if (configResolverClass != null) {
|
||||
try {
|
||||
configResolver = (KeycloakConfigResolver) servletContext.getClassLoader().loadClass(configResolverClass).newInstance();
|
||||
deploymentContext = new AdapterDeploymentContext(configResolver);
|
||||
} catch (Exception ex) {
|
||||
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
|
||||
}
|
||||
} else {
|
||||
InputStream is = getConfigInputStream(servletContext);
|
||||
|
||||
KeycloakDeployment deployment;
|
||||
|
||||
if (is == null) {
|
||||
deployment = new KeycloakDeployment();
|
||||
} else {
|
||||
deployment = KeycloakDeploymentBuilder.build(is);
|
||||
}
|
||||
|
||||
deploymentContext = new AdapterDeploymentContext(deployment);
|
||||
}
|
||||
|
||||
servletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
|
||||
}
|
||||
|
||||
private InputStream getConfigInputStream(ServletContext servletContext) {
|
||||
InputStream is = getJSONFromServletContext(servletContext);
|
||||
|
||||
if (is == null) {
|
||||
String path = servletContext.getInitParameter("keycloak.config.file");
|
||||
|
||||
if (path == null) {
|
||||
is = servletContext.getResourceAsStream("/WEB-INF/keycloak.json");
|
||||
} else {
|
||||
try {
|
||||
is = new FileInputStream(path);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
private InputStream getJSONFromServletContext(ServletContext servletContext) {
|
||||
String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
|
||||
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ByteArrayInputStream(json.getBytes());
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
||||
import org.wildfly.security.http.HttpAuthenticationException;
|
||||
import org.wildfly.security.http.HttpScope;
|
||||
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
|
||||
import org.wildfly.security.http.HttpServerRequest;
|
||||
import org.wildfly.security.http.Scope;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticationMechanism {
|
||||
|
||||
static Logger LOGGER = Logger.getLogger(KeycloakHttpServerAuthenticationMechanismFactory.class);
|
||||
static final String NAME = "KEYCLOAK";
|
||||
|
||||
private final Map<String, ?> properties;
|
||||
private final CallbackHandler callbackHandler;
|
||||
private final AdapterDeploymentContext deploymentContext;
|
||||
|
||||
public KeycloakHttpServerAuthenticationMechanism(Map<String, ?> properties, CallbackHandler callbackHandler, AdapterDeploymentContext deploymentContext) {
|
||||
this.properties = properties;
|
||||
this.callbackHandler = callbackHandler;
|
||||
this.deploymentContext = deploymentContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMechanismName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException {
|
||||
LOGGER.debugf("Evaluating request for path [%s]", request.getRequestURI());
|
||||
AdapterDeploymentContext deploymentContext = getDeploymentContext(request);
|
||||
|
||||
if (deploymentContext == null) {
|
||||
LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI());
|
||||
request.noAuthenticationInProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
ElytronHttpFacade httpFacade = new ElytronHttpFacade(request, deploymentContext, callbackHandler);
|
||||
KeycloakDeployment deployment = httpFacade.getDeployment();
|
||||
|
||||
if (!deployment.isConfigured()) {
|
||||
request.noAuthenticationInProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
RequestAuthenticator authenticator = createRequestAuthenticator(request, httpFacade, deployment);
|
||||
|
||||
httpFacade.getTokenStore().checkCurrentToken();
|
||||
|
||||
if (preActions(httpFacade, deploymentContext)) {
|
||||
LOGGER.debugf("Pre-actions has aborted the evaluation of [%s]", request.getRequestURI());
|
||||
httpFacade.authenticationInProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
AuthOutcome outcome = authenticator.authenticate();
|
||||
|
||||
if (AuthOutcome.AUTHENTICATED.equals(outcome)) {
|
||||
if (new AuthenticatedActionsHandler(deployment, httpFacade).handledRequest()) {
|
||||
httpFacade.authenticationInProgress();
|
||||
} else {
|
||||
httpFacade.authenticationComplete();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AuthChallenge challenge = authenticator.getChallenge();
|
||||
|
||||
if (challenge != null) {
|
||||
httpFacade.noAuthenticationInProgress(challenge);
|
||||
return;
|
||||
}
|
||||
|
||||
if (AuthOutcome.FAILED.equals(outcome)) {
|
||||
httpFacade.getResponse().setStatus(403);
|
||||
httpFacade.authenticationFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
httpFacade.noAuthenticationInProgress();
|
||||
}
|
||||
|
||||
private ElytronRequestAuthenticator createRequestAuthenticator(HttpServerRequest request, ElytronHttpFacade httpFacade, KeycloakDeployment deployment) {
|
||||
return new ElytronRequestAuthenticator(this.callbackHandler, httpFacade, deployment, getConfidentialPort(request));
|
||||
}
|
||||
|
||||
private AdapterDeploymentContext getDeploymentContext(HttpServerRequest request) {
|
||||
if (this.deploymentContext == null) {
|
||||
return (AdapterDeploymentContext) request.getScope(Scope.APPLICATION).getAttachment(AdapterDeploymentContext.class.getName());
|
||||
}
|
||||
|
||||
return this.deploymentContext;
|
||||
}
|
||||
|
||||
private boolean preActions(ElytronHttpFacade httpFacade, AdapterDeploymentContext deploymentContext) {
|
||||
NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
|
||||
|
||||
nodesRegistrationManagement.tryRegister(httpFacade.getDeployment());
|
||||
|
||||
PreAuthActionsHandler preActions = new PreAuthActionsHandler(new UserSessionManagement() {
|
||||
@Override
|
||||
public void logoutAll() {
|
||||
Collection<String> sessions = httpFacade.getScopeIds(Scope.SESSION);
|
||||
logoutHttpSessions(new ArrayList<>(sessions));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logoutHttpSessions(List<String> ids) {
|
||||
for (String id : ids) {
|
||||
HttpScope session = httpFacade.getScope(Scope.SESSION, id);
|
||||
|
||||
if (session != null) {
|
||||
session.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}, deploymentContext, httpFacade);
|
||||
|
||||
return preActions.handleRequest();
|
||||
}
|
||||
|
||||
// TODO: obtain confidential port from Elytron
|
||||
private int getConfidentialPort(HttpServerRequest request) {
|
||||
return 8443;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
|
||||
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.wildfly.security.http.HttpAuthenticationException;
|
||||
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
|
||||
import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
|
||||
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class KeycloakHttpServerAuthenticationMechanismFactory implements HttpServerAuthenticationMechanismFactory {
|
||||
|
||||
private final AdapterDeploymentContext deploymentContext;
|
||||
|
||||
/**
|
||||
* <p>Creates a new instance.
|
||||
*
|
||||
* <p>A default constructor is necessary in order to allow this factory to be loaded via {@link java.util.ServiceLoader}.
|
||||
*/
|
||||
public KeycloakHttpServerAuthenticationMechanismFactory() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public KeycloakHttpServerAuthenticationMechanismFactory(AdapterDeploymentContext deploymentContext) {
|
||||
this.deploymentContext = deploymentContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getMechanismNames(Map<String, ?> properties) {
|
||||
return new String[] {KeycloakHttpServerAuthenticationMechanism.NAME};
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpServerAuthenticationMechanism createAuthenticationMechanism(String mechanismName, Map<String, ?> properties, CallbackHandler callbackHandler) throws HttpAuthenticationException {
|
||||
Map<String, Object> mechanismProperties = new HashMap();
|
||||
|
||||
mechanismProperties.putAll(properties);
|
||||
|
||||
if (KeycloakHttpServerAuthenticationMechanism.NAME.equals(mechanismName)) {
|
||||
return new KeycloakHttpServerAuthenticationMechanism(properties, callbackHandler, this.deploymentContext);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.elytron;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.wildfly.security.auth.SupportLevel;
|
||||
import org.wildfly.security.auth.server.RealmIdentity;
|
||||
import org.wildfly.security.auth.server.RealmUnavailableException;
|
||||
import org.wildfly.security.auth.server.SecurityRealm;
|
||||
import org.wildfly.security.authz.Attributes;
|
||||
import org.wildfly.security.authz.AuthorizationIdentity;
|
||||
import org.wildfly.security.authz.MapAttributes;
|
||||
import org.wildfly.security.authz.RoleDecoder;
|
||||
import org.wildfly.security.credential.Credential;
|
||||
import org.wildfly.security.evidence.Evidence;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class KeycloakSecurityRealm implements SecurityRealm {
|
||||
|
||||
@Override
|
||||
public RealmIdentity getRealmIdentity(Principal principal) throws RealmUnavailableException {
|
||||
if (principal instanceof KeycloakPrincipal) {
|
||||
return createRealmIdentity((KeycloakPrincipal) principal);
|
||||
}
|
||||
return RealmIdentity.NON_EXISTENT;
|
||||
}
|
||||
|
||||
private RealmIdentity createRealmIdentity(KeycloakPrincipal principal) {
|
||||
return new RealmIdentity() {
|
||||
@Override
|
||||
public Principal getRealmIdentityPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
|
||||
return SupportLevel.UNSUPPORTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <C extends Credential> C getCredential(Class<C> credentialType) throws RealmUnavailableException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
|
||||
return SupportLevel.SUPPORTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyEvidence(Evidence evidence) throws RealmUnavailableException {
|
||||
return principal != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() throws RealmUnavailableException {
|
||||
return principal != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationIdentity getAuthorizationIdentity() throws RealmUnavailableException {
|
||||
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) principal.getKeycloakSecurityContext();
|
||||
Attributes attributes = new MapAttributes();
|
||||
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
|
||||
|
||||
attributes.addAll(RoleDecoder.KEY_ROLES, roles);
|
||||
|
||||
return AuthorizationIdentity.basicIdentity(attributes);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
|
||||
return SupportLevel.UNSUPPORTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
|
||||
return SupportLevel.POSSIBLY_SUPPORTED;
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2016 Red Hat, Inc., and individual 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.elytron;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import javax.security.sasl.AuthorizeCallback;
|
||||
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.wildfly.security.auth.callback.AuthenticationCompleteCallback;
|
||||
import org.wildfly.security.auth.callback.EvidenceVerifyCallback;
|
||||
import org.wildfly.security.auth.callback.SecurityIdentityCallback;
|
||||
import org.wildfly.security.auth.server.SecurityIdentity;
|
||||
import org.wildfly.security.evidence.Evidence;
|
||||
import org.wildfly.security.http.HttpAuthenticationException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
final class SecurityIdentityUtil {
|
||||
|
||||
static final SecurityIdentity authorize(CallbackHandler callbackHandler, Principal principal) {
|
||||
try {
|
||||
EvidenceVerifyCallback evidenceVerifyCallback = new EvidenceVerifyCallback(new Evidence() {
|
||||
@Override
|
||||
public Principal getPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
});
|
||||
|
||||
callbackHandler.handle(new Callback[]{evidenceVerifyCallback});
|
||||
|
||||
if (evidenceVerifyCallback.isVerified()) {
|
||||
AuthorizeCallback authorizeCallback = new AuthorizeCallback(null, null);
|
||||
|
||||
try {
|
||||
callbackHandler.handle(new Callback[] {authorizeCallback});
|
||||
|
||||
authorizeCallback.isAuthorized();
|
||||
} catch (Exception e) {
|
||||
throw new HttpAuthenticationException(e);
|
||||
}
|
||||
|
||||
SecurityIdentityCallback securityIdentityCallback = new SecurityIdentityCallback();
|
||||
|
||||
callbackHandler.handle(new Callback[]{AuthenticationCompleteCallback.SUCCEEDED, securityIdentityCallback});
|
||||
|
||||
SecurityIdentity securityIdentity = securityIdentityCallback.getSecurityIdentity();
|
||||
|
||||
return securityIdentity;
|
||||
}
|
||||
} catch (UnsupportedCallbackException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#
|
||||
# JBoss, Home of Professional Open Source.
|
||||
# Copyright 2016 Red Hat, Inc., and individual 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.
|
||||
#
|
||||
|
||||
org.keycloak.adapters.elytron.KeycloakHttpServerAuthenticationMechanismFactory
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak WildFly Integration</name>
|
||||
@@ -32,7 +32,20 @@
|
||||
|
||||
<modules>
|
||||
<module>wildfly-adapter</module>
|
||||
<module>wf8-subsystem</module>
|
||||
<module>wildfly-subsystem</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>community</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!product</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>wf8-subsystem</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
+7
-1
@@ -167,7 +167,12 @@ public class SharedAttributeDefinitons {
|
||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||
.build();
|
||||
|
||||
|
||||
protected static final SimpleAttributeDefinition AUTODETECT_BEARER_ONLY =
|
||||
new SimpleAttributeDefinitionBuilder("autodetect-bearer-only", ModelType.BOOLEAN, true)
|
||||
.setXmlName("autodetect-bearer-only")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
static {
|
||||
@@ -193,6 +198,7 @@ public class SharedAttributeDefinitons {
|
||||
ATTRIBUTES.add(REGISTER_NODE_PERIOD);
|
||||
ATTRIBUTES.add(TOKEN_STORE);
|
||||
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
|
||||
ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-1
@@ -46,7 +46,7 @@ keycloak.realm.register-node-at-startup=Cluster setting
|
||||
keycloak.realm.register-node-period=how often to re-register node
|
||||
keycloak.realm.token-store=cookie or session storage for auth session data
|
||||
keycloak.realm.principal-attribute=token attribute to use to set Principal name
|
||||
|
||||
keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
|
||||
|
||||
keycloak.secure-deployment=A deployment secured by Keycloak
|
||||
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
|
||||
@@ -83,6 +83,7 @@ keycloak.secure-deployment.principal-attribute=token attribute to use to set Pri
|
||||
keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
|
||||
keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
|
||||
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
|
||||
keycloak.secure-deployment.autodetect-bearer-only=autodetect bearer-only requests
|
||||
|
||||
keycloak.secure-deployment.credential=Credential value
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
<xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
@@ -107,6 +108,7 @@
|
||||
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
||||
Executable → Regular
+1
-1
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
Executable → Regular
File mode changed.
Executable → Regular
File mode changed.
Executable → Regular
File mode changed.
Executable → Regular
File mode changed.
Executable → Regular
File mode changed.
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
+7
@@ -166,6 +166,12 @@ public class SharedAttributeDefinitons {
|
||||
.setAllowExpression(true)
|
||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition AUTODETECT_BEARER_ONLY =
|
||||
new SimpleAttributeDefinitionBuilder("autodetect-bearer-only", ModelType.BOOLEAN, true)
|
||||
.setXmlName("autodetect-bearer-only")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
@@ -193,6 +199,7 @@ public class SharedAttributeDefinitons {
|
||||
ATTRIBUTES.add(REGISTER_NODE_PERIOD);
|
||||
ATTRIBUTES.add(TOKEN_STORE);
|
||||
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
|
||||
ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
|
||||
}
|
||||
|
||||
private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) {
|
||||
|
||||
+2
-1
@@ -46,7 +46,7 @@ keycloak.realm.register-node-at-startup=Cluster setting
|
||||
keycloak.realm.register-node-period=how often to re-register node
|
||||
keycloak.realm.token-store=cookie or session storage for auth session data
|
||||
keycloak.realm.principal-attribute=token attribute to use to set Principal name
|
||||
|
||||
keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
|
||||
|
||||
keycloak.secure-deployment=A deployment secured by Keycloak
|
||||
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
|
||||
@@ -67,6 +67,7 @@ keycloak.secure-deployment.bearer-only=Bearer Token Auth only
|
||||
keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
|
||||
keycloak.secure-deployment.public-client=Public client
|
||||
keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support
|
||||
keycloak.secure-deployment.autodetect-bearer-only=autodetect bearer-only requests
|
||||
keycloak.secure-deployment.client-keystore=n/a
|
||||
keycloak.secure-deployment.client-keystore-password=n/a
|
||||
keycloak.secure-deployment.client-key-password=n/a
|
||||
|
||||
+2
@@ -65,6 +65,7 @@
|
||||
<xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
@@ -107,6 +108,7 @@
|
||||
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="autodetect-bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak Adapters</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak SAML EAP Integration</name>
|
||||
@@ -30,6 +30,18 @@
|
||||
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.as</groupId>
|
||||
<artifactId>jboss-as-parent</artifactId>
|
||||
<version>${jboss.as.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<modules>
|
||||
<module>adapter</module>
|
||||
<module>subsystem</module>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -88,7 +88,6 @@
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>3.1.0.GA</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.io.Serializable;
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -31,6 +32,9 @@ import java.util.Set;
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SamlPrincipal implements Serializable, Principal {
|
||||
|
||||
public static final String DEFAULT_ROLE_ATTRIBUTE_NAME = "Roles";
|
||||
|
||||
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
|
||||
private MultivaluedHashMap<String, String> friendlyAttributes = new MultivaluedHashMap<>();
|
||||
private String name;
|
||||
@@ -98,6 +102,15 @@ public class SamlPrincipal implements Serializable, Principal {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function that gets the attributes associated with this principal
|
||||
*
|
||||
* @return attributes associated with this principal
|
||||
*/
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return Collections.unmodifiableMap(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function that gets Attribute value by attribute friendly name
|
||||
*
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+7
-5
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.keycloak.adapters.saml;
|
||||
|
||||
import org.keycloak.adapters.saml.SamlDeployment.IDP.SingleSignOnService;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
@@ -95,21 +96,22 @@ public abstract class AbstractInitiateLogin implements AuthChallenge {
|
||||
nameIDPolicyFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
|
||||
}
|
||||
|
||||
SingleSignOnService sso = deployment.getIDP().getSingleSignOnService();
|
||||
SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
|
||||
.destination(deployment.getIDP().getSingleSignOnService().getRequestBindingUrl())
|
||||
.destination(sso.getRequestBindingUrl())
|
||||
.issuer(issuerURL)
|
||||
.forceAuthn(deployment.isForceAuthentication()).isPassive(deployment.isIsPassive())
|
||||
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
|
||||
if (deployment.getIDP().getSingleSignOnService().getResponseBinding() != null) {
|
||||
if (sso.getResponseBinding() != null) {
|
||||
String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
|
||||
if (deployment.getIDP().getSingleSignOnService().getResponseBinding() == SamlDeployment.Binding.POST) {
|
||||
if (sso.getResponseBinding() == SamlDeployment.Binding.POST) {
|
||||
protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
|
||||
}
|
||||
authnRequestBuilder.protocolBinding(protocolBinding);
|
||||
|
||||
}
|
||||
if (deployment.getAssertionConsumerServiceUrl() != null) {
|
||||
authnRequestBuilder.assertionConsumerUrl(deployment.getAssertionConsumerServiceUrl());
|
||||
if (sso.getAssertionConsumerServiceUrl() != null) {
|
||||
authnRequestBuilder.assertionConsumerUrl(sso.getAssertionConsumerServiceUrl());
|
||||
}
|
||||
return authnRequestBuilder;
|
||||
}
|
||||
|
||||
+11
-10
@@ -31,6 +31,7 @@ import org.keycloak.adapters.saml.rotation.SamlDescriptorPublicKeyLocator;
|
||||
import org.keycloak.rotation.CompositeKeyLocator;
|
||||
import org.keycloak.rotation.HardcodedKeyLocator;
|
||||
import org.keycloak.rotation.KeyLocator;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
@@ -45,6 +46,7 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
||||
private Binding requestBinding;
|
||||
private Binding responseBinding;
|
||||
private String requestBindingUrl;
|
||||
private URI assertionConsumerServiceUrl;
|
||||
|
||||
@Override
|
||||
public boolean signRequest() {
|
||||
@@ -76,6 +78,15 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
||||
return requestBindingUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getAssertionConsumerServiceUrl() {
|
||||
return assertionConsumerServiceUrl;
|
||||
}
|
||||
|
||||
public void setAssertionConsumerServiceUrl(URI assertionConsumerServiceUrl) {
|
||||
this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
|
||||
}
|
||||
|
||||
public void setSignRequest(boolean signRequest) {
|
||||
this.signRequest = signRequest;
|
||||
}
|
||||
@@ -277,7 +288,6 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
||||
private boolean turnOffChangeSessionIdOnLogin;
|
||||
private PrivateKey decryptionKey;
|
||||
private KeyPair signingKeyPair;
|
||||
private String assertionConsumerServiceUrl;
|
||||
private Set<String> roleAttributeNames;
|
||||
private PrincipalNamePolicy principalNamePolicy = PrincipalNamePolicy.FROM_NAME_ID;
|
||||
private String principalAttributeName;
|
||||
@@ -340,11 +350,6 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
||||
return signingKeyPair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssertionConsumerServiceUrl() {
|
||||
return assertionConsumerServiceUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRoleAttributeNames() {
|
||||
return roleAttributeNames;
|
||||
@@ -396,10 +401,6 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
||||
this.signingKeyPair = signingKeyPair;
|
||||
}
|
||||
|
||||
public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
|
||||
this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
|
||||
}
|
||||
|
||||
public void setRoleAttributeNames(Set<String> roleAttributeNames) {
|
||||
this.roleAttributeNames = roleAttributeNames;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.security.PrivateKey;
|
||||
import java.util.Set;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.keycloak.rotation.KeyLocator;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Represents SAML deployment configuration.
|
||||
@@ -103,8 +104,24 @@ public interface SamlDeployment {
|
||||
*/
|
||||
boolean validateAssertionSignature();
|
||||
Binding getRequestBinding();
|
||||
/**
|
||||
* SAML allows the client to request what binding type it wants authn responses to use. The default is
|
||||
* that the client will not request a specific binding type for responses.
|
||||
* @return
|
||||
*/
|
||||
Binding getResponseBinding();
|
||||
/**
|
||||
* Returns URL for the IDP login service that the client will send requests to.
|
||||
* @return
|
||||
*/
|
||||
String getRequestBindingUrl();
|
||||
/**
|
||||
* Returns URI where the IdP should send the responses to. The default is
|
||||
* that the client will not request a specific assertion consumer service URL.
|
||||
* This property is typically accompanied by the ProtocolBinding attribute.
|
||||
* @return
|
||||
*/
|
||||
URI getAssertionConsumerServiceUrl();
|
||||
}
|
||||
|
||||
public interface SingleLogoutService {
|
||||
@@ -141,7 +158,6 @@ public interface SamlDeployment {
|
||||
KeyPair getSigningKeyPair();
|
||||
String getSignatureCanonicalizationMethod();
|
||||
SignatureAlgorithm getSignatureAlgorithm();
|
||||
String getAssertionConsumerServiceUrl();
|
||||
String getLogoutPage();
|
||||
|
||||
Set<String> getRoleAttributeNames();
|
||||
|
||||
@@ -32,6 +32,7 @@ public class IDP implements Serializable {
|
||||
private String requestBinding;
|
||||
private String responseBinding;
|
||||
private String bindingUrl;
|
||||
private String assertionConsumerServiceUrl;
|
||||
private boolean validateAssertionSignature;
|
||||
|
||||
public boolean isSignRequest() {
|
||||
@@ -81,6 +82,14 @@ public class IDP implements Serializable {
|
||||
public void setBindingUrl(String bindingUrl) {
|
||||
this.bindingUrl = bindingUrl;
|
||||
}
|
||||
|
||||
public String getAssertionConsumerServiceUrl() {
|
||||
return assertionConsumerServiceUrl;
|
||||
}
|
||||
|
||||
public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
|
||||
this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SingleLogoutService implements Serializable {
|
||||
|
||||
+1
@@ -72,6 +72,7 @@ public class ConfigXmlConstants {
|
||||
public static final String VALIDATE_REQUEST_SIGNATURE_ATTR = "validateRequestSignature";
|
||||
public static final String POST_BINDING_URL_ATTR = "postBindingUrl";
|
||||
public static final String REDIRECT_BINDING_URL_ATTR = "redirectBindingUrl";
|
||||
public static final String ASSERTION_CONSUMER_SERVICE_URL_ATTR = "assertionConsumerServiceUrl";
|
||||
|
||||
public static final String HTTP_CLIENT_ELEMENT = "HttpClient";
|
||||
public static final String ALLOW_ANY_HOSTNAME_ATTR = "allowAnyHostname";
|
||||
|
||||
+7
@@ -41,6 +41,7 @@ import java.security.cert.Certificate;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.keycloak.adapters.cloned.HttpClientBuilder;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
@@ -156,6 +157,12 @@ public class DeploymentBuilder {
|
||||
if (sp.getIdp().getSingleSignOnService().getResponseBinding() != null) {
|
||||
sso.setResponseBinding(SamlDeployment.Binding.parseBinding(sp.getIdp().getSingleSignOnService().getResponseBinding()));
|
||||
}
|
||||
if (sp.getIdp().getSingleSignOnService().getAssertionConsumerServiceUrl() != null) {
|
||||
if (! sp.getIdp().getSingleSignOnService().getAssertionConsumerServiceUrl().endsWith("/saml")) {
|
||||
throw new RuntimeException("AssertionConsumerServiceUrl must end with \"/saml\".");
|
||||
}
|
||||
sso.setAssertionConsumerServiceUrl(URI.create(sp.getIdp().getSingleSignOnService().getAssertionConsumerServiceUrl()));
|
||||
}
|
||||
sso.setSignRequest(sp.getIdp().getSingleSignOnService().isSignRequest());
|
||||
sso.setValidateResponseSignature(sp.getIdp().getSingleSignOnService().isValidateResponseSignature());
|
||||
sso.setValidateAssertionSignature(sp.getIdp().getSingleSignOnService().isValidateAssertionSignature());
|
||||
|
||||
+1
@@ -118,6 +118,7 @@ public class IDPXmlParser extends AbstractParser {
|
||||
sso.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
|
||||
sso.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
|
||||
sso.setBindingUrl(getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
|
||||
sso.setAssertionConsumerServiceUrl(getAttributeValue(element, ConfigXmlConstants.ASSERTION_CONSUMER_SERVICE_URL_ATTR));
|
||||
return sso;
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -17,6 +17,8 @@
|
||||
|
||||
package org.keycloak.adapters.saml.profile;
|
||||
|
||||
import static org.keycloak.adapters.saml.SamlPrincipal.DEFAULT_ROLE_ATTRIBUTE_NAME;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.saml.AbstractInitiateLogin;
|
||||
import org.keycloak.adapters.saml.OnSessionCreated;
|
||||
@@ -422,6 +424,11 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// roles should also be there as regular attributes
|
||||
// this mainly required for elytron and its ABAC nature
|
||||
attributes.put(DEFAULT_ROLE_ATTRIBUTE_NAME, new ArrayList<>(roles));
|
||||
|
||||
if (deployment.getPrincipalNamePolicy() == SamlDeployment.PrincipalNamePolicy.FROM_ATTRIBUTE) {
|
||||
if (deployment.getPrincipalAttributeName() != null) {
|
||||
String attribute = attributes.getFirst(deployment.getPrincipalAttributeName());
|
||||
|
||||
+9
-1
@@ -156,7 +156,15 @@ public class EcpAuthenticationHandler extends AbstractSamlAuthenticationHandler
|
||||
paosRequestHeader.setMustUnderstand(true);
|
||||
paosRequestHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next");
|
||||
paosRequestHeader.addAttribute(envelope.createName("service"), JBossSAMLURIConstants.ECP_PROFILE.get());
|
||||
paosRequestHeader.addAttribute(envelope.createName("responseConsumerURL"), deployment.getAssertionConsumerServiceUrl());
|
||||
paosRequestHeader.addAttribute(envelope.createName("responseConsumerURL"), getResponseConsumerUrl());
|
||||
}
|
||||
|
||||
private String getResponseConsumerUrl() {
|
||||
return (deployment.getIDP() == null
|
||||
|| deployment.getIDP().getSingleSignOnService() == null
|
||||
|| deployment.getIDP().getSingleSignOnService().getAssertionConsumerServiceUrl() == null
|
||||
) ? null
|
||||
: deployment.getIDP().getSingleSignOnService().getAssertionConsumerServiceUrl().toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<xs:schema version="1.0"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:keycloak:saml:adapter"
|
||||
targetNamespace="urn:keycloak:saml:adapter"
|
||||
elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
|
||||
<xs:element name="keycloak-saml-adapter" type="adapter-type"/>
|
||||
<xs:complexType name="adapter-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Keycloak SAML Adapter configuration file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:all>
|
||||
<xs:element name="SP" maxOccurs="1" minOccurs="0" type="sp-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Describes SAML service provider configuration.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="sp-type">
|
||||
<xs:all>
|
||||
<xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
List of service provider encryption and validation keys.
|
||||
|
||||
If the IDP requires that the client application (SP) sign all of its requests and/or if the IDP will encrypt assertions, you must define the keys used to do this. For client signed documents you must define both the private and public key or certificate that will be used to sign documents. For encryption, you only have to define the private key that will be used to decrypt.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="PrincipalNameMapping" type="principal-name-mapping-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>When creating a Java Principal object that you obtain from methods like HttpServletRequest.getUserPrincipal(), you can define what name that is returned by the Principal.getName() method.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="RoleIdentifiers" type="role-identifiers-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines what SAML attributes within the assertion received from the user should be used as role identifiers within the Java EE Security Context for the user.
|
||||
By default Role attribute values are converted to Java EE roles. Some IDPs send roles via a member or memberOf attribute assertion. You can define one or more Attribute elements to specify which SAML attributes must be converted into roles.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="IDP" type="idp-type" minOccurs="1" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Describes configuration of SAML identity provider for this service provider.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="entityID" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the identifier for this client. The IDP needs this value to determine who the client is that is communicating with it.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="sslPolicy" type="ssl-policy-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SSL policy the adapter will enforce.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="nameIDPolicyFormat" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SAML clients can request a specific NameID Subject format. Fill in this value if you want a specific format. It must be a standard SAML format identifier, i.e. urn:oasis:names:tc:SAML:2.0:nameid-format:transient. By default, no special format is requested.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="logoutPage" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>URL of the logout page.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="forceAuthentication" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SAML clients can request that a user is re-authenticated even if they are already logged in at the IDP. Default value is false.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="isPassive" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SAML clients can request that a user is never asked to authenticate even if they are not logged in at the IDP. Set this to true if you want this. Do not use together with forceAuthentication as they are opposite. Default value is false.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="turnOffChangeSessionIdOnLogin" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The session id is changed by default on a successful login on some platforms to plug a security attack vector. Change this to true to disable this. It is recommended you do not turn it off. Default value is false.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="keys-type">
|
||||
<xs:sequence>
|
||||
<xs:element name="Key" type="key-type" minOccurs="1" maxOccurs="unbounded">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Describes a single key used for signing or encryption.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="key-type">
|
||||
<xs:all>
|
||||
<xs:element name="KeyStore" maxOccurs="1" minOccurs="0" type="key-store-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Java keystore to load keys and certificates from.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="PrivateKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Private key (PEM format)</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="PublicKeyPem" type="xs:string" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Public key (PEM format)</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="CertificatePem" type="xs:string" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Certificate key (PEM format)</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="signing" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Flag defining whether the key should be used for signing.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="encryption" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Flag defining whether the key should be used for encryption</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="key-store-type">
|
||||
<xs:all>
|
||||
<xs:element name="PrivateKey" maxOccurs="1" minOccurs="0" type="private-key-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Private key declaration</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="Certificate" type="certificate-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Certificate declaration</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="file" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>File path to the key store.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="resource" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>WAR resource path to the key store. This is a path used in method call to ServletContext.getResourceAsStream().</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="password" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The password of the key store.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="private-key-type">
|
||||
<xs:attribute name="alias" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="password" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Keystores require an additional password to access private keys. In the PrivateKey element you must define this password within a password attribute.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="certificate-type">
|
||||
<xs:attribute name="alias" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Alias that points to the key or cert within the keystore.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="principal-name-mapping-type">
|
||||
<xs:attribute name="policy" type="principal-name-mapping-policy-type" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Policy used to populate value of Java Principal object obtained from methods like HttpServletRequest.getUserPrincipal().</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="attribute" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Name of the SAML assertion attribute to use within.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:simpleType name="principal-name-mapping-policy-type">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="FROM_NAME_ID">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This policy just uses whatever the SAML subject value is. This is the default setting</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="FROM_ATTRIBUTE">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This will pull the value from one of the attributes declared in the SAML assertion received from the server. You'll need to specify the name of the SAML assertion attribute to use within the attribute XML attribute.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="ssl-policy-type">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="ALL">
|
||||
<xs:annotation>
|
||||
<xs:documentation>All requests must come in via HTTPS.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="EXTERNAL">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Only non-private IP addresses must come over the wire via HTTPS.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="NONE">
|
||||
<xs:annotation>
|
||||
<xs:documentation>no requests are required to come over via HTTPS.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="signature-algorithm-type">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="RSA_SHA1"/>
|
||||
<xs:enumeration value="RSA_SHA256"/>
|
||||
<xs:enumeration value="RSA_SHA512"/>
|
||||
<xs:enumeration value="DSA_SHA1"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="binding-type">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="POST"/>
|
||||
<xs:enumeration value="REDIRECT"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:complexType name="role-identifiers-type">
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="Attribute" maxOccurs="unbounded" minOccurs="0" type="attribute-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies SAML attribute to be converted into roles.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="attribute-type">
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies name of the SAML attribute to be converted into roles.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="idp-type">
|
||||
<xs:sequence minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="SingleSignOnService" maxOccurs="1" minOccurs="1" type="sign-on-type">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Configuration of the login SAML endpoint of the IDP.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="SingleLogoutService" type="logout-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Configuration of the logout SAML endpoint of the IDP</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The Keys sub element of IDP is only used to define the certificate or public key to use to verify documents signed by the IDP.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element name="HttpClient" type="http-client-type" minOccurs="0" maxOccurs="1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Configuration of HTTP client used for automatic obtaining of certificates containing public keys for IDP signature verification via SAML descriptor of the IDP.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="entityID" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>issuer ID of the IDP.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="signaturesRequired" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If set to true, the client adapter will sign every document it sends to the IDP. Also, the client will expect that the IDP will be signing any documents sent to it. This switch sets the default for all request and response types.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="signatureAlgorithm" type="signature-algorithm-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Signature algorithm that the IDP expects signed documents to use. Defaults to RSA_SHA256</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the signature canonicalization method that the IDP expects signed documents to use. The default value is https://www.w3.org/2001/10/xml-exc-c14n# and should be good for most IDPs.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="encryption" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation></xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="sign-on-type">
|
||||
<xs:attribute name="signRequest" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client expect the IDP to sign the assertion response document sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="validateAssertionSignature" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client expect the IDP to sign the individual assertions sent back from an auhtn request? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="requestBinding" type="binding-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SAML binding type used for communicating with the IDP. The default value is POST, but you can set it to REDIRECT as well.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="responseBinding" type="binding-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SAML allows the client to request what binding type it wants authn responses to use. This value maps to ProtocolBinding attribute in SAML AuthnRequest. The default is that the client will not request a specific binding type for responses.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="bindingUrl" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the URL for the IDP login service that the client will send requests to.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="assertionConsumerServiceUrl" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>URL of the assertion consumer service (ACS) where the IDP login service should send responses to. By default it is unset, relying on the IdP settings. When set, it must end in "/saml". This property is typically accompanied by the responseBinding attribute.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="logout-type">
|
||||
<xs:attribute name="signRequest" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client sign authn requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="signResponse" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client sign logout responses it sends to the IDP requests? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client expect signed logout request documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Should the client expect signed logout response documents from the IDP? Defaults to whatever the IDP signaturesRequired element value is.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="requestBinding" type="binding-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the SAML binding type used for communicating SAML requests to the IDP. The default value is POST.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="responseBinding" type="binding-type" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the SAML binding type used for communicating SAML responses to the IDP. The default value is POST.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="postBindingUrl" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the URL for the IDP's logout service when using the POST binding. This setting is REQUIRED if using the POST binding.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="redirectBindingUrl" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the URL for the IDP's logout service when using the REDIRECT binding. This setting is REQUIRED if using the REDIRECT binding.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="http-client-type">
|
||||
<xs:attribute name="allowAnyHostname" type="xs:boolean" use="optional" default="false">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If the the IDP server requires HTTPS and this config option is set to true the IDP's certificate
|
||||
is validated via the truststore, but host name validation is not done. This setting should only be used during
|
||||
development and never in production as it will partly disable verification of SSL certificates.
|
||||
This seting may be useful in test environments. The default value is false.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="clientKeystore" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This is the file path to a keystore file. This keystore contains client certificate
|
||||
for two-way SSL when the adapter makes HTTPS requests to the IDP server.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="clientKeystorePassword" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Password for the client keystore and for the client's key.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="connectionPoolSize" type="xs:int" use="optional" default="10">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines number of pooled connections.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="disableTrustManager" type="xs:boolean" use="optional" default="false">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If the the IDP server requires HTTPS and this config option is set to true you do not have to specify a truststore.
|
||||
This setting should only be used during development and never in production as it will disable verification of SSL certificates.
|
||||
The default value is false.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="proxyUrl" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>URL to HTTP proxy to use for HTTP connections.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="truststore" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
|
||||
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
|
||||
HTTPS communications to the IDP server. Client making HTTPS requests need
|
||||
a way to verify the host of the server they are talking to. This is what the trustore does.
|
||||
The keystore contains one or more trusted host certificates or certificate authorities.
|
||||
You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="truststorePassword" type="xs:string" use="optional">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Password for the truststore keystore.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak SAML Jetty Integration</name>
|
||||
|
||||
+16
-2
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak SAML Client Adapter Modules</name>
|
||||
@@ -35,9 +35,23 @@
|
||||
<module>core</module>
|
||||
<module>undertow</module>
|
||||
<module>tomcat</module>
|
||||
<module>jetty</module>
|
||||
<module>wildfly</module>
|
||||
<module>as7-eap6</module>
|
||||
<module>servlet-filter</module>
|
||||
<module>wildfly-elytron</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>community</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!product</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>jetty</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak SAML Tomcat Integration</name>
|
||||
@@ -32,8 +32,21 @@
|
||||
|
||||
<modules>
|
||||
<module>tomcat-core</module>
|
||||
<module>tomcat6</module>
|
||||
<module>tomcat7</module>
|
||||
<module>tomcat8</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>community</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!product</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>tomcat6</module>
|
||||
<module>tomcat7</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-saml-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.0.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
Loaded 100 of 1043 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user