Remove SpringBoot adapters

Closes #28781

Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
Douglas Palmer 2024-04-23 09:22:15 -07:00 committed by Marek Posolda
parent 43aa10e091
commit bf2c97065f
48 changed files with 1 additions and 3661 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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());
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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>

View file

@ -1,2 +0,0 @@
/.apt_generated/
/.apt_generated_tests/

View file

@ -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>

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.keycloak.adapters.springboot.KeycloakAutoConfiguration

View file

@ -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));
}
}

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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::[]

View file

@ -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.

View 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();
}
...
}
----
----

View file

@ -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::[]

View file

@ -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>

View file

@ -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>

View file

@ -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
View file

@ -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>

View file

@ -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:
```

View file

@ -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>

View file

@ -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>

View file

@ -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";
}
}
}

View file

@ -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);
}
}

View file

@ -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/*

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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 {
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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.")));
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}