Remove SpringBoot adapters
Closes #28781 Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
parent
43aa10e091
commit
bf2c97065f
48 changed files with 1 additions and 3661 deletions
|
@ -38,9 +38,6 @@
|
|||
<module>js</module>
|
||||
<module>servlet-filter</module>
|
||||
<module>jakarta-servlet-filter</module>
|
||||
<module>spring-boot2</module>
|
||||
<module>spring-boot-adapter-core</module>
|
||||
<module>spring-boot-container-bundle</module>
|
||||
<module>spring-security</module>
|
||||
<module>undertow</module>
|
||||
<module>wildfly</module>
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
<?xml version="1.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.
|
||||
-->
|
||||
|
||||
<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>999.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-spring-boot-adapter-core</artifactId>
|
||||
<name>Keycloak Spring Boot Adapter Core</name>
|
||||
<description/>
|
||||
|
||||
<properties>
|
||||
<spring-boot.version>2.0.5.RELEASE</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<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>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<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>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,299 +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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.springboot;
|
||||
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import io.undertow.servlet.api.SecurityInfo.EmptyRoleSemantic;
|
||||
import io.undertow.servlet.api.WebResourceCollection;
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
||||
import org.eclipse.jetty.security.ConstraintMapping;
|
||||
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.server.handler.HandlerWrapper;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
import org.keycloak.adapters.jetty.KeycloakJettyAuthenticator;
|
||||
import org.keycloak.adapters.undertow.KeycloakServletExtension;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Keycloak authentication base integration for Spring Boot - base to be extended for particular boot versions.
|
||||
*/
|
||||
public class KeycloakBaseSpringBootConfiguration {
|
||||
|
||||
protected KeycloakSpringBootProperties keycloakProperties;
|
||||
|
||||
@Autowired
|
||||
public void setKeycloakSpringBootProperties(KeycloakSpringBootProperties keycloakProperties) {
|
||||
this.keycloakProperties = keycloakProperties;
|
||||
KeycloakSpringBootConfigResolverWrapper.setAdapterConfig(keycloakProperties);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setApplicationContext(ApplicationContext context) {
|
||||
KeycloakSpringBootConfigResolverWrapper.setApplicationContext(context);
|
||||
}
|
||||
|
||||
static class KeycloakBaseUndertowDeploymentInfoCustomizer {
|
||||
|
||||
protected final KeycloakSpringBootProperties keycloakProperties;
|
||||
|
||||
public KeycloakBaseUndertowDeploymentInfoCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||
this.keycloakProperties = keycloakProperties;
|
||||
}
|
||||
|
||||
public void customize(DeploymentInfo deploymentInfo) {
|
||||
|
||||
io.undertow.servlet.api.LoginConfig loginConfig = new io.undertow.servlet.api.LoginConfig(keycloakProperties.getRealm());
|
||||
loginConfig.addFirstAuthMethod("KEYCLOAK");
|
||||
|
||||
deploymentInfo.setLoginConfig(loginConfig);
|
||||
|
||||
deploymentInfo.addInitParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolverWrapper.class.getName());
|
||||
|
||||
|
||||
/* Support for '*' as all roles allowed
|
||||
* We clear out the role in the SecurityConstraints
|
||||
* and set the EmptyRoleSemantic to Authenticate
|
||||
* But we will set EmptyRoleSemantic to DENY (default)
|
||||
* if roles are non existing or left empty
|
||||
*/
|
||||
Iterator<io.undertow.servlet.api.SecurityConstraint> it = this.getSecurityConstraints().iterator();
|
||||
while (it.hasNext()) {
|
||||
io.undertow.servlet.api.SecurityConstraint securityConstraint = it.next();
|
||||
Set<String> rolesAllowed = securityConstraint.getRolesAllowed();
|
||||
|
||||
if (rolesAllowed.contains("*") || rolesAllowed.contains("**") ) {
|
||||
io.undertow.servlet.api.SecurityConstraint allRolesAllowed = new io.undertow.servlet.api.SecurityConstraint();
|
||||
allRolesAllowed.setEmptyRoleSemantic(EmptyRoleSemantic.AUTHENTICATE);
|
||||
allRolesAllowed.setTransportGuaranteeType(securityConstraint.getTransportGuaranteeType());
|
||||
for (WebResourceCollection wr : securityConstraint.getWebResourceCollections()) {
|
||||
allRolesAllowed.addWebResourceCollection(wr);
|
||||
}
|
||||
deploymentInfo.addSecurityConstraint(allRolesAllowed);
|
||||
} else // left empty will fall back on default EmptyRoleSemantic.DENY
|
||||
deploymentInfo.addSecurityConstraint(securityConstraint);
|
||||
|
||||
}
|
||||
deploymentInfo.addServletExtension(new KeycloakServletExtension());
|
||||
}
|
||||
|
||||
private List<io.undertow.servlet.api.SecurityConstraint> getSecurityConstraints() {
|
||||
|
||||
List<io.undertow.servlet.api.SecurityConstraint> undertowSecurityConstraints = new ArrayList<io.undertow.servlet.api.SecurityConstraint>();
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) {
|
||||
|
||||
io.undertow.servlet.api.SecurityConstraint undertowSecurityConstraint = new io.undertow.servlet.api.SecurityConstraint();
|
||||
undertowSecurityConstraint.addRolesAllowed(constraintDefinition.getAuthRoles());
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityCollection collectionDefinition : constraintDefinition.getSecurityCollections()) {
|
||||
|
||||
WebResourceCollection webResourceCollection = new WebResourceCollection();
|
||||
webResourceCollection.addHttpMethods(collectionDefinition.getMethods());
|
||||
webResourceCollection.addHttpMethodOmissions(collectionDefinition.getOmittedMethods());
|
||||
webResourceCollection.addUrlPatterns(collectionDefinition.getPatterns());
|
||||
|
||||
undertowSecurityConstraint.addWebResourceCollections(webResourceCollection);
|
||||
|
||||
}
|
||||
|
||||
undertowSecurityConstraints.add(undertowSecurityConstraint);
|
||||
}
|
||||
return undertowSecurityConstraints;
|
||||
}
|
||||
}
|
||||
|
||||
static class KeycloakBaseJettyServerCustomizer {
|
||||
|
||||
protected final KeycloakSpringBootProperties keycloakProperties;
|
||||
|
||||
public KeycloakBaseJettyServerCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||
this.keycloakProperties = keycloakProperties;
|
||||
}
|
||||
|
||||
public void customize(Server server) {
|
||||
|
||||
KeycloakJettyAuthenticator keycloakJettyAuthenticator = new KeycloakJettyAuthenticator();
|
||||
keycloakJettyAuthenticator.setConfigResolver(new KeycloakSpringBootConfigResolverWrapper());
|
||||
|
||||
/* 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());
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
WebAppContext webAppContext = server.getBean(WebAppContext.class);
|
||||
//if not found as registered bean let's try the handler
|
||||
if(webAppContext==null){
|
||||
webAppContext = getWebAppContext(server.getHandlers());
|
||||
}
|
||||
|
||||
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
|
||||
securityHandler.setConstraintMappings(jettyConstraintMappings);
|
||||
securityHandler.setAuthenticator(keycloakJettyAuthenticator);
|
||||
|
||||
webAppContext.setSecurityHandler(securityHandler);
|
||||
}
|
||||
|
||||
private WebAppContext getWebAppContext(Handler... handlers) {
|
||||
for (Handler handler : handlers) {
|
||||
if (handler instanceof WebAppContext) {
|
||||
return (WebAppContext) handler;
|
||||
} else if (handler instanceof HandlerList) {
|
||||
return getWebAppContext(((HandlerList) handler).getHandlers());
|
||||
} else if (handler instanceof HandlerCollection) {
|
||||
return getWebAppContext(((HandlerCollection) handler).getHandlers());
|
||||
} else if (handler instanceof HandlerWrapper) {
|
||||
return getWebAppContext(((HandlerWrapper) handler).getHandlers());
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("No WebAppContext found in Jetty server handlers");
|
||||
}
|
||||
}
|
||||
|
||||
static class KeycloakBaseTomcatContextCustomizer {
|
||||
|
||||
protected final KeycloakSpringBootProperties keycloakProperties;
|
||||
|
||||
public KeycloakBaseTomcatContextCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||
this.keycloakProperties = keycloakProperties;
|
||||
}
|
||||
|
||||
public void customize(Context context) {
|
||||
LoginConfig loginConfig = new LoginConfig();
|
||||
loginConfig.setAuthMethod("KEYCLOAK");
|
||||
context.setLoginConfig(loginConfig);
|
||||
|
||||
Set<String> authRoles = new HashSet<String>();
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
||||
for (String authRole : constraint.getAuthRoles()) {
|
||||
if (!authRoles.contains(authRole)) {
|
||||
context.addSecurityRole(authRole);
|
||||
authRoles.add(authRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
||||
SecurityConstraint tomcatConstraint = new SecurityConstraint();
|
||||
for (String authRole : constraint.getAuthRoles()) {
|
||||
tomcatConstraint.addAuthRole(authRole);
|
||||
if(authRole.equals("*") || authRole.equals("**")) {
|
||||
// For some reasons embed tomcat don't set the auth constraint on true when wildcard is used
|
||||
tomcatConstraint.setAuthConstraint(true);
|
||||
}
|
||||
}
|
||||
|
||||
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
|
||||
SecurityCollection tomcatSecCollection = new SecurityCollection();
|
||||
|
||||
if (collection.getName() != null) {
|
||||
tomcatSecCollection.setName(collection.getName());
|
||||
}
|
||||
if (collection.getDescription() != null) {
|
||||
tomcatSecCollection.setDescription(collection.getDescription());
|
||||
}
|
||||
|
||||
for (String pattern : collection.getPatterns()) {
|
||||
tomcatSecCollection.addPattern(pattern);
|
||||
}
|
||||
|
||||
for (String method : collection.getMethods()) {
|
||||
tomcatSecCollection.addMethod(method);
|
||||
}
|
||||
|
||||
for (String method : collection.getOmittedMethods()) {
|
||||
tomcatSecCollection.addOmittedMethod(method);
|
||||
}
|
||||
|
||||
tomcatConstraint.addCollection(tomcatSecCollection);
|
||||
}
|
||||
|
||||
context.addConstraint(tomcatConstraint);
|
||||
}
|
||||
|
||||
context.addParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolverWrapper.class.getName());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.springboot;
|
||||
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class KeycloakSpringBootConfigResolver implements org.keycloak.adapters.KeycloakConfigResolver {
|
||||
|
||||
private KeycloakDeployment keycloakDeployment;
|
||||
|
||||
@Autowired(required=false)
|
||||
private AdapterConfig adapterConfig;
|
||||
|
||||
@Override
|
||||
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
|
||||
if (keycloakDeployment != null) {
|
||||
return keycloakDeployment;
|
||||
}
|
||||
|
||||
keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfig);
|
||||
|
||||
return keycloakDeployment;
|
||||
}
|
||||
|
||||
void setAdapterConfig(AdapterConfig adapterConfig) {
|
||||
this.adapterConfig = adapterConfig;
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters.springboot;
|
||||
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.adapters.springsecurity.config.KeycloakSpringConfigResolverWrapper;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* <p>A specific implementation of {@link KeycloakSpringConfigResolverWrapper} that first tries to register any {@link KeycloakConfigResolver}
|
||||
* instance provided by the application. if none is provided, {@link KeycloakSpringBootConfigResolver} is set.
|
||||
*
|
||||
* <p>This implementation is specially useful when using Spring Boot and Spring Security in the same application where the same {@link KeycloakConfigResolver}
|
||||
* instance must be used across the different stacks.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class KeycloakSpringBootConfigResolverWrapper extends KeycloakSpringConfigResolverWrapper {
|
||||
|
||||
private static ApplicationContext context;
|
||||
private static KeycloakSpringBootProperties adapterConfig;
|
||||
|
||||
public KeycloakSpringBootConfigResolverWrapper() {
|
||||
super(new KeycloakSpringBootConfigResolver());
|
||||
try {
|
||||
setDelegate(context.getBean(KeycloakConfigResolver.class));
|
||||
} catch (NoSuchBeanDefinitionException ignore) {
|
||||
}
|
||||
if (getDelegate() instanceof KeycloakSpringBootConfigResolver) {
|
||||
KeycloakSpringBootConfigResolver.class.cast(getDelegate()).setAdapterConfig(adapterConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setApplicationContext(ApplicationContext context) {
|
||||
KeycloakSpringBootConfigResolverWrapper.context = context;
|
||||
}
|
||||
|
||||
public static void setAdapterConfig(KeycloakSpringBootProperties adapterConfig) {
|
||||
KeycloakSpringBootConfigResolverWrapper.adapterConfig = adapterConfig;
|
||||
}
|
||||
}
|
|
@ -1,162 +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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.springboot;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ConfigurationProperties(prefix = "keycloak", ignoreUnknownFields = false)
|
||||
public class KeycloakSpringBootProperties extends AdapterConfig {
|
||||
|
||||
/* this is a dummy property to avoid re-rebinding problem with property keycloak.config.resolver
|
||||
when using spring cloud - see KEYCLOAK-2977 */
|
||||
@JsonIgnore
|
||||
private Map config = new HashMap();
|
||||
|
||||
/**
|
||||
* Allow enabling of Keycloak Spring Boot adapter by configuration.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
public Map getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* To provide Java EE security constraints
|
||||
*/
|
||||
private List<SecurityConstraint> securityConstraints = new ArrayList<SecurityConstraint>();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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 {
|
||||
/**
|
||||
* The name of your security constraint
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* The description of your security collection
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* A list of URL patterns that should match to apply the security collection
|
||||
*/
|
||||
private List<String> patterns = new ArrayList<String>();
|
||||
/**
|
||||
* A list of HTTP methods that applies for this security collection
|
||||
*/
|
||||
private List<String> methods = new ArrayList<String>();
|
||||
/**
|
||||
* A list of HTTP methods that will be omitted for this security collection
|
||||
*/
|
||||
private List<String> omittedMethods = new ArrayList<String>();
|
||||
|
||||
public List<String> getPatterns() {
|
||||
return patterns;
|
||||
}
|
||||
|
||||
public List<String> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<String> getOmittedMethods() {
|
||||
return omittedMethods;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void setPatterns(List<String> patterns) {
|
||||
this.patterns = patterns;
|
||||
}
|
||||
|
||||
public void setMethods(List<String> methods) {
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
public void setOmittedMethods(List<String> omittedMethods) {
|
||||
this.omittedMethods = omittedMethods;
|
||||
}
|
||||
}
|
||||
|
||||
public List<SecurityConstraint> getSecurityConstraints() {
|
||||
return securityConstraints;
|
||||
}
|
||||
|
||||
public void setSecurityConstraints(List<SecurityConstraint> securityConstraints) {
|
||||
this.securityConstraints = securityConstraints;
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
<?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>999.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-boot-container-bundle</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</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-jetty94-adapter</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>org.keycloak:keycloak-undertow-adapter</include>
|
||||
<include>org.keycloak:keycloak-jetty94-adapter</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>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<includeDependencySources>true</includeDependencySources>
|
||||
<dependencySourceIncludes>org.keycloak:keycloak-adapter-core</dependencySourceIncludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
2
adapters/oidc/spring-boot2/.gitignore
vendored
2
adapters/oidc/spring-boot2/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
/.apt_generated/
|
||||
/.apt_generated_tests/
|
|
@ -1,147 +0,0 @@
|
|||
<?xml version="1.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.
|
||||
-->
|
||||
|
||||
<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>999.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-spring-boot-2-adapter</artifactId>
|
||||
<name>Keycloak Spring Boot 2 Integration</name>
|
||||
<description/>
|
||||
|
||||
<properties>
|
||||
<spring-boot.version>2.0.5.RELEASE</spring-boot.version>
|
||||
<spring.version>5.0.2.RELEASE</spring.version>
|
||||
<mockito.version>1.9.5</mockito.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-adapter-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<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>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</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>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,116 +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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.springboot;
|
||||
|
||||
import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
|
||||
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.undertow.UndertowDeploymentInfoCustomizer;
|
||||
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Keycloak authentication integration for Spring Boot 2
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication
|
||||
@EnableConfigurationProperties(KeycloakSpringBootProperties.class)
|
||||
@ConditionalOnProperty(value = "keycloak.enabled", matchIfMissing = true)
|
||||
public class KeycloakAutoConfiguration extends KeycloakBaseSpringBootConfiguration {
|
||||
|
||||
|
||||
@Bean
|
||||
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> getKeycloakContainerCustomizer() {
|
||||
return new WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>() {
|
||||
@Override
|
||||
public void customize(ConfigurableServletWebServerFactory configurableServletWebServerFactory) {
|
||||
if(configurableServletWebServerFactory instanceof TomcatServletWebServerFactory){
|
||||
|
||||
TomcatServletWebServerFactory container = (TomcatServletWebServerFactory)configurableServletWebServerFactory;
|
||||
container.addContextValves(new KeycloakAuthenticatorValve());
|
||||
container.addContextCustomizers(tomcatKeycloakContextCustomizer());
|
||||
|
||||
} else if (configurableServletWebServerFactory instanceof UndertowServletWebServerFactory){
|
||||
|
||||
UndertowServletWebServerFactory container = (UndertowServletWebServerFactory)configurableServletWebServerFactory;
|
||||
container.addDeploymentInfoCustomizers(undertowKeycloakContextCustomizer());
|
||||
|
||||
} else if (configurableServletWebServerFactory instanceof JettyServletWebServerFactory){
|
||||
|
||||
JettyServletWebServerFactory container = (JettyServletWebServerFactory)configurableServletWebServerFactory;
|
||||
container.addServerCustomizers(jettyKeycloakServerCustomizer());
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = {"org.eclipse.jetty.webapp.WebAppContext"})
|
||||
public JettyServerCustomizer jettyKeycloakServerCustomizer() {
|
||||
return new KeycloakJettyServerCustomizer(keycloakProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = {"org.apache.catalina.startup.Tomcat"})
|
||||
public TomcatContextCustomizer tomcatKeycloakContextCustomizer() {
|
||||
return new KeycloakTomcatContextCustomizer(keycloakProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = {"io.undertow.Undertow"})
|
||||
public UndertowDeploymentInfoCustomizer undertowKeycloakContextCustomizer() {
|
||||
return new KeycloakUndertowDeploymentInfoCustomizer(keycloakProperties);
|
||||
}
|
||||
|
||||
static class KeycloakJettyServerCustomizer extends KeycloakBaseJettyServerCustomizer implements JettyServerCustomizer {
|
||||
|
||||
|
||||
public KeycloakJettyServerCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||
super(keycloakProperties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class KeycloakTomcatContextCustomizer extends KeycloakBaseTomcatContextCustomizer implements TomcatContextCustomizer {
|
||||
|
||||
public KeycloakTomcatContextCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||
super(keycloakProperties);
|
||||
}
|
||||
}
|
||||
|
||||
static class KeycloakUndertowDeploymentInfoCustomizer extends KeycloakBaseUndertowDeploymentInfoCustomizer implements UndertowDeploymentInfoCustomizer {
|
||||
|
||||
public KeycloakUndertowDeploymentInfoCustomizer(KeycloakSpringBootProperties keycloakProperties){
|
||||
super(keycloakProperties);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package org.keycloak.adapters.springboot.client;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateCustomizer;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
public class KeycloakRestTemplateCustomizer implements RestTemplateCustomizer {
|
||||
|
||||
private final KeycloakSecurityContextClientRequestInterceptor keycloakInterceptor;
|
||||
|
||||
public KeycloakRestTemplateCustomizer() {
|
||||
this(new KeycloakSecurityContextClientRequestInterceptor());
|
||||
}
|
||||
|
||||
protected KeycloakRestTemplateCustomizer(
|
||||
KeycloakSecurityContextClientRequestInterceptor keycloakInterceptor
|
||||
) {
|
||||
this.keycloakInterceptor = keycloakInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(RestTemplate restTemplate) {
|
||||
restTemplate.getInterceptors().add(keycloakInterceptor);
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package org.keycloak.adapters.springboot.client;
|
||||
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
|
||||
/**
|
||||
* Interceptor for {@link ClientHttpRequestExecution} objects created for server to server secured
|
||||
* communication using OAuth2 bearer tokens issued by Keycloak.
|
||||
*
|
||||
* @author <a href="mailto:jmcshan1@gmail.com">James McShane</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakSecurityContextClientRequestInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
|
||||
/**
|
||||
* Returns the {@link KeycloakSecurityContext} from the Spring {@link ServletRequestAttributes}'s {@link Principal}.
|
||||
*
|
||||
* The principal must support retrieval of the KeycloakSecurityContext, so at this point, only {@link KeycloakPrincipal}
|
||||
* values are supported
|
||||
*
|
||||
* @return the current <code>KeycloakSecurityContext</code>
|
||||
*/
|
||||
protected KeycloakSecurityContext getKeycloakSecurityContext() {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
Principal principal = attributes.getRequest().getUserPrincipal();
|
||||
if (principal == null) {
|
||||
throw new IllegalStateException("Cannot set authorization header because there is no authenticated principal");
|
||||
}
|
||||
if (!(principal instanceof KeycloakPrincipal)) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Cannot set authorization header because the principal type %s does not provide the KeycloakSecurityContext",
|
||||
principal.getClass()));
|
||||
}
|
||||
return ((KeycloakPrincipal) principal).getKeycloakSecurityContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
|
||||
KeycloakSecurityContext context = this.getKeycloakSecurityContext();
|
||||
httpRequest.getHeaders().set(AUTHORIZATION_HEADER, "Bearer " + context.getTokenString());
|
||||
return clientHttpRequestExecution.execute(httpRequest, bytes);
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.keycloak.adapters.springboot.KeycloakAutoConfiguration
|
|
@ -1,28 +0,0 @@
|
|||
package org.keycloak.adapters.springboot.client;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class KeycloakRestTemplateCustomizerTest {
|
||||
|
||||
private KeycloakRestTemplateCustomizer customizer;
|
||||
private KeycloakSecurityContextClientRequestInterceptor interceptor =
|
||||
mock(KeycloakSecurityContextClientRequestInterceptor.class);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
customizer = new KeycloakRestTemplateCustomizer(interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interceptorIsAddedToRequest() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
customizer.customize(restTemplate);
|
||||
assertTrue(restTemplate.getInterceptors().contains(interceptor));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,87 +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.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.springboot.client;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Keycloak spring boot client request factory tests.
|
||||
*/
|
||||
public class KeycloakSecurityContextClientRequestInterceptorTest {
|
||||
|
||||
@Spy
|
||||
private KeycloakSecurityContextClientRequestInterceptor factory;
|
||||
|
||||
private MockHttpServletRequest servletRequest;
|
||||
|
||||
@Mock
|
||||
private KeycloakSecurityContext keycloakSecurityContext;
|
||||
|
||||
@Mock
|
||||
private KeycloakPrincipal keycloakPrincipal;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
servletRequest = new MockHttpServletRequest();
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest));
|
||||
servletRequest.setUserPrincipal(keycloakPrincipal);
|
||||
when(keycloakPrincipal.getKeycloakSecurityContext()).thenReturn(keycloakSecurityContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetKeycloakSecurityContext() throws Exception {
|
||||
KeycloakSecurityContext context = factory.getKeycloakSecurityContext();
|
||||
assertNotNull(context);
|
||||
assertEquals(keycloakSecurityContext, context);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testGetKeycloakSecurityContextInvalidPrincipal() throws Exception {
|
||||
servletRequest.setUserPrincipal(new MarkerPrincipal());
|
||||
factory.getKeycloakSecurityContext();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testGetKeycloakSecurityContextNullAuthentication() throws Exception {
|
||||
servletRequest.setUserPrincipal(null);
|
||||
factory.getKeycloakSecurityContext();
|
||||
}
|
||||
|
||||
private static class MarkerPrincipal implements Principal {
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,26 +69,6 @@
|
|||
<artifactId>keycloak-undertow-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-2-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>spring-boot-container-bundle</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-security-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-starter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-client</artifactId>
|
||||
|
|
|
@ -17,8 +17,6 @@ include::java-adapter-config.adoc[]
|
|||
endif::[]
|
||||
include::jboss-adapter.adoc[]
|
||||
|
||||
include::spring-boot-adapter.adoc[]
|
||||
|
||||
ifeval::[{project_community}==true]
|
||||
include::spring-security-adapter.adoc[]
|
||||
endif::[]
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
[[_spring_boot_adapter]]
|
||||
==== Spring Boot adapter
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
This adapter is deprecated and will be removed in a future release of Keycloak. No further enhancements or new features
|
||||
will be added to this adapter.
|
||||
|
||||
We recommend that you leverage the OAuth2/OpenID Connect support from Spring Security.
|
||||
|
||||
For more details about how to integrate {project_name} with Spring Boot applications, consider looking at the
|
||||
{quickstartRepo_link}[Keycloak Quickstart GitHub Repository].
|
||||
====
|
||||
|
||||
|
||||
[[_spring_boot_adapter_installation]]
|
||||
===== Installing the Spring Boot adapter
|
||||
|
||||
To be able to secure Spring Boot apps you must add the Keycloak Spring Boot adapter JAR to your app.
|
||||
You then have to provide some extra configuration via normal Spring Boot configuration (`application.properties`).
|
||||
|
||||
The Keycloak Spring Boot adapter takes advantage of Spring Boot's autoconfiguration so all you need to do is add this adapter Keycloak Spring Boot starter to your project.
|
||||
|
||||
.Procedure
|
||||
|
||||
. To add the starter to your project using Maven, add the following to your dependencies:
|
||||
+
|
||||
[source,xml,subs="attributes+"]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
. Add the Adapter BOM dependency:
|
||||
+
|
||||
[source,xml,subs="attributes+"]
|
||||
----
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak.bom</groupId>
|
||||
<artifactId>keycloak-adapter-bom</artifactId>
|
||||
<version>{project_versionMvn}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
----
|
||||
|
||||
|
||||
Currently the following embedded containers are supported and do not require any extra dependencies if using the Starter:
|
||||
|
||||
* Tomcat
|
||||
* Undertow
|
||||
|
||||
[[_spring_boot_adapter_configuration]]
|
||||
===== Configuring the Spring Boot Adapter
|
||||
|
||||
Use the procedure to configure your Spring Boot app to use {project_name}.
|
||||
|
||||
.Procedure
|
||||
|
||||
. Instead of a `keycloak.json` file, you configure the realm for the Spring Boot adapter via the normal Spring Boot configuration. For example:
|
||||
+
|
||||
[source,subs="attributes+"]
|
||||
----
|
||||
keycloak.realm = demorealm
|
||||
keycloak.auth-server-url = http://127.0.0.1:8080{kc_base_path}
|
||||
keycloak.ssl-required = external
|
||||
keycloak.resource = demoapp
|
||||
keycloak.credentials.secret = 11111111-1111-1111-1111-111111111111
|
||||
keycloak.use-resource-role-mappings = true
|
||||
----
|
||||
+
|
||||
You can disable the Keycloak Spring Boot Adapter (for example in tests) by setting `keycloak.enabled = false`.
|
||||
|
||||
. To configure a Policy Enforcer, unlike keycloak.json, use `policy-enforcer-config` instead of just `policy-enforcer`.
|
||||
|
||||
. Specify the Jakarta EE security config that would normally go in the `web.xml`.
|
||||
+
|
||||
The Spring Boot Adapter will set the `login-method` to `KEYCLOAK` and configure the `security-constraints` at startup time. Here's an example configuration:
|
||||
+
|
||||
[source]
|
||||
----
|
||||
keycloak.securityConstraints[0].authRoles[0] = admin
|
||||
keycloak.securityConstraints[0].authRoles[1] = user
|
||||
keycloak.securityConstraints[0].securityCollections[0].name = insecure stuff
|
||||
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /insecure
|
||||
|
||||
keycloak.securityConstraints[1].authRoles[0] = admin
|
||||
keycloak.securityConstraints[1].securityCollections[0].name = admin stuff
|
||||
keycloak.securityConstraints[1].securityCollections[0].patterns[0] = /admin
|
||||
----
|
||||
|
||||
WARNING: If you plan to deploy your Spring Application as a WAR then you should not use the Spring Boot Adapter and use the dedicated adapter for the application server or servlet container you are using. Your Spring Boot should also contain a `web.xml` file.
|
|
@ -294,99 +294,4 @@ public class RemoteProductService implements ProductService {
|
|||
return Arrays.asList(response.getBody());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
===== Spring Boot Integration
|
||||
|
||||
The Spring Boot and the Spring Security adapters can be combined.
|
||||
|
||||
If you are using the Keycloak Spring Boot Starter to make use of the Spring Security adapter you just need to add the Spring Security starter :
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
----
|
||||
|
||||
====== Using Spring Boot Configuration
|
||||
|
||||
By Default, the Spring Security Adapter looks for a `keycloak.json` configuration file. You can make sure it looks at the configuration provided by the Spring Boot Adapter by adding this bean:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
@Configuration
|
||||
public class CustomKeycloakConfig {
|
||||
|
||||
@Bean
|
||||
public KeycloakConfigResolver keycloakConfigResolver() {
|
||||
return new KeycloakSpringBootConfigResolver();
|
||||
}
|
||||
}
|
||||
|
||||
----
|
||||
|
||||
Do not declare the `KeycloakConfigResolver` bean in a configuration class that extends `KeycloakWebSecurityConfigurerAdapter` as this will cause a `Circular References` problem in Spring Boot starting with version 2.6.0.
|
||||
|
||||
====== Avoid double bean registration
|
||||
|
||||
Spring Boot attempts to eagerly register filter beans with the web application context.
|
||||
Therefore, when running the Keycloak Spring Security adapter in a Spring Boot environment, it may be necessary to add ``FilterRegistrationBean``s to your security configuration to prevent the Keycloak filters from being registered twice.
|
||||
|
||||
Spring Boot 2.1 also disables `spring.main.allow-bean-definition-overriding` by default. This can mean that an `BeanDefinitionOverrideException` will be encountered if a `Configuration` class extending `KeycloakWebSecurityConfigurerAdapter` registers a bean that is already detected by a `@ComponentScan`. This can be avoided by overriding the registration to use the Boot-specific `@ConditionalOnMissingBean` annotation, as with `HttpSessionManager` below.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
|
||||
{
|
||||
...
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
|
||||
KeycloakAuthenticationProcessingFilter filter) {
|
||||
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
|
||||
registrationBean.setEnabled(false);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
|
||||
KeycloakPreAuthActionsFilter filter) {
|
||||
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
|
||||
registrationBean.setEnabled(false);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
|
||||
KeycloakAuthenticatedActionsFilter filter) {
|
||||
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
|
||||
registrationBean.setEnabled(false);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
|
||||
KeycloakSecurityContextRequestFilter filter) {
|
||||
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
|
||||
registrationBean.setEnabled(false);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
@ConditionalOnMissingBean(HttpSessionManager.class)
|
||||
protected HttpSessionManager httpSessionManager() {
|
||||
return new HttpSessionManager();
|
||||
}
|
||||
...
|
||||
}
|
||||
----
|
||||
----
|
|
@ -15,7 +15,6 @@ ifeval::[{project_community}==true]
|
|||
* {quickstartRepo_link}/tree/latest/spring/rest-authz-resource-server[Spring Boot]
|
||||
* <<_jboss_adapter, {project_name} Wildfly Adapter>> (Deprecated)
|
||||
* <<_servlet_filter_adapter,{project_name} Servlet Filter>> (Deprecated)
|
||||
* <<_spring_boot_adapter,{project_name} Spring Boot>> (Deprecated)
|
||||
* <<_spring_security_adapter,{project_name} Spring Security>> (Deprecated)
|
||||
endif::[]
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
<artifactId>keycloak-misc-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>spring-boot-starter</module>
|
||||
<module>keycloak-test-helper</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
<?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>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-starter-parent</artifactId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>keycloak-spring-boot-starter</artifactId>
|
||||
<name>Keycloak :: Spring :: Boot :: Default :: Starter</name>
|
||||
<description>Spring Boot Default Starter for Keycloak</description>
|
||||
|
||||
<properties>
|
||||
<spring-boot.version>2.0.3.RELEASE</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-2-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>spring-boot-container-bundle</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-security-adapter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,30 +0,0 @@
|
|||
<?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/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>keycloak-misc-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-starter-parent</artifactId>
|
||||
<name>Keycloak :: Spring :: Boot</name>
|
||||
<description>Support for using Keycloak in Spring Boot applications.</description>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>keycloak-spring-boot-starter</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak.bom</groupId>
|
||||
<artifactId>keycloak-adapter-bom</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
13
pom.xml
13
pom.xml
|
@ -216,9 +216,6 @@
|
|||
<tomcat8.version>8.5.76</tomcat8.version>
|
||||
<tomcat9.version>9.0.16</tomcat9.version>
|
||||
|
||||
<!-- Spring Boot versions, used for tests -->
|
||||
<spring-boot27.version>2.7.14</spring-boot27.version>
|
||||
|
||||
<!-- webauthn support -->
|
||||
<webauthn4j.version>0.21.5.RELEASE</webauthn4j.version>
|
||||
<org.apache.kerby.kerby-asn1.version>2.0.3</org.apache.kerby.kerby-asn1.version>
|
||||
|
@ -1102,16 +1099,6 @@
|
|||
<artifactId>keycloak-js-adapter-jar</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-2-adapter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
|
||||
|
|
|
@ -339,19 +339,6 @@ that you need to use property `migration.mode` with the value `manual` .
|
|||
|
||||
-Dmigration.mode=manual
|
||||
|
||||
## Spring Boot adapter tests
|
||||
|
||||
Currently, we are testing Spring Boot with three different containers `Tomcat 8`, `Undertow` and `Jetty 9.4`.
|
||||
We are testing with Spring Boot 2.7. The version is specified in [root pom.xml](../../pom.xml) (i.e. see property `spring-boot27.version`).
|
||||
To run tests execute following command. Default version of Spring Boot is 2.7.x, there is also a profile `-Pspringboot27`.
|
||||
|
||||
```
|
||||
mvn -f testsuite/integration-arquillian/tests/other/springboot-tests/pom.xml \
|
||||
clean test \
|
||||
-Dadapter.container=[tomcat|undertow|jetty94] \
|
||||
[-Pspringboot27]
|
||||
```
|
||||
|
||||
## Disabling features
|
||||
Some features in Keycloak can be disabled. To run the testsuite with a specific feature disabled use the `auth.server.feature` system property. For example to run the tests with authorization disabled run:
|
||||
```
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
<module>servlets-jakarta</module>
|
||||
<module>app-profile-jee</module>
|
||||
<module>cors</module>
|
||||
<module>spring-boot-adapter-app</module>
|
||||
<module>fuse</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
@ -1,190 +0,0 @@
|
|||
<?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">
|
||||
<parent>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<artifactId>integration-arquillian-test-apps</artifactId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-boot-adapter-app</artifactId>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<jetty.version>${jetty94.version}</jetty.version>
|
||||
<undertow.version>${undertow-legacy.version}</undertow.version>
|
||||
|
||||
<springboot-version>2.7</springboot-version>
|
||||
|
||||
<spring-boot-adapter-jetty>false</spring-boot-adapter-jetty>
|
||||
|
||||
<app.server.debug.port>5006</app.server.debug.port>
|
||||
<app.server.debug.suspend>n</app.server.debug.suspend>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-boot-2-adapter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-common</artifactId>
|
||||
<version>2.26</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>spring.boot.2.7</id>
|
||||
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
<property>
|
||||
<name>springboot-version</name>
|
||||
<value>2.7</value>
|
||||
</property>
|
||||
</activation>
|
||||
|
||||
<properties>
|
||||
<spring-boot.version>${spring-boot27.version}</spring-boot.version>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>spring-boot-adapter-tomcat</id>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>${spring.boot.tomcat.adapter.artifactId}</artifactId>
|
||||
<version>${spring.boot.adapter.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>spring-boot-adapter-undertow</id>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-undertow-adapter</artifactId>
|
||||
<version>${spring.boot.adapter.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>spring-boot-adapter-jetty94</id>
|
||||
|
||||
<properties>
|
||||
<jetty.version>${jetty94.version}</jetty.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jetty94-adapter</artifactId>
|
||||
<version>${spring.boot.adapter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jetty</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<configuration>
|
||||
<jvmArguments>
|
||||
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=${app.server.debug.suspend},address=${app.server.debug.port}
|
||||
</jvmArguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>${spring-boot27.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-bom</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
</project>
|
|
@ -1,141 +0,0 @@
|
|||
package org.keycloak;
|
||||
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Controller
|
||||
@RequestMapping(path = "/admin")
|
||||
public class AdminController {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(AdminController.class);
|
||||
|
||||
@RequestMapping(path = "/TokenServlet", method = RequestMethod.GET)
|
||||
public String showTokens(WebRequest req, Model model, @RequestParam Map<String, String> attributes) throws IOException {
|
||||
String timeOffset = attributes.get("timeOffset");
|
||||
if (!StringUtils.isEmpty(timeOffset)) {
|
||||
int offset;
|
||||
try {
|
||||
offset = Integer.parseInt(timeOffset, 10);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
Time.setOffset(offset);
|
||||
}
|
||||
|
||||
RefreshableKeycloakSecurityContext ctx =
|
||||
(RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName(), WebRequest.SCOPE_REQUEST);
|
||||
String accessTokenPretty = JsonSerialization.writeValueAsPrettyString(ctx.getToken());
|
||||
RefreshToken refreshToken;
|
||||
try {
|
||||
refreshToken = new JWSInput(ctx.getRefreshToken()).readJsonContent(RefreshToken.class);
|
||||
} catch (JWSInputException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
String refreshTokenPretty = JsonSerialization.writeValueAsPrettyString(refreshToken);
|
||||
|
||||
model.addAttribute("accessToken", accessTokenPretty);
|
||||
model.addAttribute("refreshToken", refreshTokenPretty);
|
||||
model.addAttribute("accessTokenString", ctx.getTokenString());
|
||||
|
||||
return "tokens";
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/SessionServlet", method = RequestMethod.GET)
|
||||
public String sessionServlet(WebRequest webRequest, Model model) {
|
||||
String counterString = (String) webRequest.getAttribute("counter", RequestAttributes.SCOPE_SESSION);
|
||||
int counter = 0;
|
||||
try {
|
||||
counter = Integer.parseInt(counterString, 10);
|
||||
}
|
||||
catch (NumberFormatException ignored) {
|
||||
}
|
||||
|
||||
model.addAttribute("counter", counter);
|
||||
|
||||
webRequest.setAttribute("counter", Integer.toString(counter+1), RequestAttributes.SCOPE_SESSION);
|
||||
|
||||
return "session";
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/LinkServlet", method = RequestMethod.GET)
|
||||
public String tokenController(WebRequest webRequest,
|
||||
@RequestParam Map<String, String> attributes,
|
||||
Model model) {
|
||||
|
||||
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
|
||||
HttpSession httpSession = attr.getRequest().getSession(true);
|
||||
|
||||
// response.addHeader("Cache-Control", "no-cache");
|
||||
|
||||
String responseAttr = attributes.get("response");
|
||||
|
||||
if (StringUtils.isEmpty(responseAttr)) {
|
||||
String provider = attributes.get("provider");
|
||||
String realm = attributes.get("realm");
|
||||
KeycloakSecurityContext keycloakSession =
|
||||
(KeycloakSecurityContext) webRequest.getAttribute(
|
||||
KeycloakSecurityContext.class.getName(),
|
||||
RequestAttributes.SCOPE_REQUEST);
|
||||
AccessToken token = keycloakSession.getToken();
|
||||
String clientId = token.getIssuedFor();
|
||||
String nonce = UUID.randomUUID().toString();
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
String input = nonce + token.getSessionState() + clientId + provider;
|
||||
byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
|
||||
String hash = Base64Url.encode(check);
|
||||
httpSession.setAttribute("hash", hash);
|
||||
String redirectUri = KeycloakUriBuilder.fromUri("http://localhost:8280/admin/LinkServlet")
|
||||
.queryParam("response", "true").build().toString();
|
||||
String accountLinkUrl = KeycloakUriBuilder.fromUri("http://localhost:8180/")
|
||||
.path("/auth/realms/{realm}/broker/{provider}/link")
|
||||
.queryParam("nonce", nonce)
|
||||
.queryParam("hash", hash)
|
||||
.queryParam("client_id", token.getIssuedFor())
|
||||
.queryParam("redirect_uri", redirectUri).build(realm, provider).toString();
|
||||
|
||||
return "redirect:" + accountLinkUrl;
|
||||
} else {
|
||||
String error = attributes.get("link_error");
|
||||
if (StringUtils.isEmpty(error))
|
||||
model.addAttribute("error", "Account linked");
|
||||
else
|
||||
model.addAttribute("error", error);
|
||||
|
||||
return "linking";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package org.keycloak;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SpringBootAdapterApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootAdapterApplication.class, args);
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
server.port=8280
|
||||
|
||||
keycloak.realm=test
|
||||
keycloak.auth-server-url=http://localhost:8180/auth
|
||||
keycloak.ssl-required=external
|
||||
keycloak.resource=spring-boot-app
|
||||
keycloak.credentials.secret=e3789ac5-bde6-4957-a7b0-612823dac101
|
||||
keycloak.realm-key=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB
|
||||
|
||||
keycloak.security-constraints[0].authRoles[0]=admin
|
||||
keycloak.security-constraints[0].securityCollections[0].name=Admin zone
|
||||
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/admin/*
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>springboot admin page</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="test">You are now admin</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>springboot test page</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="test">Click <a href="admin/index.html" class="adminlink">here</a> to go admin</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org/">
|
||||
<head>
|
||||
<title>Linking page result</title>
|
||||
</head>
|
||||
<body>
|
||||
<span id="error" th:text="${error}"/>
|
||||
</body>
|
||||
</html>
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html xmlns:th="http://www.thymeleaf.org/">
|
||||
<head>
|
||||
<title>session counter page</title>
|
||||
</head>
|
||||
<body>
|
||||
<span id="counter" th:text="${counter}"></span>
|
||||
</body>
|
||||
</html>
|
|
@ -1,11 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html xmlns:th="http://www.thymeleaf.org/">
|
||||
<head>
|
||||
<title>Tokens from spring boot</title>
|
||||
</head>
|
||||
<body>
|
||||
<span id="accessToken" th:text="${accessToken}"></span>
|
||||
<span id="refreshToken" th:text="${refreshToken}"></span>
|
||||
<span id="accessTokenString" th:text="${accessTokenString}"></span>
|
||||
</body>
|
||||
</html>
|
|
@ -142,12 +142,6 @@
|
|||
<module>sssd</module>
|
||||
</modules>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>springboot</id>
|
||||
<modules>
|
||||
<module>springboot-tests</module>
|
||||
</modules>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>webauthn</id>
|
||||
<modules>
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
<?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">
|
||||
<parent>
|
||||
<artifactId>integration-arquillian-tests-other</artifactId>
|
||||
<groupId>org.keycloak.testsuite</groupId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>integration-arquillian-tests-springboot</artifactId>
|
||||
|
||||
<properties>
|
||||
<adapter.container>tomcat</adapter.container>
|
||||
|
||||
<maven.settings.file/>
|
||||
<springboot.version.option>2.7</springboot.version.option>
|
||||
<spring.boot.adapter.version>${project.version}</spring.boot.adapter.version>
|
||||
|
||||
<app.server.debug.port>5006</app.server.debug.port>
|
||||
<app.server.debug.suspend>n</app.server.debug.suspend>
|
||||
<maven.binary.path>mvn</maven.binary.path>
|
||||
<!-- For now springboot doesn't work with auth server ssl enabled -->
|
||||
<auth.server.ssl.required>false</auth.server.ssl.required>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-test-helper</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-common</artifactId>
|
||||
<version>2.26</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.core</groupId>
|
||||
<artifactId>jersey-client</artifactId>
|
||||
<version>2.26</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.inject</groupId>
|
||||
<artifactId>jersey-hk2</artifactId>
|
||||
<version>2.26</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>test-springboot</id>
|
||||
|
||||
<activation>
|
||||
<property>
|
||||
<name>!settings.path</name>
|
||||
</property>
|
||||
</activation>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.bazaarvoice.maven.plugins</groupId>
|
||||
<artifactId>process-exec-maven-plugin</artifactId>
|
||||
<version>0.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- This step is necessary to correctly initialize spring-boot-app for current scenario -->
|
||||
<id>spring-boot-clean-install</id>
|
||||
<phase>generate-test-resources</phase>
|
||||
<goals>
|
||||
<goal>start</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<name>springboot-initialize</name>
|
||||
<workingDir>../../../../test-apps/spring-boot-adapter-app</workingDir>
|
||||
<waitAfterLaunch>0</waitAfterLaunch>
|
||||
<arguments>
|
||||
<argument>${maven.binary.path}</argument>
|
||||
<argument>clean</argument>
|
||||
<argument>install</argument>
|
||||
<argument>-B</argument>
|
||||
<argument>-Pspring-boot-adapter-${adapter.container}</argument>
|
||||
<argument>-Dmaven.repo.local=${settings.localRepository}</argument>
|
||||
<argument>-Dspringboot-version=${springboot.version.option}</argument>
|
||||
<argument>-Dspring.boot.adapter.version=${spring.boot.adapter.version}</argument>
|
||||
<argument>-Dinsecure.repositories=WARN</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>spring-boot-application-process</id>
|
||||
<phase>process-test-resources</phase>
|
||||
<goals>
|
||||
<goal>start</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<name>springboot</name>
|
||||
<workingDir>../../../../test-apps/spring-boot-adapter-app</workingDir>
|
||||
<healthcheckUrl>http://localhost:8280/index.html</healthcheckUrl>
|
||||
<waitAfterLaunch>900</waitAfterLaunch>
|
||||
<arguments>
|
||||
<argument>${maven.binary.path}</argument>
|
||||
<argument>spring-boot:run</argument>
|
||||
<argument>-B</argument>
|
||||
<argument>-Pspring-boot-adapter-${adapter.container}</argument>
|
||||
<argument>-Dmaven.repo.local=${settings.localRepository}</argument>
|
||||
<argument>-Dspringboot-version=${springboot.version.option}</argument>
|
||||
<argument>-Dspring.boot.adapter.version=${spring.boot.adapter.version}</argument>
|
||||
<argument>-Dapp.server.debug.port=${app.server.debug.port}</argument>
|
||||
<argument>-Dapp.server.debug.suspend=${app.server.debug.suspend}</argument>
|
||||
<argument>-Dinsecure.repositories=WARN</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>kill-processes</id>
|
||||
<phase>post-integration-test</phase>
|
||||
<goals>
|
||||
<goal>stop-all</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>test-springboot-with-settings</id>
|
||||
|
||||
<activation>
|
||||
<property>
|
||||
<name>settings.path</name>
|
||||
</property>
|
||||
</activation>
|
||||
|
||||
<properties>
|
||||
<repo.argument>-Dno-repo</repo.argument>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.bazaarvoice.maven.plugins</groupId>
|
||||
<artifactId>process-exec-maven-plugin</artifactId>
|
||||
<version>0.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- This step is necessary to correctly initialize spring-boot-app for current scenario -->
|
||||
<id>spring-boot-clean-install</id>
|
||||
<phase>generate-test-resources</phase>
|
||||
<goals>
|
||||
<goal>start</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<name>springboot-initialize</name>
|
||||
<workingDir>../../../../test-apps/spring-boot-adapter-app</workingDir>
|
||||
<waitAfterLaunch>0</waitAfterLaunch>
|
||||
<arguments>
|
||||
<argument>${maven.binary.path}</argument>
|
||||
<argument>clean</argument>
|
||||
<argument>install</argument>
|
||||
<argument>-s</argument>
|
||||
<argument>${settings.path}</argument>
|
||||
<argument>-B</argument>
|
||||
<argument>-Pspring-boot-adapter-${adapter.container}</argument>
|
||||
<argument>-Dmaven.repo.local=${settings.localRepository}</argument>
|
||||
<argument>-Dspringboot-version=${springboot.version.option}</argument>
|
||||
<argument>-Dspring.boot.adapter.version=${spring.boot.adapter.version}</argument>
|
||||
<argument>-Dinsecure.repositories=WARN</argument>
|
||||
<argument>${repo.argument}</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>spring-boot-application-process</id>
|
||||
<phase>process-test-resources</phase>
|
||||
<goals>
|
||||
<goal>start</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<name>springboot</name>
|
||||
<workingDir>../../../../test-apps/spring-boot-adapter-app</workingDir>
|
||||
<healthcheckUrl>http://localhost:8280/index.html</healthcheckUrl>
|
||||
<waitAfterLaunch>900</waitAfterLaunch>
|
||||
<arguments>
|
||||
<argument>${maven.binary.path}</argument>
|
||||
<argument>spring-boot:run</argument>
|
||||
<argument>-B</argument>
|
||||
<argument>-s</argument>
|
||||
<argument>${settings.path}</argument>
|
||||
<argument>-Pspring-boot-adapter-${adapter.container}</argument>
|
||||
<argument>-Dmaven.repo.local=${settings.localRepository}</argument>
|
||||
<argument>-Dspringboot-version=${springboot.version.option}</argument>
|
||||
<argument>-Dspring.boot.adapter.version=${spring.boot.adapter.version}</argument>
|
||||
<argument>-Dapp.server.debug.port=${app.server.debug.port}</argument>
|
||||
<argument>-Dapp.server.debug.suspend=${app.server.debug.suspend}</argument>
|
||||
<argument>-Dinsecure.repositories=WARN</argument>
|
||||
<argument>${repo.argument}</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>kill-processes</id>
|
||||
<phase>post-integration-test</phase>
|
||||
<goals>
|
||||
<goal>stop-all</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>springboot27</id>
|
||||
<properties>
|
||||
<springboot.version.option>2.7</springboot.version.option>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>turn-on-repo-url</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>repo.url</name>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<repo.argument>-Drepo.url=${repo.url}</repo.argument>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
|
||||
</project>
|
|
@ -1,30 +0,0 @@
|
|||
package org.keycloak.testsuite.springboot;
|
||||
|
||||
import org.keycloak.testsuite.pages.AbstractPage;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
|
||||
public abstract class AbstractSpringbootPage extends AbstractPage {
|
||||
|
||||
protected String title;
|
||||
|
||||
public AbstractSpringbootPage(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public void assertIsCurrent() {
|
||||
assertThat(driver.getTitle().toLowerCase(), is(equalTo(title.toLowerCase())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return driver.getTitle().equalsIgnoreCase(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open() throws Exception {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package org.keycloak.testsuite.springboot;
|
||||
|
||||
import org.keycloak.testsuite.pages.AbstractPage;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
public class LinkingPage extends AbstractSpringbootPage {
|
||||
|
||||
public static final String PAGE_TITLE = "linking page result";
|
||||
|
||||
public LinkingPage() {
|
||||
super(PAGE_TITLE);
|
||||
}
|
||||
|
||||
@FindBy(id = "error")
|
||||
private WebElement errorMessage;
|
||||
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage.getText();
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package org.keycloak.testsuite.springboot;
|
||||
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.keycloak.testsuite.pages.AbstractPage;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
public class SessionPage extends AbstractSpringbootPage {
|
||||
|
||||
@FindBy(id = "counter")
|
||||
private WebElement counterElement;
|
||||
|
||||
public static final String PAGE_TITLE = "session counter page";
|
||||
|
||||
public SessionPage() {
|
||||
super(PAGE_TITLE);
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
String counterString = counterElement.getText();
|
||||
|
||||
return NumberUtils.toInt(counterString, 0);
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package org.keycloak.testsuite.springboot;
|
||||
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
public class SpringAdminPage extends AbstractSpringbootPage {
|
||||
|
||||
@FindBy(className = "test")
|
||||
private WebElement testDiv;
|
||||
|
||||
public static final String PAGE_TITLE = "springboot admin page";
|
||||
|
||||
public SpringAdminPage() {
|
||||
super(PAGE_TITLE);
|
||||
}
|
||||
|
||||
public String getTestDivString() {
|
||||
return testDiv.getText();
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package org.keycloak.testsuite.springboot;
|
||||
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
|
||||
public class SpringApplicationPage extends AbstractSpringbootPage {
|
||||
|
||||
@FindBy(className = "test")
|
||||
private WebElement testDiv;
|
||||
|
||||
@FindBy(className = "adminlink")
|
||||
private WebElement adminLink;
|
||||
|
||||
public static final String PAGE_TITLE = "springboot test page";
|
||||
|
||||
public SpringApplicationPage() {
|
||||
super(PAGE_TITLE);
|
||||
}
|
||||
|
||||
public void goAdmin() {
|
||||
clickLink(adminLink);
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package org.keycloak.testsuite.springboot;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.keycloak.testsuite.adapter.page.AbstractShowTokensPage;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class TokenPage extends AbstractShowTokensPage {
|
||||
|
||||
public static final String PAGE_TITLE = "tokens from spring boot";
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return driver.getTitle().equalsIgnoreCase(PAGE_TITLE.toLowerCase());
|
||||
}
|
||||
|
||||
public void assertIsCurrent() {
|
||||
assertThat(driver.getTitle().toLowerCase(), is(equalTo(PAGE_TITLE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getInjectedUrl() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,228 +0,0 @@
|
|||
package org.keycloak.testsuite.springboot;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.assignRealmRoles;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.RoleResource;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.SuiteContext;
|
||||
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
|
||||
import org.keycloak.testsuite.pages.InfoPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.LogoutConfirmPage;
|
||||
import org.keycloak.testsuite.util.DroneUtils;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
public abstract class AbstractSpringBootTest extends AbstractKeycloakTest {
|
||||
|
||||
static final String REALM_ID = "cd8ee421-5100-41ba-95dd-b27c8e5cf042";
|
||||
|
||||
static final String REALM_NAME = "test";
|
||||
|
||||
static final String CLIENT_ID = "spring-boot-app";
|
||||
static final String SECRET = "e3789ac5-bde6-4957-a7b0-612823dac101";
|
||||
|
||||
static final String APPLICATION_URL = "http://localhost:8280";
|
||||
static final String BASE_URL = APPLICATION_URL + "/admin";
|
||||
|
||||
static final String USER_LOGIN = "testuser";
|
||||
static final String USER_EMAIL = "user@email.test";
|
||||
static final String USER_PASSWORD = "user-password";
|
||||
|
||||
static final String CORRECT_ROLE = "admin";
|
||||
|
||||
static final String REALM_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5" +
|
||||
"mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi7" +
|
||||
"9NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
|
||||
|
||||
static final String REALM_PRIVATE_KEY = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3Bj" +
|
||||
"LGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vj" +
|
||||
"O2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jY" +
|
||||
"lQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn" +
|
||||
"9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEK" +
|
||||
"Xalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2w" +
|
||||
"Vl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJ" +
|
||||
"AY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZ" +
|
||||
"N39fOYAlo+nTixgeW7X8Y=";
|
||||
|
||||
@Page
|
||||
LoginPage loginPage;
|
||||
|
||||
@Page
|
||||
protected OIDCLogin testRealmLoginPage;
|
||||
|
||||
@Page
|
||||
protected LogoutConfirmPage logoutConfirmPage;
|
||||
|
||||
@Page
|
||||
protected InfoPage infoPage;
|
||||
|
||||
@Page
|
||||
SpringApplicationPage applicationPage;
|
||||
|
||||
@Page
|
||||
SpringAdminPage adminPage;
|
||||
|
||||
@Page
|
||||
TokenPage tokenPage;
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = new RealmRepresentation();
|
||||
|
||||
realm.setId(REALM_ID);
|
||||
realm.setRealm(REALM_NAME);
|
||||
realm.setEnabled(true);
|
||||
|
||||
realm.setPublicKey(REALM_PUBLIC_KEY);
|
||||
realm.setPrivateKey(REALM_PRIVATE_KEY);
|
||||
|
||||
realm.setClients(Collections.singletonList(createClient()));
|
||||
|
||||
List<String> eventListeners = new ArrayList<>();
|
||||
eventListeners.add("jboss-logging");
|
||||
eventListeners.add("event-queue");
|
||||
realm.setEventsListeners(eventListeners);
|
||||
|
||||
testRealms.add(realm);
|
||||
}
|
||||
|
||||
private ClientRepresentation createClient() {
|
||||
ClientRepresentation clientRepresentation = new ClientRepresentation();
|
||||
|
||||
clientRepresentation.setId(CLIENT_ID);
|
||||
clientRepresentation.setSecret(SECRET);
|
||||
|
||||
clientRepresentation.setBaseUrl(BASE_URL);
|
||||
clientRepresentation.setRedirectUris(Collections.singletonList(BASE_URL + "/*"));
|
||||
clientRepresentation.setAdminUrl(BASE_URL);
|
||||
|
||||
return clientRepresentation;
|
||||
}
|
||||
|
||||
void addUser(String login, String email, String password, String... roles) {
|
||||
UserRepresentation userRepresentation = new UserRepresentation();
|
||||
|
||||
userRepresentation.setUsername(login);
|
||||
userRepresentation.setEmail(email);
|
||||
userRepresentation.setEmailVerified(true);
|
||||
userRepresentation.setEnabled(true);
|
||||
|
||||
RealmResource realmResource = adminClient.realm(REALM_NAME);
|
||||
String userId = createUserWithAdminClient(realmResource, userRepresentation);
|
||||
|
||||
resetUserPassword(realmResource.users().get(userId), password, false);
|
||||
|
||||
for (String role : roles)
|
||||
assignRealmRoles(realmResource, userId, role);
|
||||
}
|
||||
|
||||
private String getAuthRoot(SuiteContext suiteContext) {
|
||||
return suiteContext.getAuthServerInfo().getContextRoot().toString();
|
||||
}
|
||||
|
||||
private String encodeUrl(String url) {
|
||||
String result;
|
||||
try {
|
||||
result = URLEncoder.encode(url, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
result = url;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
String getLogoutUrl() {
|
||||
return getAuthRoot(suiteContext)
|
||||
+ "/auth/realms/" + REALM_NAME
|
||||
+ "/protocol/" + "openid-connect"
|
||||
+ "/logout";
|
||||
}
|
||||
|
||||
void logout(String redirectUrl) {
|
||||
String logoutUrl = getLogoutUrl();
|
||||
driver.navigate().to(logoutUrl);
|
||||
|
||||
logoutConfirmPage.assertCurrent();
|
||||
logoutConfirmPage.confirmLogout();
|
||||
infoPage.assertCurrent();
|
||||
|
||||
driver.navigate().to(redirectUrl);
|
||||
}
|
||||
|
||||
void setAdapterAndServerTimeOffset(int timeOffset, String url) {
|
||||
setTimeOffset(timeOffset);
|
||||
|
||||
String timeOffsetUri = UriBuilder.fromUri(url)
|
||||
.queryParam("timeOffset", timeOffset)
|
||||
.build().toString();
|
||||
|
||||
driver.navigate().to(timeOffsetUri);
|
||||
WaitUtils.waitUntilElement(By.tagName("body")).is().visible();
|
||||
}
|
||||
|
||||
public void createRoles() {
|
||||
RealmResource realm = realmsResouce().realm(REALM_NAME);
|
||||
|
||||
RoleRepresentation correct = new RoleRepresentation(CORRECT_ROLE, CORRECT_ROLE, false);
|
||||
realm.roles().create(correct);
|
||||
}
|
||||
|
||||
public void addUsers() {
|
||||
addUser(USER_LOGIN, USER_EMAIL, USER_PASSWORD, CORRECT_ROLE);
|
||||
}
|
||||
|
||||
public void cleanupUsers() {
|
||||
RealmResource realmResource = adminClient.realm(REALM_NAME);
|
||||
UserRepresentation userRep = ApiUtil.findUserByUsername(realmResource, USER_LOGIN);
|
||||
if (userRep != null) {
|
||||
realmResource.users().get(userRep.getId()).remove();
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanupRoles() {
|
||||
RealmResource realm = realmsResouce().realm(REALM_NAME);
|
||||
|
||||
RoleResource correctRole = realm.roles().get(CORRECT_ROLE);
|
||||
correctRole.remove();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
createRoles();
|
||||
addUsers();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
cleanupUsers();
|
||||
cleanupRoles();
|
||||
}
|
||||
}
|
|
@ -1,585 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.springboot;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.Ignore;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.idm.*;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
import org.keycloak.testsuite.ActionURIUtils;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
||||
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
||||
import org.keycloak.testsuite.broker.BrokerTestTools;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.CoreMatchers.startsWith;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
|
||||
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT_LINKS;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
|
||||
import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||
|
||||
public class AccountLinkSpringBootTest extends AbstractSpringBootTest {
|
||||
|
||||
private static final String PARENT_REALM = "parent-realm";
|
||||
|
||||
private static final String LINKING_URL = BASE_URL + "/LinkServlet";
|
||||
|
||||
private static final String PARENT_USERNAME = "parent-username";
|
||||
private static final String PARENT_PASSWORD = "parent-password";
|
||||
|
||||
private static final String CHILD_USERNAME_1 = "child-username-1";
|
||||
private static final String CHILD_PASSWORD_1 = "child-password-1";
|
||||
|
||||
private static final String CHILD_USERNAME_2 = "child-username-2";
|
||||
private static final String CHILD_PASSWORD_2 = "child-password-2";
|
||||
|
||||
@Page
|
||||
private LinkingPage linkingPage;
|
||||
|
||||
@Page
|
||||
private LoginUpdateProfilePage loginUpdateProfilePage;
|
||||
|
||||
@Page
|
||||
private ErrorPage errorPage;
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = new RealmRepresentation();
|
||||
realm.setRealm(REALM_NAME);
|
||||
realm.setEnabled(true);
|
||||
realm.setPublicKey(REALM_PUBLIC_KEY);
|
||||
realm.setPrivateKey(REALM_PRIVATE_KEY);
|
||||
realm.setAccessTokenLifespan(600);
|
||||
realm.setAccessCodeLifespan(10);
|
||||
realm.setAccessCodeLifespanUserAction(6000);
|
||||
realm.setSslRequired("external");
|
||||
ClientRepresentation servlet = new ClientRepresentation();
|
||||
servlet.setClientId(CLIENT_ID);
|
||||
servlet.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
servlet.setAdminUrl(LINKING_URL);
|
||||
servlet.setDirectAccessGrantsEnabled(true);
|
||||
servlet.setBaseUrl(LINKING_URL);
|
||||
servlet.setRedirectUris(new LinkedList<>());
|
||||
servlet.getRedirectUris().add(LINKING_URL + "/*");
|
||||
servlet.setSecret(SECRET);
|
||||
servlet.setFullScopeAllowed(true);
|
||||
realm.setClients(new LinkedList<>());
|
||||
realm.getClients().add(servlet);
|
||||
testRealms.add(realm);
|
||||
|
||||
realm = new RealmRepresentation();
|
||||
realm.setRealm(PARENT_REALM);
|
||||
realm.setEnabled(true);
|
||||
|
||||
testRealms.add(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUsers() {
|
||||
addIdpUser();
|
||||
addChildUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupUsers() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createRoles() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isImportAfterEachMethod() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addIdpUser() {
|
||||
RealmResource realm = adminClient.realms().realm(PARENT_REALM);
|
||||
UserRepresentation user = new UserRepresentation();
|
||||
user.setUsername(PARENT_USERNAME);
|
||||
user.setEnabled(true);
|
||||
createUserAndResetPasswordWithAdminClient(realm, user, PARENT_PASSWORD);
|
||||
}
|
||||
|
||||
private String childUserId = null;
|
||||
|
||||
public void addChildUser() {
|
||||
RealmResource realm = adminClient.realms().realm(REALM_NAME);
|
||||
UserRepresentation user = new UserRepresentation();
|
||||
user.setUsername(CHILD_USERNAME_1);
|
||||
user.setEnabled(true);
|
||||
childUserId = createUserAndResetPasswordWithAdminClient(realm, user, CHILD_PASSWORD_1);
|
||||
UserRepresentation user2 = new UserRepresentation();
|
||||
user2.setUsername(CHILD_USERNAME_2);
|
||||
user2.setEnabled(true);
|
||||
String user2Id = createUserAndResetPasswordWithAdminClient(realm, user2, CHILD_PASSWORD_2);
|
||||
|
||||
// have to add a role as undertow default auth manager doesn't like "*". todo we can remove this eventually as undertow fixes this in later versions
|
||||
realm.roles().create(new RoleRepresentation(CORRECT_ROLE, null, false));
|
||||
RoleRepresentation role = realm.roles().get(CORRECT_ROLE).toRepresentation();
|
||||
List<RoleRepresentation> roles = new LinkedList<>();
|
||||
roles.add(role);
|
||||
realm.users().get(childUserId).roles().realmLevel().add(roles);
|
||||
realm.users().get(user2Id).roles().realmLevel().add(roles);
|
||||
ClientRepresentation brokerService = realm.clients().findByClientId(Constants.BROKER_SERVICE_CLIENT_ID).get(0);
|
||||
role = realm.clients().get(brokerService.getId()).roles().get(Constants.READ_TOKEN_ROLE).toRepresentation();
|
||||
roles.clear();
|
||||
roles.add(role);
|
||||
realm.users().get(childUserId).roles().clientLevel(brokerService.getId()).add(roles);
|
||||
realm.users().get(user2Id).roles().clientLevel(brokerService.getId()).add(roles);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void createParentChild() {
|
||||
BrokerTestTools.createKcOidcBroker(adminClient, REALM_NAME, PARENT_REALM);
|
||||
|
||||
testRealmLoginPage.setAuthRealm(REALM_NAME);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testErrorConditions() throws Exception {
|
||||
RealmResource realm = adminClient.realms().realm(REALM_NAME);
|
||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
ClientRepresentation client = adminClient.realms().realm(REALM_NAME).clients().findByClientId(CLIENT_ID).get(0);
|
||||
|
||||
UriBuilder redirectUri = UriBuilder.fromUri(LINKING_URL).queryParam("response", "true");
|
||||
|
||||
UriBuilder directLinking = UriBuilder.fromUri(getAuthServerContextRoot() + "/auth")
|
||||
.path("realms/{child-realm}/broker/{provider}/link")
|
||||
.queryParam("client_id", CLIENT_ID)
|
||||
.queryParam("redirect_uri", redirectUri.build())
|
||||
.queryParam("hash", Base64Url.encode("crap".getBytes()))
|
||||
.queryParam("nonce", UUID.randomUUID().toString());
|
||||
|
||||
String linkUrl = directLinking
|
||||
.build(REALM_NAME, PARENT_REALM).toString();
|
||||
|
||||
// test that child user cannot log into parent realm
|
||||
navigateTo(linkUrl);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
|
||||
assertThat(driver.getCurrentUrl(), containsString("link_error=not_logged_in"));
|
||||
|
||||
logoutAll();
|
||||
|
||||
// now log in
|
||||
navigateTo(LINKING_URL + "?response=true");
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
|
||||
|
||||
linkingPage.assertIsCurrent();
|
||||
|
||||
assertThat(linkingPage.getErrorMessage().toLowerCase(), containsString("account linked"));
|
||||
|
||||
// now test CSRF with bad hash.
|
||||
navigateTo(linkUrl);
|
||||
|
||||
assertThat(driver.getPageSource(), containsString("We are sorry..."));
|
||||
|
||||
logoutAll();
|
||||
|
||||
// now log in again with client that does not have scope
|
||||
|
||||
String accountId = adminClient.realms().realm(REALM_NAME).clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0).getId();
|
||||
RoleRepresentation manageAccount = adminClient.realms().realm(REALM_NAME).clients().get(accountId).roles().get(MANAGE_ACCOUNT).toRepresentation();
|
||||
RoleRepresentation manageLinks = adminClient.realms().realm(REALM_NAME).clients().get(accountId).roles().get(MANAGE_ACCOUNT_LINKS).toRepresentation();
|
||||
RoleRepresentation userRole = adminClient.realms().realm(REALM_NAME).roles().get(CORRECT_ROLE).toRepresentation();
|
||||
|
||||
client.setFullScopeAllowed(false);
|
||||
ClientResource clientResource = adminClient.realms().realm(REALM_NAME).clients().get(client.getId());
|
||||
clientResource.update(client);
|
||||
|
||||
List<RoleRepresentation> roles = new LinkedList<>();
|
||||
roles.add(userRole);
|
||||
clientResource.getScopeMappings().realmLevel().add(roles);
|
||||
|
||||
navigateTo(LINKING_URL + "?response=true");
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
|
||||
|
||||
linkingPage.assertIsCurrent();
|
||||
assertThat(linkingPage.getErrorMessage().toLowerCase(), containsString("account linked"));
|
||||
|
||||
UriBuilder linkBuilder = UriBuilder.fromUri(LINKING_URL);
|
||||
String clientLinkUrl = linkBuilder.clone()
|
||||
.queryParam("realm", REALM_NAME)
|
||||
.queryParam("provider", PARENT_REALM).build().toString();
|
||||
|
||||
navigateTo(clientLinkUrl);
|
||||
assertThat(driver.getCurrentUrl(), containsString("error=not_allowed"));
|
||||
|
||||
logoutAll();
|
||||
|
||||
// add MANAGE_ACCOUNT_LINKS scope should pass.
|
||||
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
roles = new LinkedList<>();
|
||||
roles.add(manageLinks);
|
||||
clientResource.getScopeMappings().clientLevel(accountId).add(roles);
|
||||
|
||||
navigateTo(clientLinkUrl);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
|
||||
|
||||
testRealmLoginPage.setAuthRealm(PARENT_REALM);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(PARENT_USERNAME, PARENT_PASSWORD);
|
||||
|
||||
testRealmLoginPage.setAuthRealm(REALM_NAME); // clean
|
||||
|
||||
assertThat(driver.getCurrentUrl(), startsWith(linkBuilder.toTemplate()));
|
||||
assertThat(driver.getPageSource(), containsString("Account linked"));
|
||||
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(not(empty())));
|
||||
|
||||
realm.users().get(childUserId).removeFederatedIdentity(PARENT_REALM);
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
clientResource.getScopeMappings().clientLevel(accountId).remove(roles);
|
||||
|
||||
logoutAll();
|
||||
|
||||
navigateTo(clientLinkUrl);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
|
||||
|
||||
assertThat(driver.getCurrentUrl(), containsString("link_error=not_allowed"));
|
||||
|
||||
logoutAll();
|
||||
|
||||
// add MANAGE_ACCOUNT scope should pass
|
||||
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
roles = new LinkedList<>();
|
||||
roles.add(manageAccount);
|
||||
clientResource.getScopeMappings().clientLevel(accountId).add(roles);
|
||||
|
||||
navigateTo(clientLinkUrl);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
|
||||
|
||||
testRealmLoginPage.setAuthRealm(PARENT_REALM);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(PARENT_USERNAME, PARENT_PASSWORD);
|
||||
|
||||
testRealmLoginPage.setAuthRealm(REALM_NAME); // clean
|
||||
|
||||
|
||||
assertThat(driver.getCurrentUrl(), startsWith(linkBuilder.toTemplate()));
|
||||
assertThat(driver.getPageSource(), containsString("Account linked"));
|
||||
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(not(empty())));
|
||||
|
||||
realm.users().get(childUserId).removeFederatedIdentity(PARENT_REALM);
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
clientResource.getScopeMappings().clientLevel(accountId).remove(roles);
|
||||
|
||||
logoutAll();
|
||||
|
||||
navigateTo(clientLinkUrl);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
|
||||
|
||||
assertThat(driver.getCurrentUrl(), containsString("link_error=not_allowed"));
|
||||
|
||||
logoutAll();
|
||||
|
||||
// undo fullScopeAllowed
|
||||
|
||||
client = adminClient.realms().realm(REALM_NAME).clients().findByClientId(CLIENT_ID).get(0);
|
||||
client.setFullScopeAllowed(true);
|
||||
clientResource.update(client);
|
||||
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
logoutAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccountLink() throws Exception {
|
||||
RealmResource realm = adminClient.realms().realm(REALM_NAME);
|
||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
UriBuilder linkBuilder = UriBuilder.fromUri(LINKING_URL);
|
||||
String linkUrl = linkBuilder.clone()
|
||||
.queryParam("realm", REALM_NAME)
|
||||
.queryParam("provider", PARENT_REALM).build().toString();
|
||||
log.info("linkUrl: " + linkUrl);
|
||||
navigateTo(linkUrl);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
assertThat(driver.getPageSource(), containsString(PARENT_REALM));
|
||||
testRealmLoginPage.form().login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
|
||||
|
||||
testRealmLoginPage.setAuthRealm(PARENT_REALM);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(PARENT_USERNAME, PARENT_PASSWORD);
|
||||
testRealmLoginPage.setAuthRealm(REALM_NAME); // clean
|
||||
|
||||
log.info("After linking: " + driver.getCurrentUrl());
|
||||
log.info(driver.getPageSource());
|
||||
|
||||
assertThat(driver.getCurrentUrl(), startsWith(linkBuilder.toTemplate()));
|
||||
assertThat(driver.getPageSource(), containsString("Account linked"));
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(
|
||||
REALM_NAME,
|
||||
CHILD_USERNAME_1,
|
||||
CHILD_PASSWORD_1,
|
||||
null,
|
||||
CLIENT_ID,
|
||||
SECRET);
|
||||
|
||||
assertThat(response.getAccessToken(), is(notNullValue()));
|
||||
assertThat(response.getError(), is(nullValue()));
|
||||
|
||||
|
||||
Client httpClient = ClientBuilder.newClient();
|
||||
String firstToken = getToken(response, httpClient);
|
||||
assertThat(firstToken, is(notNullValue()));
|
||||
|
||||
navigateTo(linkUrl);
|
||||
assertThat(driver.getPageSource(), containsString("Account linked"));
|
||||
|
||||
String nextToken = getToken(response, httpClient);
|
||||
assertThat(nextToken, is(notNullValue()));
|
||||
assertThat(firstToken, is(not(equalTo(nextToken))));
|
||||
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(not(empty())));
|
||||
|
||||
realm.users().get(childUserId).removeFederatedIdentity(PARENT_REALM);
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
logoutAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkOnlyProvider() throws Exception {
|
||||
RealmResource realm = adminClient.realms().realm(REALM_NAME);
|
||||
IdentityProviderRepresentation rep = realm.identityProviders().get(PARENT_REALM).toRepresentation();
|
||||
rep.setLinkOnly(true);
|
||||
realm.identityProviders().get(PARENT_REALM).update(rep);
|
||||
|
||||
try {
|
||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
UriBuilder linkBuilder = UriBuilder.fromUri(LINKING_URL);
|
||||
String linkUrl = linkBuilder.clone()
|
||||
.queryParam("realm", REALM_NAME)
|
||||
.queryParam("provider", PARENT_REALM).build().toString();
|
||||
navigateTo(linkUrl);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
// should not be on login page. This is what we are testing
|
||||
assertThat(driver.getPageSource(), not(containsString(PARENT_REALM)));
|
||||
|
||||
// now test that we can still link.
|
||||
testRealmLoginPage.form().login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
|
||||
|
||||
testRealmLoginPage.setAuthRealm(PARENT_REALM);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
testRealmLoginPage.form().login(PARENT_USERNAME, PARENT_PASSWORD);
|
||||
testRealmLoginPage.setAuthRealm(REALM_NAME);
|
||||
|
||||
log.info("After linking: " + driver.getCurrentUrl());
|
||||
log.info(driver.getPageSource());
|
||||
|
||||
assertThat(driver.getCurrentUrl(), startsWith(linkBuilder.toTemplate()));
|
||||
assertThat(driver.getPageSource(), containsString("Account linked"));
|
||||
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(not(empty())));
|
||||
|
||||
realm.users().get(childUserId).removeFederatedIdentity(PARENT_REALM);
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
pause(500);
|
||||
|
||||
logoutAll();
|
||||
|
||||
log.info("testing link-only attack");
|
||||
|
||||
navigateTo(linkUrl);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
log.info("login page uri is: " + driver.getCurrentUrl());
|
||||
|
||||
// ok, now scrape the code from page
|
||||
String pageSource = driver.getPageSource();
|
||||
String action = ActionURIUtils.getActionURIFromPageSource(pageSource);
|
||||
System.out.println("action uri: " + action);
|
||||
|
||||
Map<String, String> queryParams = ActionURIUtils.parseQueryParamsFromActionURI(action);
|
||||
System.out.println("query params: " + queryParams);
|
||||
|
||||
// now try and use the code to login to remote link-only idp
|
||||
|
||||
String uri = "/auth/realms/" + REALM_NAME + "/broker/" + PARENT_REALM + "/login";
|
||||
|
||||
uri = UriBuilder.fromUri(getAuthServerContextRoot())
|
||||
.path(uri)
|
||||
.queryParam(LoginActionsService.SESSION_CODE, queryParams.get(LoginActionsService.SESSION_CODE))
|
||||
.queryParam(Constants.CLIENT_ID, queryParams.get(Constants.CLIENT_ID))
|
||||
.queryParam(Constants.TAB_ID, queryParams.get(Constants.TAB_ID))
|
||||
.build().toString();
|
||||
|
||||
log.info("hack uri: " + uri);
|
||||
|
||||
navigateTo(uri);
|
||||
|
||||
assertThat(driver.getPageSource(), containsString("Could not send authentication request to identity provider."));
|
||||
} finally {
|
||||
rep.setLinkOnly(false);
|
||||
realm.identityProviders().get(PARENT_REALM).update(rep);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testAccountLinkingExpired() throws Exception {
|
||||
RealmResource realm = adminClient.realms().realm(REALM_NAME);
|
||||
List<FederatedIdentityRepresentation> links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
// Login to account mgmt first
|
||||
//profilePage.open(REALM_NAME);
|
||||
WaitUtils.waitForPageToLoad();
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(CHILD_USERNAME_1, CHILD_PASSWORD_1);
|
||||
//profilePage.assertCurrent();
|
||||
|
||||
// Now in another tab, request account linking
|
||||
UriBuilder linkBuilder = UriBuilder.fromUri(LINKING_URL);
|
||||
String linkUrl = linkBuilder.clone()
|
||||
.queryParam("realm", REALM_NAME)
|
||||
.queryParam("provider", PARENT_REALM).build().toString();
|
||||
navigateTo(linkUrl);
|
||||
|
||||
testRealmLoginPage.setAuthRealm(PARENT_REALM);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
setTimeOffset(1); // We need to "wait" for 1 second so that notBeforePolicy invalidates token created when logging to child realm
|
||||
|
||||
// Logout "child" userSession in the meantime (for example through admin request)
|
||||
realm.logoutAll();
|
||||
|
||||
// Finish login on parent.
|
||||
testRealmLoginPage.form().login(PARENT_USERNAME, PARENT_PASSWORD);
|
||||
|
||||
|
||||
// Test I was not automatically linked
|
||||
links = realm.users().get(childUserId).getFederatedIdentity();
|
||||
assertThat(links, is(empty()));
|
||||
|
||||
errorPage.assertCurrent();
|
||||
assertThat(errorPage.getError(), is(equalTo("Requested broker account linking, but current session is no longer valid.")));
|
||||
|
||||
logoutAll();
|
||||
|
||||
navigateTo(linkUrl); // Check we are logged out
|
||||
|
||||
testRealmLoginPage.setAuthRealm(REALM_NAME);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
resetTimeOffset();
|
||||
}
|
||||
|
||||
private void navigateTo(String uri) {
|
||||
driver.navigate().to(uri);
|
||||
WaitUtils.waitForPageToLoad();
|
||||
}
|
||||
|
||||
public void logoutAll() {
|
||||
adminClient.realm(REALM_NAME).logoutAll();
|
||||
adminClient.realm(PARENT_REALM).logoutAll();
|
||||
}
|
||||
|
||||
private String getToken(OAuthClient.AccessTokenResponse response, Client httpClient) throws Exception {
|
||||
log.info("target here is " + OAuthClient.AUTH_SERVER_ROOT);
|
||||
String idpToken = httpClient.target(OAuthClient.AUTH_SERVER_ROOT)
|
||||
.path("realms")
|
||||
.path(REALM_NAME)
|
||||
.path("broker")
|
||||
.path(PARENT_REALM)
|
||||
.path("token")
|
||||
.request()
|
||||
.header("Authorization", "Bearer " + response.getAccessToken())
|
||||
.get(String.class);
|
||||
AccessTokenResponse res = JsonSerialization.readValue(idpToken, AccessTokenResponse.class);
|
||||
return res.getToken();
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
package org.keycloak.testsuite.springboot;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.RolesResource;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||
|
||||
public class BasicSpringBootTest extends AbstractSpringBootTest {
|
||||
|
||||
private static final String USER_LOGIN_2 = "testuser2";
|
||||
private static final String USER_EMAIL_2 = "user2@email.test";
|
||||
private static final String USER_PASSWORD_2 = "user2-password";
|
||||
|
||||
private static final String INCORRECT_ROLE = "wrong-admin";
|
||||
|
||||
@Before
|
||||
public void addIncorrectUser() {
|
||||
RolesResource rolesResource = adminClient.realm(REALM_NAME).roles();
|
||||
|
||||
RoleRepresentation role = new RoleRepresentation(INCORRECT_ROLE, INCORRECT_ROLE, false);
|
||||
|
||||
rolesResource.create(role);
|
||||
|
||||
addUser(USER_LOGIN_2, USER_EMAIL_2, USER_PASSWORD_2, INCORRECT_ROLE);
|
||||
|
||||
testRealmLoginPage.setAuthRealm(REALM_NAME);
|
||||
}
|
||||
|
||||
@After
|
||||
public void removeUser() {
|
||||
UserRepresentation user = ApiUtil.findUserByUsername(adminClient.realm(REALM_NAME), USER_LOGIN_2);
|
||||
|
||||
if (user != null) {
|
||||
adminClient.realm(REALM_NAME).users().delete(user.getId());
|
||||
}
|
||||
|
||||
adminClient.realm(REALM_NAME).roles().deleteRole(INCORRECT_ROLE);
|
||||
}
|
||||
|
||||
private void navigateToApplication() {
|
||||
driver.navigate().to(APPLICATION_URL + "/index.html");
|
||||
waitForPageToLoad();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectUser() {
|
||||
navigateToApplication();
|
||||
|
||||
applicationPage.assertIsCurrent();
|
||||
applicationPage.goAdmin();
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
testRealmLoginPage.form().login(USER_LOGIN, USER_PASSWORD);
|
||||
|
||||
adminPage.assertIsCurrent();
|
||||
assertThat(driver.getPageSource(), containsString("You are now admin"));
|
||||
|
||||
logout(BASE_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncorrectUser() {
|
||||
navigateToApplication();
|
||||
|
||||
applicationPage.assertIsCurrent();
|
||||
applicationPage.goAdmin();
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
testRealmLoginPage.form().login(USER_LOGIN_2, USER_PASSWORD_2);
|
||||
|
||||
assertThat(driver.getPageSource(), containsString("Forbidden"));
|
||||
|
||||
logout(BASE_URL);
|
||||
waitForPageToLoad();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncorrectCredentials() {
|
||||
navigateToApplication();
|
||||
|
||||
applicationPage.assertIsCurrent();
|
||||
applicationPage.goAdmin();
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
testRealmLoginPage.form().login(USER_LOGIN, USER_PASSWORD_2);
|
||||
|
||||
assertThat(testRealmLoginPage.feedbackMessage().isError(), is(true));
|
||||
assertThat(testRealmLoginPage.feedbackMessage().getText(), is(equalTo("Invalid username or password.")));
|
||||
}
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
package org.keycloak.testsuite.springboot;
|
||||
|
||||
import org.eclipse.persistence.annotations.BatchFetch;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
||||
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.keycloak.testsuite.util.AccountHelper;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||
|
||||
public class OfflineTokenSpringBootTest extends AbstractSpringBootTest {
|
||||
private static final String SERVLET_URL = BASE_URL + "/TokenServlet";
|
||||
private final String TEST_REALM = "test";
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
@Page
|
||||
private OAuthGrantPage oauthGrantPage;
|
||||
|
||||
@Before
|
||||
public void setUpAuthRealm() {
|
||||
testRealmLoginPage.setAuthRealm(REALM_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokens() {
|
||||
String servletUri = UriBuilder.fromUri(SERVLET_URL)
|
||||
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
|
||||
.build().toString();
|
||||
driver.navigate().to(servletUri);
|
||||
waitForPageToLoad();
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(USER_LOGIN, USER_PASSWORD);
|
||||
|
||||
tokenPage.assertIsCurrent();
|
||||
|
||||
assertThat(tokenPage.getRefreshToken().getType(), is(equalTo(TokenUtil.TOKEN_TYPE_OFFLINE)));
|
||||
assertThat(tokenPage.getRefreshToken().getExpiration(), is(equalTo(0)));
|
||||
|
||||
String accessTokenId = tokenPage.getAccessToken().getId();
|
||||
String refreshTokenId = tokenPage.getRefreshToken().getId();
|
||||
|
||||
setAdapterAndServerTimeOffset(19999, SERVLET_URL);
|
||||
|
||||
driver.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
tokenPage.assertIsCurrent();
|
||||
assertThat(tokenPage.getRefreshToken().getId(), is(not(equalTo(refreshTokenId))));
|
||||
assertThat(tokenPage.getAccessToken().getId(), is(not(equalTo(accessTokenId))));
|
||||
|
||||
setAdapterAndServerTimeOffset(0, SERVLET_URL);
|
||||
|
||||
logout(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRevoke() {
|
||||
// Login to servlet first with offline token
|
||||
String servletUri = UriBuilder.fromUri(SERVLET_URL)
|
||||
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
|
||||
.build().toString();
|
||||
driver.navigate().to(servletUri);
|
||||
waitForPageToLoad();
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
testRealmLoginPage.form().login(USER_LOGIN, USER_PASSWORD);
|
||||
tokenPage.assertIsCurrent();
|
||||
|
||||
assertThat(tokenPage.getRefreshToken().getType(), is(equalTo(TokenUtil.TOKEN_TYPE_OFFLINE)));
|
||||
|
||||
// Assert refresh works with increased time
|
||||
setAdapterAndServerTimeOffset(9999, SERVLET_URL);
|
||||
|
||||
driver.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
tokenPage.assertIsCurrent();
|
||||
|
||||
setAdapterAndServerTimeOffset(0, SERVLET_URL);
|
||||
|
||||
events.clear();
|
||||
|
||||
// Go to account service and revoke grant
|
||||
List<Map<String, Object>> userConsents = AccountHelper.getUserConsents(adminClient.realm(TEST_REALM), USER_LOGIN);
|
||||
String grantValue = String.valueOf(((LinkedHashMap) ((ArrayList) userConsents.get(0).get("additionalGrants")).get(0)).get("key"));
|
||||
assertThat(userConsents, hasSize(1));
|
||||
Assert.assertEquals("Offline Token", grantValue);
|
||||
|
||||
AccountHelper.revokeConsents(adminClient.realm(TEST_REALM), USER_LOGIN, CLIENT_ID);
|
||||
userConsents = AccountHelper.getUserConsents(adminClient.realm(TEST_REALM), USER_LOGIN);
|
||||
Assert.assertEquals(userConsents.size(), 0);
|
||||
|
||||
UserRepresentation userRepresentation =
|
||||
ApiUtil.findUserByUsername(realmsResouce().realm(REALM_NAME), USER_LOGIN);
|
||||
assertThat(userRepresentation, is(notNullValue()));
|
||||
|
||||
// Assert refresh doesn't work now (increase time one more time)
|
||||
setAdapterAndServerTimeOffset(19999, SERVLET_URL);
|
||||
driver.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
testRealmLoginPage.form().login(USER_LOGIN, USER_PASSWORD);
|
||||
tokenPage.assertIsCurrent();
|
||||
|
||||
setAdapterAndServerTimeOffset(0, SERVLET_URL);
|
||||
logout(SERVLET_URL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConsent() {
|
||||
ClientManager.realm(adminClient.realm(REALM_NAME)).clientId(CLIENT_ID).consentRequired(true);
|
||||
|
||||
// Assert grant page doesn't have 'Offline Access' role when offline token is not requested
|
||||
driver.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
testRealmLoginPage.form().login(USER_LOGIN, USER_PASSWORD);
|
||||
oauthGrantPage.assertCurrent();
|
||||
oauthGrantPage.cancel();
|
||||
|
||||
driver.navigate().to(UriBuilder.fromUri(SERVLET_URL)
|
||||
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
|
||||
.build().toString());
|
||||
waitForPageToLoad();
|
||||
|
||||
testRealmLoginPage.form().login(USER_LOGIN, USER_PASSWORD);
|
||||
oauthGrantPage.assertCurrent();
|
||||
oauthGrantPage.accept();
|
||||
|
||||
tokenPage.assertIsCurrent();
|
||||
assertThat(tokenPage.getRefreshToken().getType(), is(equalTo(TokenUtil.TOKEN_TYPE_OFFLINE)));
|
||||
|
||||
List<Map<String, Object>> userConsents = AccountHelper.getUserConsents(adminClient.realm(TEST_REALM), USER_LOGIN);
|
||||
String grantValue = String.valueOf(((LinkedHashMap) ((ArrayList) userConsents.get(0).get("additionalGrants")).get(0)).get("key"));
|
||||
Assert.assertTrue(((List) userConsents.get(0).get("grantedClientScopes")).stream().anyMatch(p -> p.equals("offline_access")));
|
||||
Assert.assertEquals("Offline Token", grantValue);
|
||||
|
||||
//This was necessary to be introduced, otherwise other testcases will fail
|
||||
logout(SERVLET_URL);
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage);
|
||||
|
||||
events.clear();
|
||||
|
||||
// Revert change
|
||||
ClientManager.realm(adminClient.realm(REALM_NAME)).clientId(CLIENT_ID).consentRequired(false);
|
||||
}
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
package org.keycloak.testsuite.springboot;
|
||||
|
||||
import org.jboss.arquillian.drone.api.annotation.Drone;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
||||
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
|
||||
import org.keycloak.testsuite.pages.InfoPage;
|
||||
import org.keycloak.testsuite.pages.LogoutConfirmPage;
|
||||
import org.keycloak.testsuite.util.DroneUtils;
|
||||
import org.keycloak.testsuite.util.SecondBrowser;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||
|
||||
public class SessionSpringBootTest extends AbstractSpringBootTest {
|
||||
|
||||
private static final String SERVLET_URL = BASE_URL + "/SessionServlet";
|
||||
|
||||
static final String USER_LOGIN_CORRECT_2 = "testcorrectuser2";
|
||||
static final String USER_EMAIL_CORRECT_2 = "usercorrect2@email.test";
|
||||
static final String USER_PASSWORD_CORRECT_2 = "testcorrectpassword2";
|
||||
|
||||
@Page
|
||||
private SessionPage sessionPage;
|
||||
|
||||
@Page
|
||||
@SecondBrowser
|
||||
private SessionPage secondBrowserSessionPage;
|
||||
|
||||
@Drone
|
||||
@SecondBrowser
|
||||
private WebDriver driver2;
|
||||
|
||||
@Page
|
||||
@SecondBrowser
|
||||
private OIDCLogin secondTestRealmLoginPage;
|
||||
|
||||
@Page
|
||||
@SecondBrowser
|
||||
protected LogoutConfirmPage secondBrowserLogoutConfirmPage;
|
||||
|
||||
@Page
|
||||
@SecondBrowser
|
||||
protected InfoPage secondBrowserInfoPage;
|
||||
|
||||
|
||||
@Override
|
||||
public void setDefaultPageUriParameters() {
|
||||
super.setDefaultPageUriParameters();
|
||||
testRealmLoginPage.setAuthRealm(REALM_NAME);
|
||||
secondTestRealmLoginPage.setAuthRealm(REALM_NAME);
|
||||
}
|
||||
|
||||
private void loginAndCheckSession() {
|
||||
driver.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage, driver);
|
||||
testRealmLoginPage.form().login(USER_LOGIN, USER_PASSWORD);
|
||||
|
||||
sessionPage.assertIsCurrent();
|
||||
assertThat(sessionPage.getCounter(), is(equalTo(0)));
|
||||
|
||||
driver.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
assertThat(sessionPage.getCounter(), is(equalTo(1)));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void addUserCorrect2() {
|
||||
addUser(USER_LOGIN_CORRECT_2, USER_EMAIL_CORRECT_2, USER_PASSWORD_CORRECT_2, CORRECT_ROLE);
|
||||
}
|
||||
|
||||
@After
|
||||
public void removeUserCorrect2() {
|
||||
UserRepresentation userRep = ApiUtil.findUserByUsername(realmsResouce().realm(REALM_NAME), USER_LOGIN_CORRECT_2);
|
||||
if (userRep != null) {
|
||||
realmsResouce().realm(REALM_NAME).users().get(userRep.getId()).remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleSessionInvalidated() {
|
||||
loginAndCheckSession();
|
||||
|
||||
DroneUtils.addWebDriver(driver2);
|
||||
|
||||
driver2.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad(); // driver2 will be used because of DroneUtils.addWebDriver()
|
||||
|
||||
log.info("current title is " + driver2.getTitle());
|
||||
assertCurrentUrlStartsWith(secondTestRealmLoginPage, driver2);
|
||||
secondTestRealmLoginPage.form().login(USER_LOGIN, USER_PASSWORD);
|
||||
|
||||
secondBrowserSessionPage.assertIsCurrent();
|
||||
|
||||
assertThat(secondBrowserSessionPage.getCounter(), is(equalTo(0)));
|
||||
|
||||
// Counter increased now
|
||||
driver2.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad(); // driver2 will be used because of DroneUtils.addWebDriver()
|
||||
|
||||
assertThat(secondBrowserSessionPage.getCounter(), is(equalTo(1)));
|
||||
|
||||
DroneUtils.removeWebDriver(); // From now driver will be used instead of driver2
|
||||
|
||||
// Logout in browser1
|
||||
logout(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
// Assert that I am logged out in browser1
|
||||
driver.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage, driver);
|
||||
|
||||
// Assert that I am still logged in browser2 and same session is still preserved
|
||||
DroneUtils.addWebDriver(driver2);
|
||||
driver2.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
secondBrowserSessionPage.assertIsCurrent();
|
||||
assertThat(secondBrowserSessionPage.getCounter(), is(equalTo(2)));
|
||||
|
||||
String logoutUrl = getLogoutUrl();
|
||||
driver2.navigate().to(logoutUrl);
|
||||
|
||||
waitForPageToLoad();
|
||||
assertThat(true, is(secondBrowserLogoutConfirmPage.isCurrent(driver2)));
|
||||
secondBrowserLogoutConfirmPage.confirmLogout(driver2);
|
||||
waitForPageToLoad();
|
||||
secondBrowserInfoPage.assertCurrent();
|
||||
waitForPageToLoad();
|
||||
driver2.navigate().to(SERVLET_URL);
|
||||
|
||||
waitForPageToLoad();
|
||||
assertCurrentUrlStartsWith(secondTestRealmLoginPage, driver2);
|
||||
|
||||
DroneUtils.removeWebDriver();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionInvalidatedAfterFailedRefresh() {
|
||||
RealmResource realmResource = adminClient.realm(REALM_NAME);
|
||||
RealmRepresentation realmRep = realmResource.toRepresentation();
|
||||
ClientResource clientResource = null;
|
||||
for (ClientRepresentation clientRep : realmResource.clients().findAll()) {
|
||||
if (CLIENT_ID.equals(clientRep.getClientId())) {
|
||||
clientResource = realmResource.clients().get(clientRep.getId());
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(clientResource, is(notNullValue()));
|
||||
|
||||
clientResource.toRepresentation().setAdminUrl("");
|
||||
int origTokenLifespan = realmRep.getAccessCodeLifespan();
|
||||
realmRep.setAccessCodeLifespan(1);
|
||||
realmResource.update(realmRep);
|
||||
|
||||
// Login
|
||||
loginAndCheckSession();
|
||||
|
||||
// Logout
|
||||
logout(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
// Assert that http session was invalidated
|
||||
driver.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
assertCurrentUrlStartsWith(testRealmLoginPage, driver);
|
||||
testRealmLoginPage.form().login(USER_LOGIN, USER_PASSWORD);
|
||||
|
||||
sessionPage.assertIsCurrent();
|
||||
assertThat(sessionPage.getCounter(), is(equalTo(0)));
|
||||
|
||||
clientResource.toRepresentation().setAdminUrl(BASE_URL);
|
||||
realmRep.setAccessCodeLifespan(origTokenLifespan);
|
||||
realmResource.update(realmRep);
|
||||
|
||||
logout(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdminApplicationLogout() {
|
||||
loginAndCheckSession();
|
||||
|
||||
// logout user2 with admin client
|
||||
UserRepresentation correct2 = realmsResouce().realm(REALM_NAME)
|
||||
.users().search(USER_LOGIN_CORRECT_2, null, null, null, null, null).get(0);
|
||||
realmsResouce().realm(REALM_NAME).users().get(correct2.getId()).logout();
|
||||
|
||||
// user1 should be still logged with original httpSession in our browser window
|
||||
driver.navigate().to(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
|
||||
sessionPage.assertIsCurrent();
|
||||
assertThat(sessionPage.getCounter(), is(equalTo(2)));
|
||||
|
||||
logout(SERVLET_URL);
|
||||
waitForPageToLoad();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue