Remove Spring adapters
Closes #28780 Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
parent
bf2c97065f
commit
3e13b40648
64 changed files with 0 additions and 5397 deletions
|
@ -38,7 +38,6 @@
|
||||||
<module>js</module>
|
<module>js</module>
|
||||||
<module>servlet-filter</module>
|
<module>servlet-filter</module>
|
||||||
<module>jakarta-servlet-filter</module>
|
<module>jakarta-servlet-filter</module>
|
||||||
<module>spring-security</module>
|
|
||||||
<module>undertow</module>
|
<module>undertow</module>
|
||||||
<module>wildfly</module>
|
<module>wildfly</module>
|
||||||
<module>wildfly-elytron</module>
|
<module>wildfly-elytron</module>
|
||||||
|
|
|
@ -1,140 +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-security-adapter</artifactId>
|
|
||||||
<name>Keycloak Spring Security Integration</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<spring.version>5.2.9.RELEASE</spring.version>
|
|
||||||
<spring-security.version>5.2.9.RELEASE</spring-security.version>
|
|
||||||
<mockito.version>1.9.5</mockito.version>
|
|
||||||
<apache-httpcomponents.version>4.3.6</apache-httpcomponents.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-spi</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-policy-enforcer</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
|
||||||
<artifactId>jboss-servlet-api_4.0_spec</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.security</groupId>
|
|
||||||
<artifactId>spring-security-config</artifactId>
|
|
||||||
<version>${spring-security.version}</version>
|
|
||||||
<optional>true</optional>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.security</groupId>
|
|
||||||
<artifactId>spring-security-web</artifactId>
|
|
||||||
<version>${spring-security.version}</version>
|
|
||||||
<optional>true</optional>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-api</artifactId>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.logging</groupId>
|
|
||||||
<artifactId>jboss-logging</artifactId>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.logging</groupId>
|
|
||||||
<artifactId>commons-logging-jboss-logging</artifactId>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpclient</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bouncycastle</groupId>
|
|
||||||
<artifactId>bcprov-jdk18on</artifactId>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-core</artifactId>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-databind</artifactId>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-annotations</artifactId>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-test</artifactId>
|
|
||||||
<version>${spring.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.mockito</groupId>
|
|
||||||
<artifactId>mockito-all</artifactId>
|
|
||||||
<version>${mockito.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-simple</artifactId>
|
|
||||||
<version>${slf4j.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
|
@ -1,94 +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.springsecurity;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.FactoryBean;
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link FactoryBean} that creates an {@link AdapterDeploymentContext} given a {@link Resource} defining the Keycloak
|
|
||||||
* client configuration or a {@link KeycloakConfigResolver} for multi-tenant environments.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:thomas.raehalme@aitiofinland.com">Thomas Raehalme</a>
|
|
||||||
*/
|
|
||||||
public class AdapterDeploymentContextFactoryBean
|
|
||||||
implements FactoryBean<AdapterDeploymentContext>, InitializingBean {
|
|
||||||
private static final Logger log =
|
|
||||||
LoggerFactory.getLogger(AdapterDeploymentContextFactoryBean.class);
|
|
||||||
private final Resource keycloakConfigFileResource;
|
|
||||||
private final KeycloakConfigResolver keycloakConfigResolver;
|
|
||||||
private AdapterDeploymentContext adapterDeploymentContext;
|
|
||||||
|
|
||||||
public AdapterDeploymentContextFactoryBean(Resource keycloakConfigFileResource) {
|
|
||||||
this.keycloakConfigFileResource = Objects.requireNonNull(keycloakConfigFileResource);
|
|
||||||
this.keycloakConfigResolver = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AdapterDeploymentContextFactoryBean(KeycloakConfigResolver keycloakConfigResolver) {
|
|
||||||
this.keycloakConfigResolver = Objects.requireNonNull(keycloakConfigResolver);
|
|
||||||
this.keycloakConfigFileResource = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<?> getObjectType() {
|
|
||||||
return AdapterDeploymentContext.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSingleton() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterPropertiesSet() throws Exception {
|
|
||||||
if (keycloakConfigResolver != null) {
|
|
||||||
adapterDeploymentContext = new AdapterDeploymentContext(keycloakConfigResolver);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.info("Loading Keycloak deployment from configuration file: {}", keycloakConfigFileResource);
|
|
||||||
|
|
||||||
KeycloakDeployment deployment = loadKeycloakDeployment();
|
|
||||||
adapterDeploymentContext = new AdapterDeploymentContext(deployment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeycloakDeployment loadKeycloakDeployment() throws IOException {
|
|
||||||
if (!keycloakConfigFileResource.isReadable()) {
|
|
||||||
throw new FileNotFoundException(String.format("Unable to locate Keycloak configuration file: %s",
|
|
||||||
keycloakConfigFileResource.getFilename()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return KeycloakDeploymentBuilder.build(keycloakConfigFileResource.getInputStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AdapterDeploymentContext getObject() throws Exception {
|
|
||||||
return adapterDeploymentContext;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +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.springsecurity;
|
|
||||||
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
|
|
||||||
public class KeycloakAuthenticationException extends AuthenticationException {
|
|
||||||
public KeycloakAuthenticationException(String msg, Throwable t) {
|
|
||||||
super(msg, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeycloakAuthenticationException(String msg) {
|
|
||||||
super(msg);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package org.keycloak.adapters.springsecurity;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.TYPE;
|
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add this annotation to a class that extends {@code KeycloakWebSecurityConfigurerAdapter} to provide
|
|
||||||
* a keycloak based Spring security configuration.
|
|
||||||
*
|
|
||||||
* @author Hendrik Ebbers
|
|
||||||
*/
|
|
||||||
@Retention(value = RUNTIME)
|
|
||||||
@Target(value = { TYPE })
|
|
||||||
@Configuration
|
|
||||||
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
|
|
||||||
@EnableWebSecurity
|
|
||||||
public @interface KeycloakConfiguration {
|
|
||||||
}
|
|
|
@ -1,27 +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.springsecurity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Locator interface for Spring context component scanning.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public interface KeycloakSecurityComponents {
|
|
||||||
}
|
|
|
@ -1,79 +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.springsecurity.account;
|
|
||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an authority granted to an {@link Authentication} by the Keycloak server.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class KeycloakRole implements GrantedAuthority {
|
|
||||||
|
|
||||||
private String role;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new granted authority from the given Keycloak role.
|
|
||||||
*
|
|
||||||
* @param role the name of this granted authority
|
|
||||||
*/
|
|
||||||
public KeycloakRole(String role) {
|
|
||||||
Assert.notNull(role, "role cannot be null");
|
|
||||||
this.role = role;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAuthority() {
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(o instanceof GrantedAuthority)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GrantedAuthority that = (GrantedAuthority) o;
|
|
||||||
|
|
||||||
if (!role.equals(that.getAuthority())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return 3 * role.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "KeycloakRole{" +
|
|
||||||
"role='" + role + '\'' +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,59 +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.springsecurity.account;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Concrete, serializable {@link org.keycloak.adapters.OidcKeycloakAccount} implementation.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class SimpleKeycloakAccount implements OidcKeycloakAccount, Serializable {
|
|
||||||
|
|
||||||
private Set<String> roles;
|
|
||||||
private Principal principal;
|
|
||||||
private RefreshableKeycloakSecurityContext securityContext;
|
|
||||||
|
|
||||||
public SimpleKeycloakAccount(Principal principal, Set<String> roles, RefreshableKeycloakSecurityContext securityContext) {
|
|
||||||
this.principal = principal;
|
|
||||||
this.roles = roles;
|
|
||||||
this.securityContext = securityContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Principal getPrincipal() {
|
|
||||||
return principal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getRoles() {
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
|
|
||||||
return securityContext;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import org.apache.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link RequestMatcher} that determines if a given request is an API request or an
|
|
||||||
* interactive login request.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @see RequestMatcher
|
|
||||||
*/
|
|
||||||
public class HttpHeaderInspectingApiRequestMatcher implements RequestMatcher {
|
|
||||||
|
|
||||||
protected static final String X_REQUESTED_WITH_HEADER = "X-Requested-With";
|
|
||||||
protected static final String X_REQUESTED_WITH_HEADER_AJAX_VALUE = "XMLHttpRequest";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given request is an API request or false if it's an interactive
|
|
||||||
* login request.
|
|
||||||
*
|
|
||||||
* @param request the <code>HttpServletRequest</code>
|
|
||||||
* @return <code>true</code> if the given <code>request</code> is an API request;
|
|
||||||
* <code>false</code> otherwise
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean matches(HttpServletRequest request) {
|
|
||||||
return X_REQUESTED_WITH_HEADER_AJAX_VALUE.equals(request.getHeader(X_REQUESTED_WITH_HEADER));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,134 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.apache.http.HttpHeaders;
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a Keycloak {@link AuthenticationEntryPoint authentication entry point}. Uses a
|
|
||||||
* {@link RequestMatcher} to determine if the request is an interactive login request or a
|
|
||||||
* API request, which should not be redirected to an interactive login page. By default,
|
|
||||||
* this entry point uses a {@link HttpHeaderInspectingApiRequestMatcher} but can be overridden using in the
|
|
||||||
* constructor.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
*
|
|
||||||
* @see HttpHeaderInspectingApiRequestMatcher
|
|
||||||
*/
|
|
||||||
public class KeycloakAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default Keycloak authentication login URI
|
|
||||||
*/
|
|
||||||
public static final String DEFAULT_LOGIN_URI = "/sso/login";
|
|
||||||
private static final String DEFAULT_REALM = "Unknown";
|
|
||||||
private static final RequestMatcher DEFAULT_API_REQUEST_MATCHER = new HttpHeaderInspectingApiRequestMatcher();
|
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(KeycloakAuthenticationEntryPoint.class);
|
|
||||||
|
|
||||||
private final RequestMatcher apiRequestMatcher;
|
|
||||||
private String loginUri = DEFAULT_LOGIN_URI;
|
|
||||||
private String realm = DEFAULT_REALM;
|
|
||||||
|
|
||||||
private AdapterDeploymentContext adapterDeploymentContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Keycloak authentication entry point.
|
|
||||||
*/
|
|
||||||
public KeycloakAuthenticationEntryPoint(AdapterDeploymentContext adapterDeploymentContext) {
|
|
||||||
this(adapterDeploymentContext, DEFAULT_API_REQUEST_MATCHER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Keycloak authentication entry point using the given request
|
|
||||||
* matcher to determine if the current request is an API request or a browser request.
|
|
||||||
*
|
|
||||||
* @param apiRequestMatcher the <code>RequestMatcher</code> to use to determine
|
|
||||||
* if the current request is an API request or a browser request (required)
|
|
||||||
*/
|
|
||||||
public KeycloakAuthenticationEntryPoint(AdapterDeploymentContext adapterDeploymentContext, RequestMatcher apiRequestMatcher) {
|
|
||||||
Assert.notNull(apiRequestMatcher, "apiRequestMatcher required");
|
|
||||||
Assert.notNull(adapterDeploymentContext, "adapterDeploymentContext required");
|
|
||||||
this.adapterDeploymentContext = adapterDeploymentContext;
|
|
||||||
this.apiRequestMatcher = apiRequestMatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
|
||||||
HttpFacade facade = new SimpleHttpFacade(request, response);
|
|
||||||
if (apiRequestMatcher.matches(request) || adapterDeploymentContext.resolveDeployment(facade).isBearerOnly()) {
|
|
||||||
commenceUnauthorizedResponse(request, response);
|
|
||||||
} else {
|
|
||||||
commenceLoginRedirect(request, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirects to the login page. If HTTP sessions are disabled, the redirect URL is saved in a
|
|
||||||
* cookie now, to be retrieved by the {@link KeycloakAuthenticationSuccessHandler} or the
|
|
||||||
* {@link KeycloakAuthenticationFailureHandler} when the login sequence completes.
|
|
||||||
*/
|
|
||||||
protected void commenceLoginRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
|
||||||
if (request.getSession(false) == null && KeycloakCookieBasedRedirect.getRedirectUrlFromCookie(request) == null) {
|
|
||||||
// If no session exists yet at this point, then apparently the redirect URL is not
|
|
||||||
// stored in a session. We'll store it in a cookie instead.
|
|
||||||
response.addCookie(KeycloakCookieBasedRedirect.createCookieFromRedirectUrl(request.getRequestURI()));
|
|
||||||
}
|
|
||||||
|
|
||||||
String queryParameters = "";
|
|
||||||
if (!StringUtils.isEmpty(request.getQueryString())) {
|
|
||||||
queryParameters = "?" + request.getQueryString();
|
|
||||||
}
|
|
||||||
|
|
||||||
String contextAwareLoginUri = request.getContextPath() + loginUri + queryParameters;
|
|
||||||
log.debug("Redirecting to login URI {}", contextAwareLoginUri);
|
|
||||||
response.sendRedirect(contextAwareLoginUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void commenceUnauthorizedResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
|
||||||
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, String.format("Bearer realm=\"%s\"", realm));
|
|
||||||
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLoginUri(String loginUri) {
|
|
||||||
Assert.notNull(loginUri, "loginUri cannot be null");
|
|
||||||
this.loginUri = loginUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealm(String realm) {
|
|
||||||
Assert.notNull(realm, "realm cannot be null");
|
|
||||||
this.realm = realm;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To return the forbidden code with the corresponding message.
|
|
||||||
*
|
|
||||||
* @author emilienbondu
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class KeycloakAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
|
||||||
// Check that the response was not committed yet (this may happen when another
|
|
||||||
// part of the Keycloak adapter sends a challenge or a redirect).
|
|
||||||
if (!response.isCommitted()) {
|
|
||||||
if (KeycloakCookieBasedRedirect.getRedirectUrlFromCookie(request) != null) {
|
|
||||||
response.addCookie(KeycloakCookieBasedRedirect.createCookieFromRedirectUrl(null));
|
|
||||||
}
|
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate using the Authorization header");
|
|
||||||
} else {
|
|
||||||
if (200 <= response.getStatus() && response.getStatus() < 300) {
|
|
||||||
throw new RuntimeException("Success response was committed while authentication failed!", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs authentication on a {@link KeycloakAuthenticationToken}.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class KeycloakAuthenticationProvider implements AuthenticationProvider {
|
|
||||||
private GrantedAuthoritiesMapper grantedAuthoritiesMapper;
|
|
||||||
|
|
||||||
public void setGrantedAuthoritiesMapper(GrantedAuthoritiesMapper grantedAuthoritiesMapper) {
|
|
||||||
this.grantedAuthoritiesMapper = grantedAuthoritiesMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
|
||||||
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
|
|
||||||
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
|
|
||||||
|
|
||||||
for (String role : token.getAccount().getRoles()) {
|
|
||||||
grantedAuthorities.add(new KeycloakRole(role));
|
|
||||||
}
|
|
||||||
return new KeycloakAuthenticationToken(token.getAccount(), token.isInteractive(), mapAuthorities(grantedAuthorities));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<? extends GrantedAuthority> mapAuthorities(
|
|
||||||
Collection<? extends GrantedAuthority> authorities) {
|
|
||||||
return grantedAuthoritiesMapper != null
|
|
||||||
? grantedAuthoritiesMapper.mapAuthorities(authorities)
|
|
||||||
: authorities;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(Class<?> aClass) {
|
|
||||||
return KeycloakAuthenticationToken.class.isAssignableFrom(aClass);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper for an authentication success handler that sends a redirect if a redirect URL was set in
|
|
||||||
* a cookie.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:scranen@gmail.com">Sjoerd Cranen</a>
|
|
||||||
*
|
|
||||||
* @see KeycloakCookieBasedRedirect
|
|
||||||
* @see KeycloakAuthenticationEntryPoint#commenceLoginRedirect
|
|
||||||
*/
|
|
||||||
public class KeycloakAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(KeycloakAuthenticationSuccessHandler.class);
|
|
||||||
|
|
||||||
private final AuthenticationSuccessHandler fallback;
|
|
||||||
|
|
||||||
public KeycloakAuthenticationSuccessHandler(AuthenticationSuccessHandler fallback) {
|
|
||||||
this.fallback = fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationSuccess(
|
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
String location = KeycloakCookieBasedRedirect.getRedirectUrlFromCookie(request);
|
|
||||||
if (location == null) {
|
|
||||||
if (fallback != null) {
|
|
||||||
fallback.onAuthenticationSuccess(request, response, authentication);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
response.addCookie(KeycloakCookieBasedRedirect.createCookieFromRedirectUrl(null));
|
|
||||||
response.sendRedirect(location);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn("Unable to redirect user after login", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,69 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class that provides methods to create and retrieve cookies used for login redirects.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:scranen@gmail.com">Sjoerd Cranen</a>
|
|
||||||
*/
|
|
||||||
public final class KeycloakCookieBasedRedirect {
|
|
||||||
|
|
||||||
private static final String REDIRECT_COOKIE = "KC_REDIRECT";
|
|
||||||
|
|
||||||
private KeycloakCookieBasedRedirect() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a cookie with name {@value REDIRECT_COOKIE} exists, and if so, returns its value.
|
|
||||||
* If multiple cookies of the same name exist, the value of the first cookie is returned.
|
|
||||||
*
|
|
||||||
* @param request the request to retrieve the cookie from.
|
|
||||||
* @return the value of the cookie, if it exists, or else {@code null}.
|
|
||||||
*/
|
|
||||||
public static String getRedirectUrlFromCookie(HttpServletRequest request) {
|
|
||||||
if (request.getCookies() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (Cookie cookie : request.getCookies()) {
|
|
||||||
if (REDIRECT_COOKIE.equals(cookie.getName())) {
|
|
||||||
return cookie.getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a cookie with name {@value REDIRECT_COOKIE} and the given URL as value.
|
|
||||||
*
|
|
||||||
* @param url the value that the cookie should have. If {@code null}, a cookie is created that
|
|
||||||
* expires immediately and has an empty string as value.
|
|
||||||
* @return a cookie that can be added to a response.
|
|
||||||
*/
|
|
||||||
public static Cookie createCookieFromRedirectUrl(String url) {
|
|
||||||
Cookie cookie = new Cookie(REDIRECT_COOKIE, url == null ? "" : url);
|
|
||||||
cookie.setHttpOnly(true);
|
|
||||||
cookie.setPath("/");
|
|
||||||
if (url == null) {
|
|
||||||
cookie.setMaxAge(0);
|
|
||||||
}
|
|
||||||
return cookie;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.AdapterTokenStoreFactory;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.SpringSecurityAdapterTokenStoreFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs the current user out of Keycloak.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class KeycloakLogoutHandler implements LogoutHandler {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(KeycloakLogoutHandler.class);
|
|
||||||
|
|
||||||
private AdapterDeploymentContext adapterDeploymentContext;
|
|
||||||
private AdapterTokenStoreFactory adapterTokenStoreFactory = new SpringSecurityAdapterTokenStoreFactory();
|
|
||||||
|
|
||||||
public KeycloakLogoutHandler(AdapterDeploymentContext adapterDeploymentContext) {
|
|
||||||
Assert.notNull(adapterDeploymentContext);
|
|
||||||
this.adapterDeploymentContext = adapterDeploymentContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAdapterTokenStoreFactory(AdapterTokenStoreFactory adapterTokenStoreFactory) {
|
|
||||||
this.adapterTokenStoreFactory = adapterTokenStoreFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
|
||||||
if (authentication == null) {
|
|
||||||
log.warn("Cannot log out without authentication");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (!KeycloakAuthenticationToken.class.isAssignableFrom(authentication.getClass())) {
|
|
||||||
log.warn("Cannot log out a non-Keycloak authentication: {}", authentication);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSingleSignOut(request, response, (KeycloakAuthenticationToken) authentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void handleSingleSignOut(HttpServletRequest request, HttpServletResponse response, KeycloakAuthenticationToken authenticationToken) {
|
|
||||||
HttpFacade facade = new SimpleHttpFacade(request, response);
|
|
||||||
KeycloakDeployment deployment = adapterDeploymentContext.resolveDeployment(facade);
|
|
||||||
adapterTokenStoreFactory.createAdapterTokenStore(deployment, request, response).logout();
|
|
||||||
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) authenticationToken.getAccount().getKeycloakSecurityContext();
|
|
||||||
session.logout(deployment);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
package org.keycloak.adapters.springsecurity.authentication;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates {@link RequestAuthenticator}s.
|
|
||||||
*/
|
|
||||||
public interface RequestAuthenticatorFactory {
|
|
||||||
/**
|
|
||||||
* Creates new {@link RequestAuthenticator} instances on a per-request basis.
|
|
||||||
*/
|
|
||||||
RequestAuthenticator createRequestAuthenticator(HttpFacade facade, HttpServletRequest request,
|
|
||||||
KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort);
|
|
||||||
}
|
|
|
@ -1,110 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request authenticator adapter for Spring Security.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class SpringSecurityRequestAuthenticator extends RequestAuthenticator {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SpringSecurityRequestAuthenticator.class);
|
|
||||||
private final HttpServletRequest request;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Spring Security request authenticator.
|
|
||||||
*
|
|
||||||
* @param facade the current <code>HttpFacade</code> (required)
|
|
||||||
* @param request the current <code>HttpServletRequest</code> (required)
|
|
||||||
* @param deployment the <code>KeycloakDeployment</code> (required)
|
|
||||||
* @param tokenStore the <cdoe>AdapterTokenStore</cdoe> (required)
|
|
||||||
* @param sslRedirectPort the SSL redirect port (required)
|
|
||||||
*/
|
|
||||||
public SpringSecurityRequestAuthenticator(
|
|
||||||
HttpFacade facade,
|
|
||||||
HttpServletRequest request,
|
|
||||||
KeycloakDeployment deployment,
|
|
||||||
AdapterTokenStore tokenStore,
|
|
||||||
int sslRedirectPort) {
|
|
||||||
|
|
||||||
super(facade, deployment, tokenStore, sslRedirectPort);
|
|
||||||
this.request = request;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
|
|
||||||
return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
|
|
||||||
|
|
||||||
final RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
|
|
||||||
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
|
|
||||||
final OidcKeycloakAccount account = new SimpleKeycloakAccount(principal, roles, securityContext);
|
|
||||||
|
|
||||||
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
|
|
||||||
this.tokenStore.saveAccountInfo(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
|
|
||||||
|
|
||||||
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
|
|
||||||
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
|
|
||||||
final KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, securityContext);
|
|
||||||
|
|
||||||
logger.debug("Completing bearer authentication. Bearer roles: {} ",roles);
|
|
||||||
|
|
||||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
|
||||||
context.setAuthentication(new KeycloakAuthenticationToken(account, false));
|
|
||||||
SecurityContextHolder.setContext(context);
|
|
||||||
|
|
||||||
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String changeHttpSessionId(boolean create) {
|
|
||||||
HttpSession session = request.getSession(create);
|
|
||||||
return session != null ? session.getId() : null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package org.keycloak.adapters.springsecurity.authentication;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
|
|
||||||
public class SpringSecurityRequestAuthenticatorFactory implements RequestAuthenticatorFactory {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RequestAuthenticator createRequestAuthenticator(HttpFacade facade,
|
|
||||||
HttpServletRequest request, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort) {
|
|
||||||
return new SpringSecurityRequestAuthenticator(facade, request, deployment, tokenStore, sslRedirectPort);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +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.springsecurity.client;
|
|
||||||
|
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
|
||||||
import org.apache.http.impl.client.HttpClients;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.http.client.ClientHttpRequest;
|
|
||||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory for {@link ClientHttpRequest} objects created for server to server secured
|
|
||||||
* communication using OAuth2 bearer tokens issued by Keycloak.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
|
||||||
public class KeycloakClientRequestFactory extends HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory {
|
|
||||||
|
|
||||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
|
||||||
|
|
||||||
public KeycloakClientRequestFactory() {
|
|
||||||
super(HttpClients.custom()
|
|
||||||
.disableCookieManagement()
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void postProcessHttpRequest(HttpUriRequest request) {
|
|
||||||
KeycloakSecurityContext context = this.getKeycloakSecurityContext();
|
|
||||||
request.setHeader(AUTHORIZATION_HEADER, "Bearer " + context.getTokenString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link KeycloakSecurityContext} from the Spring {@link SecurityContextHolder}'s {@link Authentication}.
|
|
||||||
*
|
|
||||||
* @return the current <code>KeycloakSecurityContext</code>
|
|
||||||
*/
|
|
||||||
protected KeycloakSecurityContext getKeycloakSecurityContext() {
|
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
KeycloakAuthenticationToken token;
|
|
||||||
KeycloakSecurityContext context;
|
|
||||||
|
|
||||||
if (authentication == null) {
|
|
||||||
throw new IllegalStateException("Cannot set authorization header because there is no authenticated principal");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!KeycloakAuthenticationToken.class.isAssignableFrom(authentication.getClass())) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
String.format(
|
|
||||||
"Cannot set authorization header because Authentication is of type %s but %s is required",
|
|
||||||
authentication.getClass(), KeycloakAuthenticationToken.class)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
token = (KeycloakAuthenticationToken) authentication;
|
|
||||||
context = token.getAccount().getKeycloakSecurityContext();
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +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.springsecurity.client;
|
|
||||||
|
|
||||||
import org.springframework.web.client.RestOperations;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extends Spring's central class for client-side HTTP access, {@link RestTemplate}, adding
|
|
||||||
* automatic authentication for service to service calls using the currently authenticated Keycloak principal.
|
|
||||||
* This class is designed to work with other services secured by Keycloak.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* The main advantage to using this class over Spring's <code>RestTemplate</code> is that authentication
|
|
||||||
* is handled automatically when both the service making the API call and the service being called are
|
|
||||||
* protected by Keycloak authentication.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @see RestOperations
|
|
||||||
* @see RestTemplate
|
|
||||||
*
|
|
||||||
* @author Scott Rossillo
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class KeycloakRestTemplate extends RestTemplate implements RestOperations {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new instance based on the given {@link KeycloakClientRequestFactory}.
|
|
||||||
*
|
|
||||||
* @param factory the <code>KeycloakClientRequestFactory</code> to use when creating new requests
|
|
||||||
*/
|
|
||||||
public KeycloakRestTemplate(KeycloakClientRequestFactory factory) {
|
|
||||||
super(factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,50 +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.springsecurity.config;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spring applications may use different security stacks in order to enforce access based on the configuration provided
|
|
||||||
* by a {@code KeycloakDeployment}. This implementation of {@code KeycloakConfigResolver} wraps and avoid calling multiple
|
|
||||||
* {@code KeycloakConfigResolver} instances but only those defined by applications or set as default by the configuration.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakSpringConfigResolverWrapper implements KeycloakConfigResolver {
|
|
||||||
|
|
||||||
private KeycloakConfigResolver delegate;
|
|
||||||
|
|
||||||
public KeycloakSpringConfigResolverWrapper(KeycloakConfigResolver delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakDeployment resolve(HttpFacade.Request facade) {
|
|
||||||
return delegate.resolve(facade);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setDelegate(KeycloakConfigResolver delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected KeycloakConfigResolver getDelegate() {
|
|
||||||
return delegate;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,141 +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.springsecurity.config;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
|
||||||
import org.keycloak.adapters.springsecurity.AdapterDeploymentContextFactoryBean;
|
|
||||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
|
|
||||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
|
|
||||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakLogoutHandler;
|
|
||||||
import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter;
|
|
||||||
import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter;
|
|
||||||
import org.keycloak.adapters.springsecurity.filter.KeycloakCsrfRequestMatcher;
|
|
||||||
import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
|
|
||||||
import org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter;
|
|
||||||
import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
|
||||||
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
|
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
|
||||||
import org.springframework.security.web.authentication.logout.LogoutFilter;
|
|
||||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
|
||||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a convenient base class for creating a {@link WebSecurityConfigurer}
|
|
||||||
* instance secured by Keycloak. This implementation allows customization by overriding methods.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
* @see EnableWebSecurity
|
|
||||||
* @see EnableWebMvcSecurity
|
|
||||||
*/
|
|
||||||
public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
|
|
||||||
|
|
||||||
@Value("${keycloak.configurationFile:WEB-INF/keycloak.json}")
|
|
||||||
private Resource keycloakConfigFileResource;
|
|
||||||
@Autowired(required = false)
|
|
||||||
private KeycloakConfigResolver keycloakConfigResolver;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
protected AdapterDeploymentContext adapterDeploymentContext() throws Exception {
|
|
||||||
AdapterDeploymentContextFactoryBean factoryBean;
|
|
||||||
if (keycloakConfigResolver != null) {
|
|
||||||
factoryBean = new AdapterDeploymentContextFactoryBean(new KeycloakSpringConfigResolverWrapper(keycloakConfigResolver));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
factoryBean = new AdapterDeploymentContextFactoryBean(keycloakConfigFileResource);
|
|
||||||
}
|
|
||||||
factoryBean.afterPropertiesSet();
|
|
||||||
return factoryBean.getObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AuthenticationEntryPoint authenticationEntryPoint() throws Exception {
|
|
||||||
return new KeycloakAuthenticationEntryPoint(adapterDeploymentContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
|
|
||||||
return new KeycloakAuthenticationProvider();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
|
|
||||||
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean());
|
|
||||||
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
protected KeycloakPreAuthActionsFilter keycloakPreAuthActionsFilter() {
|
|
||||||
return new KeycloakPreAuthActionsFilter(httpSessionManager());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected KeycloakCsrfRequestMatcher keycloakCsrfRequestMatcher() {
|
|
||||||
return new KeycloakCsrfRequestMatcher();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
protected HttpSessionManager httpSessionManager() {
|
|
||||||
return new HttpSessionManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected KeycloakLogoutHandler keycloakLogoutHandler() throws Exception {
|
|
||||||
return new KeycloakLogoutHandler(adapterDeploymentContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract SessionAuthenticationStrategy sessionAuthenticationStrategy();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
|
||||||
|
|
||||||
http
|
|
||||||
.csrf().requireCsrfProtectionMatcher(keycloakCsrfRequestMatcher())
|
|
||||||
.and()
|
|
||||||
.sessionManagement()
|
|
||||||
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
|
|
||||||
.and()
|
|
||||||
.addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class)
|
|
||||||
.addFilterBefore(keycloakAuthenticationProcessingFilter(), LogoutFilter.class)
|
|
||||||
.addFilterAfter(keycloakSecurityContextRequestFilter(), SecurityContextHolderAwareRequestFilter.class)
|
|
||||||
.addFilterAfter(keycloakAuthenticatedActionsRequestFilter(), KeycloakSecurityContextRequestFilter.class)
|
|
||||||
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
|
|
||||||
.and()
|
|
||||||
.logout()
|
|
||||||
.addLogoutHandler(keycloakLogoutHandler())
|
|
||||||
.logoutUrl("/sso/logout").permitAll()
|
|
||||||
.logoutSuccessUrl("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
protected KeycloakSecurityContextRequestFilter keycloakSecurityContextRequestFilter() {
|
|
||||||
return new KeycloakSecurityContextRequestFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
protected KeycloakAuthenticatedActionsFilter keycloakAuthenticatedActionsRequestFilter() {
|
|
||||||
return new KeycloakAuthenticatedActionsFilter();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,85 +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.springsecurity.facade;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import javax.security.cert.X509Certificate;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple {@link org.keycloak.adapters.OIDCHttpFacade} wrapping an {@link HttpServletRequest} and {@link HttpServletResponse}.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class SimpleHttpFacade implements OIDCHttpFacade {
|
|
||||||
|
|
||||||
private final HttpServletRequest request;
|
|
||||||
private final HttpServletResponse response;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new simple HTTP facade for the given request and response.
|
|
||||||
*
|
|
||||||
* @param request the current <code>HttpServletRequest</code> (required)
|
|
||||||
* @param response the current <code>HttpServletResponse</code> (required)
|
|
||||||
*/
|
|
||||||
public SimpleHttpFacade(HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
Assert.notNull(request, "HttpServletRequest required");
|
|
||||||
Assert.notNull(response, "HttpServletResponse required");
|
|
||||||
this.request = request;
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakSecurityContext getSecurityContext() {
|
|
||||||
|
|
||||||
SecurityContext context = SecurityContextHolder.getContext();
|
|
||||||
|
|
||||||
if (context != null && context.getAuthentication() != null) {
|
|
||||||
KeycloakAuthenticationToken authentication = (KeycloakAuthenticationToken) context.getAuthentication();
|
|
||||||
return authentication.getAccount().getKeycloakSecurityContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Request getRequest() {
|
|
||||||
return new WrappedHttpServletRequest(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response getResponse() {
|
|
||||||
return new WrappedHttpServletResponse(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public X509Certificate[] getCertificateChain() {
|
|
||||||
// TODO: implement me
|
|
||||||
return new X509Certificate[0];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,169 +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.springsecurity.facade;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.spi.AuthenticationError;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade.Cookie;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
|
||||||
import org.keycloak.adapters.spi.LogoutError;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Concrete Keycloak {@link Request request} implementation wrapping an {@link HttpServletRequest}.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
class WrappedHttpServletRequest implements Request {
|
|
||||||
|
|
||||||
private final HttpServletRequest request;
|
|
||||||
private InputStream inputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new request for the given <code>HttpServletRequest</code>
|
|
||||||
*
|
|
||||||
* @param request the current <code>HttpServletRequest</code> (required)
|
|
||||||
*/
|
|
||||||
public WrappedHttpServletRequest(HttpServletRequest request) {
|
|
||||||
Assert.notNull(request, "HttpServletRequest required");
|
|
||||||
this.request = request;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFirstParam(String param) {
|
|
||||||
return request.getParameter(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMethod() {
|
|
||||||
return request.getMethod();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getURI() {
|
|
||||||
StringBuffer buf = request.getRequestURL();
|
|
||||||
if (request.getQueryString() != null) {
|
|
||||||
buf.append('?').append(request.getQueryString());
|
|
||||||
}
|
|
||||||
return buf.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRelativePath() {
|
|
||||||
return request.getServletPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSecure() {
|
|
||||||
return request.isSecure();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getQueryParamValue(String param) {
|
|
||||||
return request.getParameter(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cookie getCookie(String cookieName) {
|
|
||||||
|
|
||||||
javax.servlet.http.Cookie[] cookies = request.getCookies();
|
|
||||||
|
|
||||||
if (cookies == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (javax.servlet.http.Cookie cookie : request.getCookies()) {
|
|
||||||
if (cookie.getName().equals(cookieName)) {
|
|
||||||
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHeader(String name) {
|
|
||||||
return request.getHeader(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getHeaders(String name) {
|
|
||||||
Enumeration<String> values = request.getHeaders(name);
|
|
||||||
List<String> array = new ArrayList<String>();
|
|
||||||
|
|
||||||
while (values.hasMoreElements()) {
|
|
||||||
array.add(values.nextElement());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Collections.unmodifiableList(array);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getInputStream() {
|
|
||||||
return getInputStream(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getInputStream(boolean buffered) {
|
|
||||||
if (inputStream != null) {
|
|
||||||
return inputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffered) {
|
|
||||||
try {
|
|
||||||
return inputStream = new BufferedInputStream(request.getInputStream());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return request.getInputStream();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRemoteAddr() {
|
|
||||||
return request.getRemoteAddr();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setError(AuthenticationError error) {
|
|
||||||
request.setAttribute(AuthenticationError.class.getName(), error);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setError(LogoutError error) {
|
|
||||||
request.setAttribute(LogoutError.class.getName(), error);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,137 +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.springsecurity.facade;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade.Response;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Concrete Keycloak {@link Response response} implementation wrapping an {@link HttpServletResponse}.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
class WrappedHttpServletResponse implements Response {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(WrappedHttpServletResponse.class);
|
|
||||||
private final HttpServletResponse response;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new response for the given <code>HttpServletResponse</code>.
|
|
||||||
*
|
|
||||||
* @param response the current <code>HttpServletResponse</code> (required)
|
|
||||||
*/
|
|
||||||
public WrappedHttpServletResponse(HttpServletResponse response) {
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resetCookie(String name, String path) {
|
|
||||||
Cookie cookie = new Cookie(name, "");
|
|
||||||
cookie.setMaxAge(0);
|
|
||||||
if (path != null) {
|
|
||||||
cookie.setPath(path);
|
|
||||||
}
|
|
||||||
response.addCookie(cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
|
|
||||||
Cookie cookie = new Cookie(name, value);
|
|
||||||
|
|
||||||
if (path != null) {
|
|
||||||
cookie.setPath(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (domain != null) {
|
|
||||||
cookie.setDomain(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie.setMaxAge(maxAge);
|
|
||||||
cookie.setSecure(secure);
|
|
||||||
this.setHttpOnly(cookie, httpOnly);
|
|
||||||
|
|
||||||
response.addCookie(cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setHttpOnly(Cookie cookie, boolean httpOnly) {
|
|
||||||
Method method;
|
|
||||||
try {
|
|
||||||
method = Cookie.class.getMethod("setHttpOnly", boolean.class);
|
|
||||||
method.invoke(cookie, httpOnly);
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
log.warn("Unable to set httpOnly on cookie [{}]; no such method on javax.servlet.http.Cookie", cookie.getName());
|
|
||||||
} catch (ReflectiveOperationException e) {
|
|
||||||
log.error("Unable to set httpOnly on cookie [{}]", cookie.getName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setStatus(int status) {
|
|
||||||
response.setStatus(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addHeader(String name, String value) {
|
|
||||||
response.addHeader(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setHeader(String name, String value) {
|
|
||||||
response.setHeader(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OutputStream getOutputStream() {
|
|
||||||
try {
|
|
||||||
return response.getOutputStream();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Unable to return response output stream", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendError(int code) {
|
|
||||||
try {
|
|
||||||
response.sendError(code);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Unable to set HTTP status", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendError(int code, String message) {
|
|
||||||
try {
|
|
||||||
response.sendError(code, message);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Unable to set HTTP status", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void end() {
|
|
||||||
// TODO: do we need this?
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides an {@link org.keycloak.adapters.OIDCHttpFacade} implementation.
|
|
||||||
*/
|
|
||||||
package org.keycloak.adapters.springsecurity.facade;
|
|
|
@ -1,45 +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.springsecurity.filter;
|
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Matches a request if it contains a {@value AdapterConstants#KEYCLOAK_ADAPTER_STATE_COOKIE}
|
|
||||||
* cookie.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:scranen@gmail.com">Sjoerd Cranen</a>
|
|
||||||
*/
|
|
||||||
public class AdapterStateCookieRequestMatcher implements RequestMatcher {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(HttpServletRequest request) {
|
|
||||||
if (request.getCookies() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (Cookie cookie: request.getCookies()) {
|
|
||||||
if (AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE.equals(cookie.getName())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,108 +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.springsecurity.filter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.ApplicationContextAware;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.web.filter.GenericFilterBean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakAuthenticatedActionsFilter extends GenericFilterBean implements ApplicationContextAware {
|
|
||||||
|
|
||||||
private static final String FILTER_APPLIED = KeycloakAuthenticatedActionsFilter.class.getPackage().getName() + ".authenticated-actions";
|
|
||||||
|
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
private AdapterDeploymentContext deploymentContext;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
|
|
||||||
if (request.getAttribute(FILTER_APPLIED) != null) {
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
|
|
||||||
|
|
||||||
KeycloakSecurityContext keycloakSecurityContext = getKeycloakPrincipal();
|
|
||||||
|
|
||||||
if (keycloakSecurityContext instanceof RefreshableKeycloakSecurityContext) {
|
|
||||||
HttpFacade facade = new SimpleHttpFacade((HttpServletRequest) request, (HttpServletResponse) response);
|
|
||||||
KeycloakDeployment deployment = resolveDeployment(request, response);
|
|
||||||
AuthenticatedActionsHandler actions = new AuthenticatedActionsHandler(deployment, OIDCHttpFacade.class.cast(facade));
|
|
||||||
if (actions.handledRequest()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initFilterBean() {
|
|
||||||
deploymentContext = applicationContext.getBean(AdapterDeploymentContext.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
|
||||||
this.applicationContext = applicationContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeycloakSecurityContext getKeycloakPrincipal() {
|
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
|
|
||||||
if (authentication != null) {
|
|
||||||
Object principal = authentication.getPrincipal();
|
|
||||||
|
|
||||||
if (principal instanceof KeycloakPrincipal) {
|
|
||||||
return KeycloakPrincipal.class.cast(principal).getKeycloakSecurityContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeycloakDeployment resolveDeployment(ServletRequest servletRequest, ServletResponse servletResponse) {
|
|
||||||
return deploymentContext.resolveDeployment(new SimpleHttpFacade(HttpServletRequest.class.cast(servletRequest), HttpServletResponse.class.cast(servletResponse)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearAuthenticationContext() {
|
|
||||||
SecurityContextHolder.clearContext();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,270 +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.springsecurity.filter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.keycloak.OAuth2Constants;
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
|
||||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
|
|
||||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
|
|
||||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationSuccessHandler;
|
|
||||||
import org.keycloak.adapters.springsecurity.authentication.RequestAuthenticatorFactory;
|
|
||||||
import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticatorFactory;
|
|
||||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.AdapterTokenStoreFactory;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.SpringSecurityAdapterTokenStoreFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.ApplicationContextAware;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
|
||||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a Keycloak authentication processing filter.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
|
|
||||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
|
|
||||||
* and any request with a <code>Authorization</code> header or an {@link AdapterStateCookieRequestMatcher adapter state cookie}.
|
|
||||||
*/
|
|
||||||
public static final RequestMatcher DEFAULT_REQUEST_MATCHER =
|
|
||||||
new OrRequestMatcher(
|
|
||||||
new AntPathRequestMatcher(KeycloakAuthenticationEntryPoint.DEFAULT_LOGIN_URI),
|
|
||||||
new RequestHeaderRequestMatcher(AUTHORIZATION_HEADER),
|
|
||||||
new QueryParamPresenceRequestMatcher(OAuth2Constants.ACCESS_TOKEN),
|
|
||||||
new AdapterStateCookieRequestMatcher()
|
|
||||||
);
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticationProcessingFilter.class);
|
|
||||||
|
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
private AdapterDeploymentContext adapterDeploymentContext;
|
|
||||||
private AdapterTokenStoreFactory adapterTokenStoreFactory = new SpringSecurityAdapterTokenStoreFactory();
|
|
||||||
private AuthenticationManager authenticationManager;
|
|
||||||
private RequestAuthenticatorFactory requestAuthenticatorFactory = new SpringSecurityRequestAuthenticatorFactory();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Keycloak authentication processing filter with given {@link AuthenticationManager} and the
|
|
||||||
* {@link KeycloakAuthenticationProcessingFilter#DEFAULT_REQUEST_MATCHER default request matcher}.
|
|
||||||
*
|
|
||||||
* @param authenticationManager the {@link AuthenticationManager} to authenticate requests (cannot be null)
|
|
||||||
* @see KeycloakAuthenticationProcessingFilter#DEFAULT_REQUEST_MATCHER
|
|
||||||
*/
|
|
||||||
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
|
|
||||||
this(authenticationManager, DEFAULT_REQUEST_MATCHER);
|
|
||||||
setAuthenticationFailureHandler(new KeycloakAuthenticationFailureHandler());
|
|
||||||
setAuthenticationSuccessHandler(new KeycloakAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Keycloak authentication processing filter with given {@link AuthenticationManager} and
|
|
||||||
* {@link RequestMatcher}.
|
|
||||||
* <p>
|
|
||||||
* Note: the given request matcher must support matching the <code>Authorization</code> header if
|
|
||||||
* bearer token authentication is to be accepted.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param authenticationManager the {@link AuthenticationManager} to authenticate requests (cannot be null)
|
|
||||||
* @param requiresAuthenticationRequestMatcher the {@link RequestMatcher} used to determine if authentication
|
|
||||||
* is required (cannot be null)
|
|
||||||
*
|
|
||||||
* @see RequestHeaderRequestMatcher
|
|
||||||
* @see OrRequestMatcher
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager, RequestMatcher
|
|
||||||
requiresAuthenticationRequestMatcher) {
|
|
||||||
super(requiresAuthenticationRequestMatcher);
|
|
||||||
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
|
||||||
this.authenticationManager = authenticationManager;
|
|
||||||
super.setAuthenticationManager(authenticationManager);
|
|
||||||
super.setAllowSessionCreation(false);
|
|
||||||
super.setContinueChainBeforeSuccessfulAuthentication(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterPropertiesSet() {
|
|
||||||
adapterDeploymentContext = applicationContext.getBean(AdapterDeploymentContext.class);
|
|
||||||
super.afterPropertiesSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
|
||||||
throws AuthenticationException, IOException, ServletException {
|
|
||||||
|
|
||||||
log.debug("Attempting Keycloak authentication");
|
|
||||||
|
|
||||||
HttpFacade facade = new SimpleHttpFacade(request, response);
|
|
||||||
KeycloakDeployment deployment = adapterDeploymentContext.resolveDeployment(facade);
|
|
||||||
|
|
||||||
// using Spring authenticationFailureHandler
|
|
||||||
deployment.setDelegateBearerErrorResponseSending(true);
|
|
||||||
|
|
||||||
AdapterTokenStore tokenStore = adapterTokenStoreFactory.createAdapterTokenStore(deployment, request, response);
|
|
||||||
RequestAuthenticator authenticator
|
|
||||||
= requestAuthenticatorFactory.createRequestAuthenticator(facade, request, deployment, tokenStore, -1);
|
|
||||||
|
|
||||||
AuthOutcome result = authenticator.authenticate();
|
|
||||||
log.debug("Auth outcome: {}", result);
|
|
||||||
|
|
||||||
if (AuthOutcome.FAILED.equals(result)) {
|
|
||||||
AuthChallenge challenge = authenticator.getChallenge();
|
|
||||||
if (challenge != null) {
|
|
||||||
challenge.challenge(facade);
|
|
||||||
}
|
|
||||||
throw new KeycloakAuthenticationException("Invalid authorization header, see WWW-Authenticate header for details");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AuthOutcome.NOT_ATTEMPTED.equals(result)) {
|
|
||||||
AuthChallenge challenge = authenticator.getChallenge();
|
|
||||||
if (challenge != null) {
|
|
||||||
challenge.challenge(facade);
|
|
||||||
}
|
|
||||||
if (deployment.isBearerOnly()) {
|
|
||||||
// no redirection in this mode, throwing exception for the spring handler
|
|
||||||
throw new KeycloakAuthenticationException("Authorization header not found, see WWW-Authenticate header");
|
|
||||||
} else {
|
|
||||||
// let continue if challenged, it may redirect
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
|
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
|
|
||||||
return authenticationManager.authenticate(authentication);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
AuthChallenge challenge = authenticator.getChallenge();
|
|
||||||
if (challenge != null) {
|
|
||||||
challenge.challenge(facade);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
|
|
||||||
Authentication authResult) throws IOException, ServletException {
|
|
||||||
if (authResult instanceof KeycloakAuthenticationToken && ((KeycloakAuthenticationToken) authResult).isInteractive()) {
|
|
||||||
super.successfulAuthentication(request, response, chain, authResult);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Authentication success using bearer token/basic authentication. Updating SecurityContextHolder to contain: {}", authResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
|
||||||
context.setAuthentication(authResult);
|
|
||||||
SecurityContextHolder.setContext(context);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Fire event
|
|
||||||
if (this.eventPublisher != null) {
|
|
||||||
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
|
|
||||||
}
|
|
||||||
chain.doFilter(request, response);
|
|
||||||
} finally {
|
|
||||||
SecurityContextHolder.clearContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
AuthenticationException failed) throws IOException, ServletException {
|
|
||||||
super.unsuccessfulAuthentication(request, response, failed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
|
||||||
this.applicationContext = applicationContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the adapter token store factory to use when creating per-request adapter token stores.
|
|
||||||
*
|
|
||||||
* @param adapterTokenStoreFactory the <code>AdapterTokenStoreFactory</code> to use
|
|
||||||
*/
|
|
||||||
public void setAdapterTokenStoreFactory(AdapterTokenStoreFactory adapterTokenStoreFactory) {
|
|
||||||
Assert.notNull(adapterTokenStoreFactory, "AdapterTokenStoreFactory cannot be null");
|
|
||||||
this.adapterTokenStoreFactory = adapterTokenStoreFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This filter does not support explicitly enabling session creation.
|
|
||||||
*
|
|
||||||
* @throws UnsupportedOperationException this filter does not support explicitly enabling session creation.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public final void setAllowSessionCreation(boolean allowSessionCreation) {
|
|
||||||
throw new UnsupportedOperationException("This filter does not support explicitly setting a session creation policy");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This filter does not support explicitly setting a continue chain before success policy
|
|
||||||
*
|
|
||||||
* @throws UnsupportedOperationException this filter does not support explicitly setting a continue chain before success policy
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public final void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
|
|
||||||
throw new UnsupportedOperationException("This filter does not support explicitly setting a continue chain before success policy");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the request authenticator factory to use when creating per-request authenticators.
|
|
||||||
*
|
|
||||||
* @param requestAuthenticatorFactory the <code>RequestAuthenticatorFactory</code> to use
|
|
||||||
*/
|
|
||||||
public void setRequestAuthenticatorFactory(RequestAuthenticatorFactory requestAuthenticatorFactory) {
|
|
||||||
Assert.notNull(requestAuthenticatorFactory, "RequestAuthenticatorFactory cannot be null");
|
|
||||||
this.requestAuthenticatorFactory = requestAuthenticatorFactory;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +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.springsecurity.filter;
|
|
||||||
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSRF protection matcher that allows administrative POST requests from the Keycloak server.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakCsrfRequestMatcher implements RequestMatcher {
|
|
||||||
|
|
||||||
private static final List<String> ALLOWED_ENDPOINTS = Arrays.asList(
|
|
||||||
AdapterConstants.K_LOGOUT,
|
|
||||||
AdapterConstants.K_PUSH_NOT_BEFORE,
|
|
||||||
AdapterConstants.K_QUERY_BEARER_TOKEN,
|
|
||||||
AdapterConstants.K_TEST_AVAILABLE
|
|
||||||
);
|
|
||||||
|
|
||||||
private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
|
|
||||||
private Pattern allowedEndpoints = Pattern.compile(String.format("^\\/(%s)$", StringUtils.arrayToDelimitedString(ALLOWED_ENDPOINTS.toArray(), "|")));
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.servlet.http.HttpServletRequest)
|
|
||||||
*/
|
|
||||||
public boolean matches(HttpServletRequest request) {
|
|
||||||
String uri = request.getRequestURI().replaceFirst(request.getContextPath(), "");
|
|
||||||
return !allowedEndpoints.matcher(uri).matches() && !allowedMethods.matcher(request.getMethod()).matches();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,127 +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.springsecurity.filter;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
|
||||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
|
||||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.ApplicationContextAware;
|
|
||||||
import org.springframework.web.filter.GenericFilterBean;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exposes a Keycloak adapter {@link PreAuthActionsHandler} as a Spring Security filter.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class KeycloakPreAuthActionsFilter extends GenericFilterBean implements ApplicationContextAware {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(KeycloakPreAuthActionsFilter.class);
|
|
||||||
|
|
||||||
private NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
|
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
private AdapterDeploymentContext deploymentContext;
|
|
||||||
private UserSessionManagement userSessionManagement;
|
|
||||||
private PreAuthActionsHandlerFactory preAuthActionsHandlerFactory = new PreAuthActionsHandlerFactory();
|
|
||||||
|
|
||||||
public KeycloakPreAuthActionsFilter() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeycloakPreAuthActionsFilter(UserSessionManagement userSessionManagement) {
|
|
||||||
this.userSessionManagement = userSessionManagement;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initFilterBean() throws ServletException {
|
|
||||||
deploymentContext = applicationContext.getBean(AdapterDeploymentContext.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
log.debug("Unregistering deployment");
|
|
||||||
nodesRegistrationManagement.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
|
|
||||||
HttpFacade facade = new SimpleHttpFacade((HttpServletRequest)request, (HttpServletResponse)response);
|
|
||||||
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
|
||||||
|
|
||||||
if (deployment == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deployment.isConfigured()) {
|
|
||||||
nodesRegistrationManagement.tryRegister(deploymentContext.resolveDeployment(facade));
|
|
||||||
}
|
|
||||||
|
|
||||||
PreAuthActionsHandler handler = preAuthActionsHandlerFactory.createPreAuthActionsHandler(facade);
|
|
||||||
if (handler.handleRequest()) {
|
|
||||||
log.debug("Pre-auth filter handled request: {}", ((HttpServletRequest) request).getRequestURI());
|
|
||||||
} else {
|
|
||||||
chain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserSessionManagement(UserSessionManagement userSessionManagement) {
|
|
||||||
this.userSessionManagement = userSessionManagement;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
|
||||||
this.applicationContext = applicationContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setNodesRegistrationManagement(NodesRegistrationManagement nodesRegistrationManagement) {
|
|
||||||
this.nodesRegistrationManagement = nodesRegistrationManagement;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPreAuthActionsHandlerFactory(PreAuthActionsHandlerFactory preAuthActionsHandlerFactory) {
|
|
||||||
this.preAuthActionsHandlerFactory = preAuthActionsHandlerFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates {@link PreAuthActionsHandler}s.
|
|
||||||
*
|
|
||||||
* Package-private class to enable mocking.
|
|
||||||
*/
|
|
||||||
class PreAuthActionsHandlerFactory {
|
|
||||||
PreAuthActionsHandler createPreAuthActionsHandler(HttpFacade facade) {
|
|
||||||
return new PreAuthActionsHandler(userSessionManagement, deploymentContext, facade);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.keycloak.adapters.springsecurity.filter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.AdapterTokenStoreFactory;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.SpringSecurityAdapterTokenStoreFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.ApplicationContextAware;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.web.filter.GenericFilterBean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakSecurityContextRequestFilter extends GenericFilterBean implements ApplicationContextAware {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(KeycloakSecurityContextRequestFilter.class);
|
|
||||||
|
|
||||||
private static final String FILTER_APPLIED = KeycloakSecurityContext.class.getPackage().getName() + ".token-refreshed";
|
|
||||||
private final AdapterTokenStoreFactory adapterTokenStoreFactory = new SpringSecurityAdapterTokenStoreFactory();
|
|
||||||
|
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
private AdapterDeploymentContext deploymentContext;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
|
|
||||||
if (request.getAttribute(FILTER_APPLIED) != null) {
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
|
|
||||||
|
|
||||||
KeycloakSecurityContext keycloakSecurityContext = getKeycloakSecurityContext();
|
|
||||||
|
|
||||||
if (keycloakSecurityContext instanceof RefreshableKeycloakSecurityContext) {
|
|
||||||
RefreshableKeycloakSecurityContext refreshableSecurityContext = (RefreshableKeycloakSecurityContext) keycloakSecurityContext;
|
|
||||||
KeycloakDeployment deployment = resolveDeployment(request, response);
|
|
||||||
|
|
||||||
// just in case session got serialized
|
|
||||||
if (refreshableSecurityContext.getDeployment()==null) {
|
|
||||||
log.trace("Recreating missing deployment and related fields in deserialized context");
|
|
||||||
AdapterTokenStore adapterTokenStore = adapterTokenStoreFactory.createAdapterTokenStore(deployment, (HttpServletRequest) request,
|
|
||||||
(HttpServletResponse) response);
|
|
||||||
refreshableSecurityContext.setCurrentRequestInfo(deployment, adapterTokenStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!refreshableSecurityContext.isActive() || deployment.isAlwaysRefreshToken()) {
|
|
||||||
if (refreshableSecurityContext.refreshExpiredToken(false)) {
|
|
||||||
request.setAttribute(KeycloakSecurityContext.class.getName(), refreshableSecurityContext);
|
|
||||||
} else {
|
|
||||||
clearAuthenticationContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request.setAttribute(KeycloakSecurityContext.class.getName(), keycloakSecurityContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initFilterBean() {
|
|
||||||
deploymentContext = applicationContext.getBean(AdapterDeploymentContext.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
|
||||||
this.applicationContext = applicationContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeycloakSecurityContext getKeycloakSecurityContext() {
|
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
|
|
||||||
if (authentication != null) {
|
|
||||||
Object principal = authentication.getPrincipal();
|
|
||||||
|
|
||||||
if (principal instanceof KeycloakPrincipal) {
|
|
||||||
return KeycloakPrincipal.class.cast(principal).getKeycloakSecurityContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeycloakDeployment resolveDeployment(ServletRequest servletRequest, ServletResponse servletResponse) {
|
|
||||||
return deploymentContext.resolveDeployment(new SimpleHttpFacade(HttpServletRequest.class.cast(servletRequest), HttpServletResponse.class.cast(servletResponse)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearAuthenticationContext() {
|
|
||||||
SecurityContextHolder.clearContext();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.keycloak.adapters.springsecurity.filter;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spring RequestMatcher that checks for the presence of a query parameter.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:glavoie@gmail.com">Gabriel Lavoie</a>
|
|
||||||
*/
|
|
||||||
public class QueryParamPresenceRequestMatcher implements RequestMatcher {
|
|
||||||
private String param;
|
|
||||||
|
|
||||||
public QueryParamPresenceRequestMatcher(String param) {
|
|
||||||
this.param = param;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(HttpServletRequest httpServletRequest) {
|
|
||||||
return param != null && httpServletRequest.getParameter(param) != null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides Spring Security filters for Keycloak.
|
|
||||||
*/
|
|
||||||
package org.keycloak.adapters.springsecurity.filter;
|
|
|
@ -1,78 +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.springsecurity.management;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
import org.springframework.context.ApplicationListener;
|
|
||||||
import org.springframework.security.web.session.HttpSessionCreatedEvent;
|
|
||||||
import org.springframework.security.web.session.HttpSessionDestroyedEvent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User session manager for handling logout of Spring Secured sessions.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class HttpSessionManager implements ApplicationListener<ApplicationEvent>, UserSessionManagement {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(HttpSessionManager.class);
|
|
||||||
private SessionManagementStrategy sessions = new LocalSessionManagementStrategy();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onApplicationEvent(ApplicationEvent event) {
|
|
||||||
if (event instanceof HttpSessionCreatedEvent) {
|
|
||||||
HttpSessionCreatedEvent e = (HttpSessionCreatedEvent) event;
|
|
||||||
HttpSession session = e.getSession();
|
|
||||||
log.debug("Session created: {}", session.getId());
|
|
||||||
sessions.store(session);
|
|
||||||
} else if (event instanceof HttpSessionDestroyedEvent) {
|
|
||||||
HttpSessionDestroyedEvent e = (HttpSessionDestroyedEvent) event;
|
|
||||||
HttpSession session = e.getSession();
|
|
||||||
sessions.remove(session.getId());
|
|
||||||
log.debug("Session destroyed: {}", session.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logoutAll() {
|
|
||||||
log.info("Received request to log out all users.");
|
|
||||||
for (HttpSession session : sessions.getAll()) {
|
|
||||||
session.invalidate();
|
|
||||||
}
|
|
||||||
sessions.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logoutHttpSessions(List<String> ids) {
|
|
||||||
log.info("Received request to log out {} session(s): {}", ids.size(), ids);
|
|
||||||
for (String id : ids) {
|
|
||||||
HttpSession session = sessions.remove(id);
|
|
||||||
if (session != null) {
|
|
||||||
session.invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,51 +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.springsecurity.management;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by scott on 4/24/15.
|
|
||||||
*/
|
|
||||||
public class LocalSessionManagementStrategy implements SessionManagementStrategy {
|
|
||||||
|
|
||||||
private final Map<String, HttpSession> sessions = new ConcurrentHashMap<String, HttpSession>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
sessions.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<HttpSession> getAll() {
|
|
||||||
return sessions.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void store(HttpSession session) {
|
|
||||||
sessions.put(session.getId(), session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpSession remove(String id) {
|
|
||||||
return sessions.remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +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.springsecurity.management;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a session management strategy.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public interface SessionManagementStrategy {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all sessions.
|
|
||||||
*/
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a collection containing all sessions.
|
|
||||||
*
|
|
||||||
* @return a <code>Collection</code> of all known <code>HttpSession</code>s, if any;
|
|
||||||
* an empty <code>Collection</code> otherwise
|
|
||||||
*/
|
|
||||||
Collection<HttpSession> getAll();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the given session.
|
|
||||||
*
|
|
||||||
* @param session the <code>HttpSession</code> to store (required)
|
|
||||||
*/
|
|
||||||
void store(HttpSession session);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The unique identifier for the session to remove.
|
|
||||||
*
|
|
||||||
* @param id the unique identifier for the session to remove (required)
|
|
||||||
* @return the <code>HttpSession</code> if it exists; <code>null</code> otherwise
|
|
||||||
*/
|
|
||||||
HttpSession remove(String id);
|
|
||||||
}
|
|
|
@ -1,21 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a Keycloak adapter for Spring Security.
|
|
||||||
*/
|
|
||||||
package org.keycloak.adapters.springsecurity;
|
|
|
@ -1,44 +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.springsecurity.registration;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages registration of application nodes.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public interface NodeManager {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the given deployment with the Keycloak server.
|
|
||||||
*
|
|
||||||
* @param deployment the deployment to register (required)
|
|
||||||
*/
|
|
||||||
void register(KeycloakDeployment deployment);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters the give deployment from the Keycloak server
|
|
||||||
* .
|
|
||||||
* @param deployment the deployment to unregister (required)
|
|
||||||
*/
|
|
||||||
void unregister(KeycloakDeployment deployment);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,45 +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.springsecurity.token;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a per-request adapter token store.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
*/
|
|
||||||
public interface AdapterTokenStoreFactory {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new {@link AdapterTokenStore} for the given {@link KeycloakDeployment} and {@link HttpServletRequest request}.
|
|
||||||
*
|
|
||||||
* @param deployment the <code>KeycloakDeployment</code> (required)
|
|
||||||
* @param request the current <code>HttpServletRequest</code> (required)
|
|
||||||
* @param response the current <code>HttpServletResponse</code> (required when using cookies)
|
|
||||||
*
|
|
||||||
* @return a new <code>AdapterTokenStore</code> for the given <code>deployment</code>, <code>request</code> and <code>response</code>
|
|
||||||
* @throws IllegalArgumentException if any required parameter is <code>null</code>
|
|
||||||
*/
|
|
||||||
AdapterTokenStore createAdapterTokenStore(KeycloakDeployment deployment, HttpServletRequest request, HttpServletResponse response);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,82 +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.springsecurity.token;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the token for a Keycloak authentication request or for an authenticated principal once the request has been
|
|
||||||
* processed by the {@link AuthenticationManager#authenticate(Authentication)}.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class KeycloakAuthenticationToken extends AbstractAuthenticationToken implements Authentication {
|
|
||||||
|
|
||||||
private Principal principal;
|
|
||||||
private boolean interactive;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new, unauthenticated Keycloak security token for the given account.
|
|
||||||
*/
|
|
||||||
public KeycloakAuthenticationToken(KeycloakAccount account, boolean interactive) {
|
|
||||||
super(null);
|
|
||||||
Assert.notNull(account, "KeycloakAccount cannot be null");
|
|
||||||
Assert.notNull(account.getPrincipal(), "KeycloakAccount.getPrincipal() cannot be null");
|
|
||||||
this.principal = account.getPrincipal();
|
|
||||||
this.setDetails(account);
|
|
||||||
this.interactive = interactive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeycloakAuthenticationToken(KeycloakAccount account, boolean interactive, Collection<? extends GrantedAuthority> authorities) {
|
|
||||||
super(authorities);
|
|
||||||
Assert.notNull(account, "KeycloakAccount cannot be null");
|
|
||||||
Assert.notNull(account.getPrincipal(), "KeycloakAccount.getPrincipal() cannot be null");
|
|
||||||
this.principal = account.getPrincipal();
|
|
||||||
this.setDetails(account);
|
|
||||||
this.interactive = interactive;
|
|
||||||
setAuthenticated(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getCredentials() {
|
|
||||||
return this.getAccount().getKeycloakSecurityContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getPrincipal() {
|
|
||||||
return principal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OidcKeycloakAccount getAccount() {
|
|
||||||
return (OidcKeycloakAccount) this.getDetails();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInteractive() {
|
|
||||||
return interactive;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +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.springsecurity.token;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.enums.TokenStore;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link AdapterTokenStoreFactory} that returns a new {@link SpringSecurityTokenStore} for each request.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
*/
|
|
||||||
public class SpringSecurityAdapterTokenStoreFactory implements AdapterTokenStoreFactory {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AdapterTokenStore createAdapterTokenStore(KeycloakDeployment deployment, HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
Assert.notNull(deployment, "KeycloakDeployment is required");
|
|
||||||
if (deployment.getTokenStore() == TokenStore.COOKIE) {
|
|
||||||
return new SpringSecurityCookieTokenStore(deployment, request, response);
|
|
||||||
}
|
|
||||||
return new SpringSecurityTokenStore(deployment, request);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,141 +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.springsecurity.token;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
|
||||||
import org.keycloak.adapters.CookieTokenStore;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension of {@link SpringSecurityTokenStore} that stores the obtains tokens in a cookie.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:scranen@gmail.com">Sjoerd Cranen</a>
|
|
||||||
*/
|
|
||||||
public class SpringSecurityCookieTokenStore extends SpringSecurityTokenStore {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(SpringSecurityCookieTokenStore.class);
|
|
||||||
|
|
||||||
private final KeycloakDeployment deployment;
|
|
||||||
private final HttpFacade facade;
|
|
||||||
private volatile boolean cookieChecked = false;
|
|
||||||
|
|
||||||
public SpringSecurityCookieTokenStore(
|
|
||||||
KeycloakDeployment deployment,
|
|
||||||
HttpServletRequest request,
|
|
||||||
HttpServletResponse response) {
|
|
||||||
super(deployment, request);
|
|
||||||
Assert.notNull(response, "HttpServletResponse is required");
|
|
||||||
this.deployment = deployment;
|
|
||||||
this.facade = new SimpleHttpFacade(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkCurrentToken() {
|
|
||||||
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal =
|
|
||||||
checkPrincipalFromCookie();
|
|
||||||
if (principal != null) {
|
|
||||||
final RefreshableKeycloakSecurityContext securityContext =
|
|
||||||
principal.getKeycloakSecurityContext();
|
|
||||||
KeycloakSecurityContext current = ((OIDCHttpFacade) facade).getSecurityContext();
|
|
||||||
if (current != null) {
|
|
||||||
securityContext.setAuthorizationContext(current.getAuthorizationContext());
|
|
||||||
}
|
|
||||||
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
|
|
||||||
final OidcKeycloakAccount account =
|
|
||||||
new SimpleKeycloakAccount(principal, roles, securityContext);
|
|
||||||
SecurityContextHolder.getContext()
|
|
||||||
.setAuthentication(new KeycloakAuthenticationToken(account, false));
|
|
||||||
} else {
|
|
||||||
super.checkCurrentToken();
|
|
||||||
}
|
|
||||||
cookieChecked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCached(RequestAuthenticator authenticator) {
|
|
||||||
if (!cookieChecked) {
|
|
||||||
checkCurrentToken();
|
|
||||||
}
|
|
||||||
return super.isCached(authenticator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
|
|
||||||
super.refreshCallback(securityContext);
|
|
||||||
CookieTokenStore.setTokenCookie(deployment, facade, securityContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveAccountInfo(OidcKeycloakAccount account) {
|
|
||||||
super.saveAccountInfo(account);
|
|
||||||
RefreshableKeycloakSecurityContext securityContext =
|
|
||||||
(RefreshableKeycloakSecurityContext) account.getKeycloakSecurityContext();
|
|
||||||
CookieTokenStore.setTokenCookie(deployment, facade, securityContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logout() {
|
|
||||||
CookieTokenStore.removeCookie(deployment, facade);
|
|
||||||
super.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify if we already have authenticated and active principal in cookie. Perform refresh if
|
|
||||||
* it's not active
|
|
||||||
*
|
|
||||||
* @return valid principal
|
|
||||||
*/
|
|
||||||
private KeycloakPrincipal<RefreshableKeycloakSecurityContext> checkPrincipalFromCookie() {
|
|
||||||
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal =
|
|
||||||
CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
|
|
||||||
if (principal == null) {
|
|
||||||
logger.debug("Account was not in cookie or was invalid");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext();
|
|
||||||
|
|
||||||
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal;
|
|
||||||
boolean success = session.refreshExpiredToken(false);
|
|
||||||
if (success && session.isActive()) {
|
|
||||||
refreshCallback(session);
|
|
||||||
return principal;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
"Cleanup and expire cookie for user {} after failed refresh", principal.getName());
|
|
||||||
CookieTokenStore.removeCookie(deployment, facade);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,142 +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.springsecurity.token;
|
|
||||||
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple Spring {@link SecurityContext security context} aware {@link AdapterTokenStore adapter token store}.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class SpringSecurityTokenStore implements AdapterTokenStore {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(SpringSecurityTokenStore.class);
|
|
||||||
|
|
||||||
private final KeycloakDeployment deployment;
|
|
||||||
private final HttpServletRequest request;
|
|
||||||
|
|
||||||
public SpringSecurityTokenStore(KeycloakDeployment deployment, HttpServletRequest request) {
|
|
||||||
Assert.notNull(deployment, "KeycloakDeployment is required");
|
|
||||||
Assert.notNull(request, "HttpServletRequest is required");
|
|
||||||
this.deployment = deployment;
|
|
||||||
this.request = request;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkCurrentToken() {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCached(RequestAuthenticator authenticator) {
|
|
||||||
|
|
||||||
logger.debug("Checking if {} is cached", authenticator);
|
|
||||||
SecurityContext context = SecurityContextHolder.getContext();
|
|
||||||
KeycloakAuthenticationToken token;
|
|
||||||
KeycloakSecurityContext keycloakSecurityContext;
|
|
||||||
|
|
||||||
if (context == null || context.getAuthentication() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!KeycloakAuthenticationToken.class.isAssignableFrom(context.getAuthentication().getClass())) {
|
|
||||||
logger.warn("Expected a KeycloakAuthenticationToken, but found {}", context.getAuthentication());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Remote logged in already. Establishing state from security context.");
|
|
||||||
token = (KeycloakAuthenticationToken) context.getAuthentication();
|
|
||||||
keycloakSecurityContext = token.getAccount().getKeycloakSecurityContext();
|
|
||||||
|
|
||||||
if (!deployment.getRealm().equals(keycloakSecurityContext.getRealm())) {
|
|
||||||
logger.debug("Account from security context is from a different realm than for the request.");
|
|
||||||
logout();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keycloakSecurityContext.getToken().isExpired()) {
|
|
||||||
logger.warn("Security token expired ... not returning from cache");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.setAttribute(KeycloakSecurityContext.class.getName(), keycloakSecurityContext);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveAccountInfo(OidcKeycloakAccount account) {
|
|
||||||
|
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
|
|
||||||
if (authentication != null) {
|
|
||||||
throw new IllegalStateException(String.format("Went to save Keycloak account %s, but already have %s", account, authentication));
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Saving account info {}", account);
|
|
||||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
|
||||||
context.setAuthentication(new KeycloakAuthenticationToken(account, true));
|
|
||||||
SecurityContextHolder.setContext(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logout() {
|
|
||||||
|
|
||||||
logger.debug("Handling logout request");
|
|
||||||
HttpSession session = request.getSession(false);
|
|
||||||
|
|
||||||
if (session != null) {
|
|
||||||
session.setAttribute(KeycloakSecurityContext.class.getName(), null);
|
|
||||||
session.invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
SecurityContextHolder.clearContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveRequest() {
|
|
||||||
// no-op, Spring Security will handle this
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean restoreRequest() {
|
|
||||||
// no-op, Spring Security will handle this
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +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.springsecurity;
|
|
||||||
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.rules.ExpectedException;
|
|
||||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
|
|
||||||
public class AdapterDeploymentContextFactoryBeanTest {
|
|
||||||
@Rule
|
|
||||||
public ExpectedException expectedException = ExpectedException.none();
|
|
||||||
|
|
||||||
private AdapterDeploymentContextFactoryBean adapterDeploymentContextFactoryBean;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void should_create_adapter_deployment_context_from_configuration_file() throws Exception {
|
|
||||||
// given:
|
|
||||||
adapterDeploymentContextFactoryBean = new AdapterDeploymentContextFactoryBean(getCorrectResource());
|
|
||||||
|
|
||||||
// when:
|
|
||||||
adapterDeploymentContextFactoryBean.afterPropertiesSet();
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertNotNull(adapterDeploymentContextFactoryBean.getObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Resource getCorrectResource() {
|
|
||||||
return new ClassPathResource("keycloak.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void should_throw_exception_when_configuration_file_was_not_found() throws Exception {
|
|
||||||
// given:
|
|
||||||
adapterDeploymentContextFactoryBean = new AdapterDeploymentContextFactoryBean(getEmptyResource());
|
|
||||||
|
|
||||||
// then:
|
|
||||||
expectedException.expect(FileNotFoundException.class);
|
|
||||||
expectedException.expectMessage("Unable to locate Keycloak configuration file: no-file.json");
|
|
||||||
|
|
||||||
// when:
|
|
||||||
adapterDeploymentContextFactoryBean.afterPropertiesSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Resource getEmptyResource() {
|
|
||||||
return new ClassPathResource("no-file.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void should_create_adapter_deployment_context_from_keycloak_config_resolver() throws Exception {
|
|
||||||
// given:
|
|
||||||
adapterDeploymentContextFactoryBean = new AdapterDeploymentContextFactoryBean(getKeycloakConfigResolver());
|
|
||||||
|
|
||||||
// when:
|
|
||||||
adapterDeploymentContextFactoryBean.afterPropertiesSet();
|
|
||||||
|
|
||||||
// then:
|
|
||||||
assertNotNull(adapterDeploymentContextFactoryBean.getObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeycloakConfigResolver getKeycloakConfigResolver() {
|
|
||||||
return new KeycloakConfigResolver() {
|
|
||||||
@Override
|
|
||||||
public KeycloakDeployment resolve(HttpFacade.Request facade) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import org.apache.http.HttpHeaders;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP header inspecting API request matcher tests.
|
|
||||||
*/
|
|
||||||
public class HttpHeaderInspectingApiRequestMatcherTest {
|
|
||||||
|
|
||||||
private RequestMatcher apiRequestMatcher = new HttpHeaderInspectingApiRequestMatcher();
|
|
||||||
private MockHttpServletRequest request;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
request = new MockHttpServletRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchesBrowserRequest() throws Exception {
|
|
||||||
request.addHeader(HttpHeaders.ACCEPT, "application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
|
||||||
assertFalse(apiRequestMatcher.matches(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchesRequestedWith() throws Exception {
|
|
||||||
request.addHeader(
|
|
||||||
HttpHeaderInspectingApiRequestMatcher.X_REQUESTED_WITH_HEADER,
|
|
||||||
HttpHeaderInspectingApiRequestMatcher.X_REQUESTED_WITH_HEADER_AJAX_VALUE);
|
|
||||||
|
|
||||||
assertTrue(apiRequestMatcher.matches(request));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,134 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotEquals;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import org.apache.http.HttpHeaders;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keycloak authentication entry point tests.
|
|
||||||
*/
|
|
||||||
public class KeycloakAuthenticationEntryPointTest {
|
|
||||||
|
|
||||||
private KeycloakAuthenticationEntryPoint authenticationEntryPoint;
|
|
||||||
private MockHttpServletRequest request;
|
|
||||||
private MockHttpServletResponse response;
|
|
||||||
@Mock
|
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private AdapterDeploymentContext adapterDeploymentContext;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakDeployment keycloakDeployment;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private RequestMatcher requestMatcher;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
authenticationEntryPoint = new KeycloakAuthenticationEntryPoint(adapterDeploymentContext);
|
|
||||||
request = new MockHttpServletRequest();
|
|
||||||
response = new MockHttpServletResponse();
|
|
||||||
when(applicationContext.getBean(eq(AdapterDeploymentContext.class))).thenReturn(adapterDeploymentContext);
|
|
||||||
when(adapterDeploymentContext.resolveDeployment(any(HttpFacade.class))).thenReturn(keycloakDeployment);
|
|
||||||
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCommenceWithRedirect() throws Exception {
|
|
||||||
configureBrowserRequest();
|
|
||||||
authenticationEntryPoint.commence(request, response, null);
|
|
||||||
assertEquals(HttpStatus.FOUND.value(), response.getStatus());
|
|
||||||
assertEquals(KeycloakAuthenticationEntryPoint.DEFAULT_LOGIN_URI, response.getHeader("Location"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCommenceWithRedirectAndQueryParameters() throws Exception {
|
|
||||||
configureBrowserRequest();
|
|
||||||
request.setQueryString("prompt=login");
|
|
||||||
authenticationEntryPoint.commence(request, response, null);
|
|
||||||
assertEquals(HttpStatus.FOUND.value(), response.getStatus());
|
|
||||||
assertNotEquals(KeycloakAuthenticationEntryPoint.DEFAULT_LOGIN_URI, response.getHeader("Location"));
|
|
||||||
assertThat(response.getHeader("Location"), containsString(KeycloakAuthenticationEntryPoint.DEFAULT_LOGIN_URI));
|
|
||||||
assertThat(response.getHeader("Location"), containsString("prompt=login"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCommenceWithRedirectNotRootContext() throws Exception {
|
|
||||||
configureBrowserRequest();
|
|
||||||
String contextPath = "/foo";
|
|
||||||
request.setContextPath(contextPath);
|
|
||||||
authenticationEntryPoint.commence(request, response, null);
|
|
||||||
assertEquals(HttpStatus.FOUND.value(), response.getStatus());
|
|
||||||
assertEquals(contextPath + KeycloakAuthenticationEntryPoint.DEFAULT_LOGIN_URI, response.getHeader("Location"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCommenceWithUnauthorizedWithAccept() throws Exception {
|
|
||||||
request.addHeader(HttpHeaders.ACCEPT, "application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
|
||||||
authenticationEntryPoint.commence(request, response, null);
|
|
||||||
assertEquals(HttpStatus.FOUND.value(), response.getStatus());
|
|
||||||
assertNull(response.getHeader(HttpHeaders.WWW_AUTHENTICATE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSetLoginUri() throws Exception {
|
|
||||||
configureBrowserRequest();
|
|
||||||
final String logoutUri = "/foo";
|
|
||||||
authenticationEntryPoint.setLoginUri(logoutUri);
|
|
||||||
authenticationEntryPoint.commence(request, response, null);
|
|
||||||
assertEquals(HttpStatus.FOUND.value(), response.getStatus());
|
|
||||||
assertEquals(logoutUri, response.getHeader("Location"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCommenceWithCustomRequestMatcher() throws Exception {
|
|
||||||
new KeycloakAuthenticationEntryPoint(adapterDeploymentContext, requestMatcher)
|
|
||||||
.commence(request, response, null);
|
|
||||||
|
|
||||||
verify(requestMatcher).matches(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void configureBrowserRequest() {
|
|
||||||
request.addHeader(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.mockito.internal.util.collections.Sets;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
|
||||||
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
|
|
||||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keycloak authentication provider tests.
|
|
||||||
*/
|
|
||||||
public class KeycloakAuthenticationProviderTest {
|
|
||||||
private KeycloakAuthenticationProvider provider = new KeycloakAuthenticationProvider();
|
|
||||||
private KeycloakAuthenticationToken token;
|
|
||||||
private KeycloakAuthenticationToken interactiveToken;
|
|
||||||
private Set<String> roles = Sets.newSet("user", "admin");
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
Principal principal = mock(Principal.class);
|
|
||||||
RefreshableKeycloakSecurityContext securityContext = mock(RefreshableKeycloakSecurityContext.class);
|
|
||||||
KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, securityContext);
|
|
||||||
|
|
||||||
token = new KeycloakAuthenticationToken(account, false);
|
|
||||||
interactiveToken = new KeycloakAuthenticationToken(account, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAuthenticate() throws Exception {
|
|
||||||
assertAuthenticationResult(provider.authenticate(token));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAuthenticateInteractive() throws Exception {
|
|
||||||
assertAuthenticationResult(provider.authenticate(interactiveToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSupports() throws Exception {
|
|
||||||
assertTrue(provider.supports(KeycloakAuthenticationToken.class));
|
|
||||||
assertFalse(provider.supports(PreAuthenticatedAuthenticationToken.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGrantedAuthoritiesMapper() throws Exception {
|
|
||||||
SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
|
|
||||||
grantedAuthorityMapper.setPrefix("ROLE_");
|
|
||||||
grantedAuthorityMapper.setConvertToUpperCase(true);
|
|
||||||
provider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
|
|
||||||
|
|
||||||
Authentication result = provider.authenticate(token);
|
|
||||||
assertEquals(Sets.newSet("ROLE_USER", "ROLE_ADMIN"),
|
|
||||||
AuthorityUtils.authorityListToSet(result.getAuthorities()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertAuthenticationResult(Authentication result) {
|
|
||||||
assertNotNull(result);
|
|
||||||
assertEquals(roles, AuthorityUtils.authorityListToSet(result.getAuthorities()));
|
|
||||||
assertTrue(result.isAuthenticated());
|
|
||||||
assertNotNull(result.getPrincipal());
|
|
||||||
assertNotNull(result.getCredentials());
|
|
||||||
assertNotNull(result.getDetails());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,125 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
|
||||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
|
||||||
import org.springframework.security.authentication.RememberMeAuthenticationToken;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.any;
|
|
||||||
import static org.mockito.Mockito.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keycloak logout handler tests.
|
|
||||||
*/
|
|
||||||
public class KeycloakLogoutHandlerTest {
|
|
||||||
|
|
||||||
private KeycloakAuthenticationToken keycloakAuthenticationToken;
|
|
||||||
private KeycloakLogoutHandler keycloakLogoutHandler;
|
|
||||||
|
|
||||||
private MockHttpServletRequest request;
|
|
||||||
private MockHttpServletResponse response;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private AdapterDeploymentContext adapterDeploymentContext;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private OidcKeycloakAccount keycloakAccount;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakDeployment keycloakDeployment;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private RefreshableKeycloakSecurityContext session;
|
|
||||||
|
|
||||||
private Collection<KeycloakRole> authorities = Collections.singleton(new KeycloakRole(UUID.randomUUID().toString()));
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
keycloakAuthenticationToken = mock(KeycloakAuthenticationToken.class);
|
|
||||||
keycloakLogoutHandler = new KeycloakLogoutHandler(adapterDeploymentContext);
|
|
||||||
request = new MockHttpServletRequest();
|
|
||||||
response = new MockHttpServletResponse();
|
|
||||||
|
|
||||||
when(adapterDeploymentContext.resolveDeployment(any(HttpFacade.class))).thenReturn(keycloakDeployment);
|
|
||||||
when(keycloakAuthenticationToken.getAccount()).thenReturn(keycloakAccount);
|
|
||||||
when(keycloakAccount.getKeycloakSecurityContext()).thenReturn(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLogout() throws Exception {
|
|
||||||
keycloakLogoutHandler.logout(request, response, keycloakAuthenticationToken);
|
|
||||||
verify(session).logout(eq(keycloakDeployment));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLogoutAnonymousAuthentication() throws Exception {
|
|
||||||
Authentication authentication = new AnonymousAuthenticationToken(UUID.randomUUID().toString(), UUID.randomUUID().toString(), authorities);
|
|
||||||
keycloakLogoutHandler.logout(request, response, authentication);
|
|
||||||
verifyZeroInteractions(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLogoutUsernamePasswordAuthentication() throws Exception {
|
|
||||||
Authentication authentication = new UsernamePasswordAuthenticationToken(UUID.randomUUID().toString(), UUID.randomUUID().toString(), authorities);
|
|
||||||
keycloakLogoutHandler.logout(request, response, authentication);
|
|
||||||
verifyZeroInteractions(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLogoutRememberMeAuthentication() throws Exception {
|
|
||||||
Authentication authentication = new RememberMeAuthenticationToken(UUID.randomUUID().toString(), UUID.randomUUID().toString(), authorities);
|
|
||||||
keycloakLogoutHandler.logout(request, response, authentication);
|
|
||||||
verifyZeroInteractions(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLogoutNullAuthentication() throws Exception {
|
|
||||||
keycloakLogoutHandler.logout(request, response, null);
|
|
||||||
verifyZeroInteractions(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testHandleSingleSignOut() throws Exception {
|
|
||||||
keycloakLogoutHandler.handleSingleSignOut(request, response, keycloakAuthenticationToken);
|
|
||||||
verify(session).logout(eq(keycloakDeployment));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,130 +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.springsecurity.authentication;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.mockito.internal.util.collections.Sets;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.mockito.Mockito.any;
|
|
||||||
import static org.mockito.Mockito.eq;
|
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spring Security request authenticator tests.
|
|
||||||
*/
|
|
||||||
public class SpringSecurityRequestAuthenticatorTest {
|
|
||||||
|
|
||||||
private SpringSecurityRequestAuthenticator authenticator;
|
|
||||||
|
|
||||||
private MockHttpServletRequest request;
|
|
||||||
private MockHttpServletResponse response;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakDeployment deployment;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private AdapterTokenStore tokenStore;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private AccessToken accessToken;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private AccessToken.Access access;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private RefreshableKeycloakSecurityContext refreshableKeycloakSecurityContext;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
request = spy(new MockHttpServletRequest());
|
|
||||||
response = new MockHttpServletResponse();
|
|
||||||
HttpFacade facade = new SimpleHttpFacade(request, response);
|
|
||||||
|
|
||||||
authenticator = new SpringSecurityRequestAuthenticator(facade, request, deployment, tokenStore, 443);
|
|
||||||
|
|
||||||
// mocks
|
|
||||||
when(principal.getKeycloakSecurityContext()).thenReturn(refreshableKeycloakSecurityContext);
|
|
||||||
|
|
||||||
when(refreshableKeycloakSecurityContext.getDeployment()).thenReturn(deployment);
|
|
||||||
when(refreshableKeycloakSecurityContext.getToken()).thenReturn(accessToken);
|
|
||||||
|
|
||||||
when(accessToken.getRealmAccess()).thenReturn(access);
|
|
||||||
when(access.getRoles()).thenReturn(Sets.newSet("user", "admin"));
|
|
||||||
|
|
||||||
when(deployment.isUseResourceRoleMappings()).thenReturn(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateOAuthAuthenticator() throws Exception {
|
|
||||||
OAuthRequestAuthenticator oathAuthenticator = authenticator.createOAuthAuthenticator();
|
|
||||||
assertNotNull(oathAuthenticator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCompleteOAuthAuthentication() throws Exception {
|
|
||||||
authenticator.completeOAuthAuthentication(principal);
|
|
||||||
verify(request).setAttribute(eq(KeycloakSecurityContext.class.getName()), eq(refreshableKeycloakSecurityContext));
|
|
||||||
verify(tokenStore).saveAccountInfo(any(OidcKeycloakAccount.class)); // FIXME: should verify account
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCompleteBearerAuthentication() throws Exception {
|
|
||||||
authenticator.completeBearerAuthentication(principal, "foo");
|
|
||||||
verify(request).setAttribute(eq(KeycloakSecurityContext.class.getName()), eq(refreshableKeycloakSecurityContext));
|
|
||||||
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
|
|
||||||
assertTrue(KeycloakAuthenticationToken.class.isAssignableFrom(SecurityContextHolder.getContext().getAuthentication().getClass()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetHttpSessionIdTrue() throws Exception {
|
|
||||||
String sessionId = authenticator.changeHttpSessionId(true);
|
|
||||||
assertNotNull(sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetHttpSessionIdFalse() throws Exception {
|
|
||||||
String sessionId = authenticator.changeHttpSessionId(false);
|
|
||||||
assertNull(sessionId);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +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.springsecurity.client;
|
|
||||||
|
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.mockito.Spy;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.mockito.Mockito.eq;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keycloak client request factory tests.
|
|
||||||
*/
|
|
||||||
public class KeycloakClientRequestFactoryTest {
|
|
||||||
|
|
||||||
@Spy
|
|
||||||
private KeycloakClientRequestFactory factory;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private OidcKeycloakAccount account;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakAuthenticationToken keycloakAuthenticationToken;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakSecurityContext keycloakSecurityContext;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private HttpUriRequest request;
|
|
||||||
|
|
||||||
private String bearerTokenString;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
bearerTokenString = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(keycloakAuthenticationToken);
|
|
||||||
when(keycloakAuthenticationToken.getAccount()).thenReturn(account);
|
|
||||||
when(account.getKeycloakSecurityContext()).thenReturn(keycloakSecurityContext);
|
|
||||||
when(keycloakSecurityContext.getTokenString()).thenReturn(bearerTokenString);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPostProcessHttpRequest() throws Exception {
|
|
||||||
factory.postProcessHttpRequest(request);
|
|
||||||
verify(factory).getKeycloakSecurityContext();
|
|
||||||
verify(request).setHeader(eq(KeycloakClientRequestFactory.AUTHORIZATION_HEADER), eq("Bearer " + bearerTokenString));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetKeycloakSecurityContext() throws Exception {
|
|
||||||
KeycloakSecurityContext context = factory.getKeycloakSecurityContext();
|
|
||||||
assertNotNull(context);
|
|
||||||
assertEquals(keycloakSecurityContext, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
|
||||||
public void testGetKeycloakSecurityContextInvalidAuthentication() throws Exception {
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(
|
|
||||||
new PreAuthenticatedAuthenticationToken("foo", "bar", Collections.singleton(new KeycloakRole("baz"))));
|
|
||||||
factory.getKeycloakSecurityContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
|
||||||
public void testGetKeycloakSecurityContextNullAuthentication() throws Exception {
|
|
||||||
SecurityContextHolder.clearContext();
|
|
||||||
factory.getKeycloakSecurityContext();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package org.keycloak.adapters.springsecurity.facade;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.mockito.internal.util.collections.Sets;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
public class SimpleHttpFacadeTest {
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
SecurityContext springSecurityContext = SecurityContextHolder.createEmptyContext();
|
|
||||||
SecurityContextHolder.setContext(springSecurityContext);
|
|
||||||
Set<String> roles = Sets.newSet("user");
|
|
||||||
Principal principal = mock(Principal.class);
|
|
||||||
RefreshableKeycloakSecurityContext keycloakSecurityContext = mock(RefreshableKeycloakSecurityContext.class);
|
|
||||||
KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, keycloakSecurityContext);
|
|
||||||
KeycloakAuthenticationToken token = new KeycloakAuthenticationToken(account, false);
|
|
||||||
springSecurityContext.setAuthentication(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldRetrieveKeycloakSecurityContext() {
|
|
||||||
SimpleHttpFacade facade = new SimpleHttpFacade(new MockHttpServletRequest(), new MockHttpServletResponse());
|
|
||||||
|
|
||||||
assertNotNull(facade.getSecurityContext());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,130 +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.springsecurity.facade;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapped HTTP servlet request tests.
|
|
||||||
*/
|
|
||||||
public class WrappedHttpServletRequestTest {
|
|
||||||
|
|
||||||
private static final String COOKIE_NAME = "oreo";
|
|
||||||
private static final String HEADER_MULTI_VALUE = "Multi";
|
|
||||||
private static final String HEADER_SINGLE_VALUE = "Single";
|
|
||||||
private static final String REQUEST_METHOD = RequestMethod.GET.name();
|
|
||||||
private static final String REQUEST_URI = "/foo/bar";
|
|
||||||
private static final String QUERY_PARM_1 = "code";
|
|
||||||
private static final String QUERY_PARM_2 = "code2";
|
|
||||||
|
|
||||||
private WrappedHttpServletRequest request;
|
|
||||||
private MockHttpServletRequest mockHttpServletRequest;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
mockHttpServletRequest = new MockHttpServletRequest();
|
|
||||||
request = new WrappedHttpServletRequest(mockHttpServletRequest);
|
|
||||||
|
|
||||||
mockHttpServletRequest.setMethod(REQUEST_METHOD);
|
|
||||||
mockHttpServletRequest.setRequestURI(REQUEST_URI);
|
|
||||||
|
|
||||||
mockHttpServletRequest.setSecure(true);
|
|
||||||
mockHttpServletRequest.setScheme("https");
|
|
||||||
|
|
||||||
mockHttpServletRequest.addHeader(HEADER_SINGLE_VALUE, "baz");
|
|
||||||
mockHttpServletRequest.addHeader(HEADER_MULTI_VALUE, "foo");
|
|
||||||
mockHttpServletRequest.addHeader(HEADER_MULTI_VALUE, "bar");
|
|
||||||
|
|
||||||
mockHttpServletRequest.addParameter(QUERY_PARM_1, "java");
|
|
||||||
mockHttpServletRequest.addParameter(QUERY_PARM_2, "groovy");
|
|
||||||
mockHttpServletRequest.setQueryString(String.format("%s=%s&%s=%s", QUERY_PARM_1, "java", QUERY_PARM_2, "groovy"));
|
|
||||||
mockHttpServletRequest.setCookies(new Cookie(COOKIE_NAME, "yum"));
|
|
||||||
|
|
||||||
mockHttpServletRequest.setContent("All work and no play makes Jack a dull boy".getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetMethod() throws Exception {
|
|
||||||
assertNotNull(request.getMethod());
|
|
||||||
assertEquals(REQUEST_METHOD, request.getMethod());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetURI() throws Exception {
|
|
||||||
assertEquals("https://localhost:80" + REQUEST_URI + "?code=java&code2=groovy" , request.getURI());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIsSecure() throws Exception {
|
|
||||||
assertTrue(request.isSecure());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetQueryParamValue() throws Exception {
|
|
||||||
assertNotNull(request.getQueryParamValue(QUERY_PARM_1));
|
|
||||||
assertNotNull(request.getQueryParamValue(QUERY_PARM_2));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetCookie() throws Exception {
|
|
||||||
assertNotNull(request.getCookie(COOKIE_NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetCookieCookiesNull() throws Exception
|
|
||||||
{
|
|
||||||
mockHttpServletRequest.setCookies(null);
|
|
||||||
request.getCookie(COOKIE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetHeader() throws Exception {
|
|
||||||
String header = request.getHeader(HEADER_SINGLE_VALUE);
|
|
||||||
assertNotNull(header);
|
|
||||||
assertEquals("baz", header);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetHeaders() throws Exception {
|
|
||||||
List<String> headers = request.getHeaders(HEADER_MULTI_VALUE);
|
|
||||||
assertNotNull(headers);
|
|
||||||
assertEquals(2, headers.size());
|
|
||||||
assertTrue(headers.contains("foo"));
|
|
||||||
assertTrue(headers.contains("bar"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetInputStream() throws Exception {
|
|
||||||
assertNotNull(request.getInputStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetRemoteAddr() throws Exception {
|
|
||||||
assertNotNull(request.getRemoteAddr());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +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.springsecurity.facade;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.mockito.Mockito.any;
|
|
||||||
import static org.mockito.Mockito.eq;
|
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
|
|
||||||
public class WrappedHttpServletResponseTest {
|
|
||||||
|
|
||||||
private static final String COOKIE_DOMAIN = ".keycloak.org";
|
|
||||||
private static final String COOKIE_NAME = "foo";
|
|
||||||
private static final String COOKIE_PATH = "/bar";
|
|
||||||
private static final String COOKIE_VALUE = "onegreatcookie";
|
|
||||||
private static final String HEADER = "Test";
|
|
||||||
|
|
||||||
private WrappedHttpServletResponse response;
|
|
||||||
private MockHttpServletResponse mockResponse;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
mockResponse = spy(new MockHttpServletResponse());
|
|
||||||
response = new WrappedHttpServletResponse(mockResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testResetCookie() throws Exception {
|
|
||||||
response.resetCookie(COOKIE_NAME, COOKIE_PATH);
|
|
||||||
verify(mockResponse).addCookie(any(Cookie.class));
|
|
||||||
assertEquals(COOKIE_NAME, mockResponse.getCookie(COOKIE_NAME).getName());
|
|
||||||
assertEquals(COOKIE_PATH, mockResponse.getCookie(COOKIE_NAME).getPath());
|
|
||||||
assertEquals(0, mockResponse.getCookie(COOKIE_NAME).getMaxAge());
|
|
||||||
assertEquals("", mockResponse.getCookie(COOKIE_NAME).getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSetCookie() throws Exception {
|
|
||||||
int maxAge = 300;
|
|
||||||
response.setCookie(COOKIE_NAME, COOKIE_VALUE, COOKIE_PATH, COOKIE_DOMAIN, maxAge, false, true);
|
|
||||||
verify(mockResponse).addCookie(any(Cookie.class));
|
|
||||||
assertEquals(COOKIE_NAME, mockResponse.getCookie(COOKIE_NAME).getName());
|
|
||||||
assertEquals(COOKIE_PATH, mockResponse.getCookie(COOKIE_NAME).getPath());
|
|
||||||
assertEquals(COOKIE_DOMAIN, mockResponse.getCookie(COOKIE_NAME).getDomain());
|
|
||||||
assertEquals(maxAge, mockResponse.getCookie(COOKIE_NAME).getMaxAge());
|
|
||||||
assertEquals(COOKIE_VALUE, mockResponse.getCookie(COOKIE_NAME).getValue());
|
|
||||||
assertEquals(true, mockResponse.getCookie(COOKIE_NAME).isHttpOnly());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSetStatus() throws Exception {
|
|
||||||
int status = HttpStatus.OK.value();
|
|
||||||
response.setStatus(status);
|
|
||||||
verify(mockResponse).setStatus(eq(status));
|
|
||||||
assertEquals(status, mockResponse.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddHeader() throws Exception {
|
|
||||||
String headerValue = "foo";
|
|
||||||
response.addHeader(HEADER, headerValue);
|
|
||||||
verify(mockResponse).addHeader(eq(HEADER), eq(headerValue));
|
|
||||||
assertTrue(mockResponse.containsHeader(HEADER));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSetHeader() throws Exception {
|
|
||||||
String headerValue = "foo";
|
|
||||||
response.setHeader(HEADER, headerValue);
|
|
||||||
verify(mockResponse).setHeader(eq(HEADER), eq(headerValue));
|
|
||||||
assertTrue(mockResponse.containsHeader(HEADER));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetOutputStream() throws Exception {
|
|
||||||
assertNotNull(response.getOutputStream());
|
|
||||||
verify(mockResponse).getOutputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSendError() throws Exception {
|
|
||||||
int status = HttpStatus.UNAUTHORIZED.value();
|
|
||||||
String reason = HttpStatus.UNAUTHORIZED.getReasonPhrase();
|
|
||||||
|
|
||||||
response.sendError(status, reason);
|
|
||||||
verify(mockResponse).sendError(eq(status), eq(reason));
|
|
||||||
assertEquals(status, mockResponse.getStatus());
|
|
||||||
assertEquals(reason, mockResponse.getErrorMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void testEnd() throws Exception {
|
|
||||||
// TODO: what is an ended response, one that's committed?
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,239 +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.springsecurity.filter;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.KeycloakPrincipal;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
|
|
||||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
|
|
||||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
|
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
|
||||||
import org.keycloak.common.enums.SslRequired;
|
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
|
||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.any;
|
|
||||||
import static org.mockito.Mockito.eq;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
import static org.mockito.Mockito.startsWith;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keycloak authentication process filter test cases.
|
|
||||||
*/
|
|
||||||
public class KeycloakAuthenticationProcessingFilterTest {
|
|
||||||
|
|
||||||
private KeycloakAuthenticationProcessingFilter filter;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private AuthenticationManager authenticationManager;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private AdapterDeploymentContext adapterDeploymentContext;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private FilterChain chain;
|
|
||||||
|
|
||||||
private MockHttpServletRequest request;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private HttpServletResponse response;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private AuthenticationSuccessHandler successHandler;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private AuthenticationFailureHandler failureHandler;
|
|
||||||
|
|
||||||
private KeycloakAuthenticationFailureHandler keycloakFailureHandler;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private OidcKeycloakAccount keycloakAccount;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakDeployment keycloakDeployment;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakSecurityContext keycloakSecurityContext;
|
|
||||||
|
|
||||||
private final List<? extends GrantedAuthority> authorities = Collections.singletonList(new KeycloakRole("ROLE_USER"));
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
request = spy(new MockHttpServletRequest());
|
|
||||||
request.setRequestURI("http://host");
|
|
||||||
filter = new KeycloakAuthenticationProcessingFilter(authenticationManager);
|
|
||||||
keycloakFailureHandler = new KeycloakAuthenticationFailureHandler();
|
|
||||||
|
|
||||||
filter.setApplicationContext(applicationContext);
|
|
||||||
filter.setAuthenticationSuccessHandler(successHandler);
|
|
||||||
filter.setAuthenticationFailureHandler(failureHandler);
|
|
||||||
|
|
||||||
when(applicationContext.getBean(eq(AdapterDeploymentContext.class))).thenReturn(adapterDeploymentContext);
|
|
||||||
when(adapterDeploymentContext.resolveDeployment(any(HttpFacade.class))).thenReturn(keycloakDeployment);
|
|
||||||
when(keycloakAccount.getPrincipal()).thenReturn(
|
|
||||||
new KeycloakPrincipal<KeycloakSecurityContext>(UUID.randomUUID().toString(), keycloakSecurityContext));
|
|
||||||
|
|
||||||
|
|
||||||
filter.afterPropertiesSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAttemptAuthenticationExpectRedirect() throws Exception {
|
|
||||||
when(keycloakDeployment.getAuthUrl()).thenReturn(KeycloakUriBuilder.fromUri("http://localhost:8080/auth"));
|
|
||||||
when(keycloakDeployment.getResourceName()).thenReturn("resource-name");
|
|
||||||
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
|
|
||||||
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
|
|
||||||
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
|
|
||||||
|
|
||||||
filter.attemptAuthentication(request, response);
|
|
||||||
verify(response).setStatus(302);
|
|
||||||
verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = KeycloakAuthenticationException.class)
|
|
||||||
public void testAttemptAuthenticationWithInvalidToken() throws Exception {
|
|
||||||
request.addHeader("Authorization", "Bearer xxx");
|
|
||||||
filter.attemptAuthentication(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = KeycloakAuthenticationException.class)
|
|
||||||
public void testAttemptAuthenticationWithInvalidTokenBearerOnly() throws Exception {
|
|
||||||
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.TRUE);
|
|
||||||
request.addHeader("Authorization", "Bearer xxx");
|
|
||||||
filter.attemptAuthentication(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSuccessfulAuthenticationInteractive() throws Exception {
|
|
||||||
request.setRequestURI("http://host" + KeycloakAuthenticationEntryPoint.DEFAULT_LOGIN_URI + "?query");
|
|
||||||
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, true, authorities);
|
|
||||||
filter.successfulAuthentication(request, response, chain, authentication);
|
|
||||||
|
|
||||||
verify(successHandler).onAuthenticationSuccess(eq(request), eq(response), eq(authentication));
|
|
||||||
verify(chain, never()).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSuccessfulAuthenticationBearer() throws Exception {
|
|
||||||
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, false, authorities);
|
|
||||||
this.setBearerAuthHeader(request);
|
|
||||||
filter.successfulAuthentication(request, response, chain, authentication);
|
|
||||||
|
|
||||||
verify(chain).doFilter(eq(request), eq(response));
|
|
||||||
verify(successHandler, never()).onAuthenticationSuccess(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
|
||||||
any(Authentication.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSuccessfulAuthenticationBasicAuth() throws Exception {
|
|
||||||
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, false, authorities);
|
|
||||||
this.setBasicAuthHeader(request);
|
|
||||||
filter.successfulAuthentication(request, response, chain, authentication);
|
|
||||||
|
|
||||||
verify(chain).doFilter(eq(request), eq(response));
|
|
||||||
verify(successHandler, never()).onAuthenticationSuccess(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
|
||||||
any(Authentication.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testUnsuccessfulAuthenticationInteractive() throws Exception {
|
|
||||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
|
||||||
filter.unsuccessfulAuthentication(request, response, exception);
|
|
||||||
verify(failureHandler).onAuthenticationFailure(eq(request), eq(response), eq(exception));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testUnsuccessfulAuthenticatioBearer() throws Exception {
|
|
||||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
|
||||||
this.setBearerAuthHeader(request);
|
|
||||||
filter.unsuccessfulAuthentication(request, response, exception);
|
|
||||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
|
||||||
any(AuthenticationException.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testUnsuccessfulAuthenticatioBasicAuth() throws Exception {
|
|
||||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
|
||||||
this.setBasicAuthHeader(request);
|
|
||||||
filter.unsuccessfulAuthentication(request, response, exception);
|
|
||||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
|
||||||
any(AuthenticationException.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDefaultFailureHanlder() throws Exception {
|
|
||||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
|
||||||
filter.setAuthenticationFailureHandler(keycloakFailureHandler);
|
|
||||||
filter.unsuccessfulAuthentication(request, response, exception);
|
|
||||||
|
|
||||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), any(String.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = UnsupportedOperationException.class)
|
|
||||||
public void testSetAllowSessionCreation() throws Exception {
|
|
||||||
filter.setAllowSessionCreation(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = UnsupportedOperationException.class)
|
|
||||||
public void testSetContinueChainBeforeSuccessfulAuthentication() throws Exception {
|
|
||||||
filter.setContinueChainBeforeSuccessfulAuthentication(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setBearerAuthHeader(MockHttpServletRequest request) {
|
|
||||||
setAuthorizationHeader(request, "Bearer");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setBasicAuthHeader(MockHttpServletRequest request) {
|
|
||||||
setAuthorizationHeader(request, "Basic");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setAuthorizationHeader(MockHttpServletRequest request, String scheme) {
|
|
||||||
request.addHeader(KeycloakAuthenticationProcessingFilter.AUTHORIZATION_HEADER, scheme + " " + UUID.randomUUID().toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +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.springsecurity.filter;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keycloak CSRF request matcher tests.
|
|
||||||
*/
|
|
||||||
public class KeycloakCsrfRequestMatcherTest {
|
|
||||||
|
|
||||||
private static final String ROOT_CONTEXT_PATH = "";
|
|
||||||
private static final String SUB_CONTEXT_PATH = "/foo";
|
|
||||||
|
|
||||||
private KeycloakCsrfRequestMatcher matcher = new KeycloakCsrfRequestMatcher();
|
|
||||||
|
|
||||||
private MockHttpServletRequest request;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
request = new MockHttpServletRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchesMethodGet() throws Exception {
|
|
||||||
request.setMethod(HttpMethod.GET.name());
|
|
||||||
assertFalse(matcher.matches(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchesMethodPost() throws Exception {
|
|
||||||
prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, "some/random/uri");
|
|
||||||
assertTrue(matcher.matches(request));
|
|
||||||
|
|
||||||
prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, "some/random/uri");
|
|
||||||
assertTrue(matcher.matches(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchesKeycloakLogout() throws Exception {
|
|
||||||
|
|
||||||
prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_LOGOUT);
|
|
||||||
assertFalse(matcher.matches(request));
|
|
||||||
|
|
||||||
prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_LOGOUT);
|
|
||||||
assertFalse(matcher.matches(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchesKeycloakPushNotBefore() throws Exception {
|
|
||||||
|
|
||||||
prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_PUSH_NOT_BEFORE);
|
|
||||||
assertFalse(matcher.matches(request));
|
|
||||||
|
|
||||||
prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_PUSH_NOT_BEFORE);
|
|
||||||
assertFalse(matcher.matches(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchesKeycloakQueryBearerToken() throws Exception {
|
|
||||||
|
|
||||||
prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_QUERY_BEARER_TOKEN);
|
|
||||||
assertFalse(matcher.matches(request));
|
|
||||||
|
|
||||||
prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_QUERY_BEARER_TOKEN);
|
|
||||||
assertFalse(matcher.matches(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchesKeycloakTestAvailable() throws Exception {
|
|
||||||
|
|
||||||
prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_TEST_AVAILABLE);
|
|
||||||
assertFalse(matcher.matches(request));
|
|
||||||
|
|
||||||
prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_TEST_AVAILABLE);
|
|
||||||
assertFalse(matcher.matches(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareRequest(HttpMethod method, String contextPath, String uri) {
|
|
||||||
request.setMethod(method.name());
|
|
||||||
request.setContextPath(contextPath);
|
|
||||||
request.setRequestURI(contextPath + "/" + uri);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
package org.keycloak.adapters.springsecurity.filter;
|
|
||||||
|
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
import static org.mockito.MockitoAnnotations.initMocks;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
|
||||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
|
||||||
import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.PreAuthActionsHandlerFactory;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
|
|
||||||
public class KeycloakPreAuthActionsFilterTest {
|
|
||||||
|
|
||||||
private KeycloakPreAuthActionsFilter filter;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private NodesRegistrationManagement nodesRegistrationManagement;
|
|
||||||
@Mock
|
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
@Mock
|
|
||||||
private AdapterDeploymentContext deploymentContext;
|
|
||||||
@Mock
|
|
||||||
private PreAuthActionsHandlerFactory preAuthActionsHandlerFactory;
|
|
||||||
@Mock
|
|
||||||
private UserSessionManagement userSessionManagement;
|
|
||||||
@Mock
|
|
||||||
private PreAuthActionsHandler preAuthActionsHandler;
|
|
||||||
@Mock
|
|
||||||
private KeycloakDeployment deployment;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private HttpServletRequest request;
|
|
||||||
@Mock
|
|
||||||
private HttpServletResponse response;
|
|
||||||
@Mock
|
|
||||||
private FilterChain chain;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
initMocks(this);
|
|
||||||
filter = new KeycloakPreAuthActionsFilter(userSessionManagement);
|
|
||||||
filter.setNodesRegistrationManagement(nodesRegistrationManagement);
|
|
||||||
filter.setApplicationContext(applicationContext);
|
|
||||||
filter.setPreAuthActionsHandlerFactory(preAuthActionsHandlerFactory);
|
|
||||||
when(applicationContext.getBean(AdapterDeploymentContext.class)).thenReturn(deploymentContext);
|
|
||||||
when(deploymentContext.resolveDeployment(any(HttpFacade.class))).thenReturn(deployment);
|
|
||||||
when(preAuthActionsHandlerFactory.createPreAuthActionsHandler(any(HttpFacade.class))).thenReturn(preAuthActionsHandler);
|
|
||||||
when(deployment.isConfigured()).thenReturn(true);
|
|
||||||
filter.initFilterBean();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldIgnoreChainWhenPreAuthActionHandlerHandled() throws Exception {
|
|
||||||
when(preAuthActionsHandler.handleRequest()).thenReturn(true);
|
|
||||||
|
|
||||||
filter.doFilter(request, response, chain);
|
|
||||||
|
|
||||||
verifyZeroInteractions(chain);
|
|
||||||
verify(nodesRegistrationManagement).tryRegister(deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldContinueChainWhenPreAuthActionHandlerDidNotHandle() throws Exception {
|
|
||||||
when(preAuthActionsHandler.handleRequest()).thenReturn(false);
|
|
||||||
|
|
||||||
filter.doFilter(request, response, chain);
|
|
||||||
|
|
||||||
verify(chain).doFilter(request, response);;
|
|
||||||
verify(nodesRegistrationManagement).tryRegister(deployment);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
filter.destroy();
|
|
||||||
verify(nodesRegistrationManagement).stop();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.keycloak.adapters.springsecurity.filter;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
|
|
||||||
public class QueryParamPresenceRequestMatcherTest {
|
|
||||||
private static final String ROOT_CONTEXT_PATH = "";
|
|
||||||
|
|
||||||
private static final String VALID_PARAMETER = "access_token";
|
|
||||||
|
|
||||||
private QueryParamPresenceRequestMatcher matcher = new QueryParamPresenceRequestMatcher(VALID_PARAMETER);
|
|
||||||
|
|
||||||
private MockHttpServletRequest request;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
request = new MockHttpServletRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDoesNotMatchWithoutQueryParameter() throws Exception {
|
|
||||||
prepareRequest(HttpMethod.GET, ROOT_CONTEXT_PATH, "some/random/uri", Collections.EMPTY_MAP);
|
|
||||||
assertFalse(matcher.matches(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMatchesWithValidParameter() throws Exception {
|
|
||||||
prepareRequest(HttpMethod.GET, ROOT_CONTEXT_PATH, "some/random/uri", Collections.singletonMap(VALID_PARAMETER, (Object) "123"));
|
|
||||||
assertTrue(matcher.matches(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDoesNotMatchWithInvalidParameter() throws Exception {
|
|
||||||
prepareRequest(HttpMethod.GET, ROOT_CONTEXT_PATH, "some/random/uri", Collections.singletonMap("some_parameter", (Object) "123"));
|
|
||||||
assertFalse(matcher.matches(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareRequest(HttpMethod method, String contextPath, String uri, Map<String, Object> params) {
|
|
||||||
request.setMethod(method.name());
|
|
||||||
request.setContextPath(contextPath);
|
|
||||||
request.setRequestURI(contextPath + "/" + uri);
|
|
||||||
request.setParameters(params);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,90 +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.springsecurity.token;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
|
||||||
import org.keycloak.enums.TokenStore;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spring Security adapter token store factory tests.
|
|
||||||
*/
|
|
||||||
public class SpringSecurityAdapterTokenStoreFactoryTest {
|
|
||||||
|
|
||||||
private AdapterTokenStoreFactory factory = new SpringSecurityAdapterTokenStoreFactory();
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakDeployment deployment;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private HttpServletRequest request;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private HttpServletResponse response;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateAdapterTokenStore() throws Exception {
|
|
||||||
when(deployment.getTokenStore()).thenReturn(TokenStore.SESSION);
|
|
||||||
AdapterSessionStore store = factory.createAdapterTokenStore(deployment, request, response);
|
|
||||||
assertTrue(store instanceof SpringSecurityTokenStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateAdapterTokenStoreUsingCookies() throws Exception {
|
|
||||||
when(deployment.getTokenStore()).thenReturn(TokenStore.COOKIE);
|
|
||||||
AdapterSessionStore store = factory.createAdapterTokenStore(deployment, request, response);
|
|
||||||
assertTrue(store instanceof SpringSecurityCookieTokenStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
|
||||||
public void testCreateAdapterTokenStoreNullDeployment() throws Exception {
|
|
||||||
factory.createAdapterTokenStore(null, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
|
||||||
public void testCreateAdapterTokenStoreNullRequest() throws Exception {
|
|
||||||
factory.createAdapterTokenStore(deployment, null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateAdapterTokenStoreNullResponse() throws Exception {
|
|
||||||
when(deployment.getTokenStore()).thenReturn(TokenStore.SESSION);
|
|
||||||
factory.createAdapterTokenStore(deployment, request, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
|
||||||
public void testCreateAdapterTokenStoreNullResponseUsingCookies() throws Exception {
|
|
||||||
when(deployment.getTokenStore()).thenReturn(TokenStore.COOKIE);
|
|
||||||
factory.createAdapterTokenStore(deployment, request, null);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,111 +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.springsecurity.token;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
|
|
||||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.mock.web.MockHttpSession;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spring Security token store tests.
|
|
||||||
*/
|
|
||||||
public class SpringSecurityTokenStoreTest {
|
|
||||||
|
|
||||||
private SpringSecurityTokenStore store;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private KeycloakDeployment deployment;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private Principal principal;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private RequestAuthenticator requestAuthenticator;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private RefreshableKeycloakSecurityContext keycloakSecurityContext;
|
|
||||||
|
|
||||||
private MockHttpServletRequest request;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
request = new MockHttpServletRequest();
|
|
||||||
store = new SpringSecurityTokenStore(deployment, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() throws Exception {
|
|
||||||
SecurityContextHolder.clearContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIsCached() throws Exception {
|
|
||||||
Authentication authentication = new PreAuthenticatedAuthenticationToken("foo", "bar", Collections.singleton(new KeycloakRole("ROLE_FOO")));
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
||||||
assertFalse(store.isCached(requestAuthenticator));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSaveAccountInfo() throws Exception {
|
|
||||||
OidcKeycloakAccount account = new SimpleKeycloakAccount(principal, Collections.singleton("FOO"), keycloakSecurityContext);
|
|
||||||
Authentication authentication;
|
|
||||||
|
|
||||||
store.saveAccountInfo(account);
|
|
||||||
authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
|
|
||||||
assertNotNull(authentication);
|
|
||||||
assertTrue(authentication instanceof KeycloakAuthenticationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
|
||||||
public void testSaveAccountInfoInvalidAuthenticationType() throws Exception {
|
|
||||||
OidcKeycloakAccount account = new SimpleKeycloakAccount(principal, Collections.singleton("FOO"), keycloakSecurityContext);
|
|
||||||
Authentication authentication = new PreAuthenticatedAuthenticationToken("foo", "bar", Collections.singleton(new KeycloakRole("ROLE_FOO")));
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
||||||
store.saveAccountInfo(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLogout() throws Exception {
|
|
||||||
MockHttpSession session = (MockHttpSession) request.getSession(true);
|
|
||||||
assertFalse(session.isInvalid());
|
|
||||||
store.logout();
|
|
||||||
assertTrue(session.isInvalid());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"realm": "spring-security",
|
|
||||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCh65Gqi3BSaVe12JHlqChWm8WscICrj46MVqmRoO9FCmqbxEpCQhE1RLjW+GDyc3YdXW3xqUQ3AZxDkTmN1h6BWkhdxPLzA4EnwgWmGurhyJlUF9Id2tKns0jbC+Z7kIb2LcOiKHKL7mRb3q7EtWubNnrvunv8fx+WeXGaQoGEVQIDAQAB",
|
|
||||||
"auth-server-url": "http://localhost:8080/auth",
|
|
||||||
"ssl-required": "external",
|
|
||||||
"resource": "some-resource",
|
|
||||||
"credentials": {
|
|
||||||
"secret": "a9c3501e-20dd-4277-8a7b-351063848446"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,10 +17,6 @@ include::java-adapter-config.adoc[]
|
||||||
endif::[]
|
endif::[]
|
||||||
include::jboss-adapter.adoc[]
|
include::jboss-adapter.adoc[]
|
||||||
|
|
||||||
ifeval::[{project_community}==true]
|
|
||||||
include::spring-security-adapter.adoc[]
|
|
||||||
endif::[]
|
|
||||||
|
|
||||||
ifeval::[{project_community}==true]
|
ifeval::[{project_community}==true]
|
||||||
include::servlet-filter-adapter.adoc[]
|
include::servlet-filter-adapter.adoc[]
|
||||||
endif::[]
|
endif::[]
|
||||||
|
|
|
@ -1,297 +0,0 @@
|
||||||
|
|
||||||
[[_spring_security_adapter]]
|
|
||||||
==== Spring Security 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].
|
|
||||||
====
|
|
||||||
|
|
||||||
To secure an application with Spring Security and Keycloak, add this adapter as a dependency to your project.
|
|
||||||
You then have to provide some extra beans in your Spring Security configuration file and add the Keycloak security filter to your pipeline.
|
|
||||||
|
|
||||||
Unlike the other Keycloak Adapters, you should not configure your security in web.xml.
|
|
||||||
However, keycloak.json is still required.
|
|
||||||
In order for Single Sign Out to work properly you have to define a session listener.
|
|
||||||
|
|
||||||
.The session listener can be defined:
|
|
||||||
* in web.xml (for pure Spring Security environments):
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
<listener>
|
|
||||||
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
|
|
||||||
</listener>
|
|
||||||
----
|
|
||||||
* as a Spring bean (in Spring Boot environments using Spring Security adapter)
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
@Bean
|
|
||||||
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
|
|
||||||
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
===== Installing the adapter
|
|
||||||
|
|
||||||
Add Keycloak Spring Security adapter as a dependency to your Maven POM or Gradle build.
|
|
||||||
|
|
||||||
|
|
||||||
[source,xml,subs="attributes+"]
|
|
||||||
----
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-spring-security-adapter</artifactId>
|
|
||||||
<version>{project_versionMvn}</version>
|
|
||||||
</dependency>
|
|
||||||
----
|
|
||||||
|
|
||||||
===== Configuring the Spring Security Adapter
|
|
||||||
|
|
||||||
The Keycloak Spring Security adapter takes advantage of Spring Security's flexible security configuration syntax.
|
|
||||||
|
|
||||||
====== Java configuration
|
|
||||||
|
|
||||||
Keycloak provides a KeycloakWebSecurityConfigurerAdapter as a convenient base class for creating a https://docs.spring.io/spring-security/site/docs/4.0.x/apidocs/org/springframework/security/config/annotation/web/WebSecurityConfigurer.html[WebSecurityConfigurer] instance.
|
|
||||||
The implementation allows customization by overriding methods.
|
|
||||||
While its use is not required, it greatly simplifies your security context configuration.
|
|
||||||
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
@KeycloakConfiguration
|
|
||||||
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Registers the KeycloakAuthenticationProvider with the authentication manager.
|
|
||||||
*/
|
|
||||||
@Autowired
|
|
||||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
|
||||||
auth.authenticationProvider(keycloakAuthenticationProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the session authentication strategy.
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@Override
|
|
||||||
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
|
|
||||||
return new RegisterSessionAuthenticationStrategy(buildSessionRegistry());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
protected SessionRegistry buildSessionRegistry() {
|
|
||||||
return new SessionRegistryImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure(HttpSecurity http) throws Exception
|
|
||||||
{
|
|
||||||
super.configure(http);
|
|
||||||
http
|
|
||||||
.authorizeRequests()
|
|
||||||
.antMatchers("/customers*").hasRole("USER")
|
|
||||||
.antMatchers("/admin*").hasRole("ADMIN")
|
|
||||||
.anyRequest().permitAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
You must provide a session authentication strategy bean which should be of type `RegisterSessionAuthenticationStrategy` for public or confidential applications and `NullAuthenticatedSessionStrategy` for bearer-only applications.
|
|
||||||
|
|
||||||
Spring Security's `SessionFixationProtectionStrategy` is currently not supported because it changes the session identifier after login via Keycloak.
|
|
||||||
If the session identifier changes, universal log out will not work because Keycloak is unaware of the new session identifier.
|
|
||||||
|
|
||||||
TIP: The `@KeycloakConfiguration` annotation is a metadata annotation that defines all annotations that are needed to integrate
|
|
||||||
{project_name} in Spring Security. If you have a complex Spring Security setup you can simply have a look at the annotations of
|
|
||||||
the `@KeycloakConfiguration` annotation and create your own custom meta annotation or just use specific Spring annotations
|
|
||||||
for the {project_name} adapter.
|
|
||||||
|
|
||||||
====== XML configuration
|
|
||||||
|
|
||||||
While Spring Security's XML namespace simplifies configuration, customizing the configuration can be a bit verbose.
|
|
||||||
|
|
||||||
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
|
||||||
xmlns:context="http://www.springframework.org/schema/context"
|
|
||||||
xmlns:security="http://www.springframework.org/schema/security"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="
|
|
||||||
http://www.springframework.org/schema/beans
|
|
||||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
|
||||||
http://www.springframework.org/schema/context
|
|
||||||
http://www.springframework.org/schema/context/spring-context.xsd
|
|
||||||
http://www.springframework.org/schema/security
|
|
||||||
http://www.springframework.org/schema/security/spring-security.xsd">
|
|
||||||
|
|
||||||
<context:component-scan base-package="org.keycloak.adapters.springsecurity" />
|
|
||||||
|
|
||||||
<security:authentication-manager alias="authenticationManager">
|
|
||||||
<security:authentication-provider ref="keycloakAuthenticationProvider" />
|
|
||||||
</security:authentication-manager>
|
|
||||||
|
|
||||||
<bean id="adapterDeploymentContext" class="org.keycloak.adapters.springsecurity.AdapterDeploymentContextFactoryBean">
|
|
||||||
<constructor-arg value="/WEB-INF/keycloak.json" />
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="keycloakAuthenticationEntryPoint" class="org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint">
|
|
||||||
<constructor-arg ref="adapterDeploymentContext" />
|
|
||||||
</bean>
|
|
||||||
<bean id="keycloakAuthenticationProvider" class="org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider" />
|
|
||||||
<bean id="keycloakPreAuthActionsFilter" class="org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter" />
|
|
||||||
<bean id="keycloakAuthenticationProcessingFilter" class="org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter">
|
|
||||||
<constructor-arg name="authenticationManager" ref="authenticationManager" />
|
|
||||||
</bean>
|
|
||||||
<bean id="keycloakSecurityContextRequestFilter"
|
|
||||||
class="org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter" />
|
|
||||||
|
|
||||||
<bean id="keycloakSecurityContextRequestFilter"
|
|
||||||
class="org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter" />
|
|
||||||
|
|
||||||
<bean id="keycloakLogoutHandler" class="org.keycloak.adapters.springsecurity.authentication.KeycloakLogoutHandler">
|
|
||||||
<constructor-arg ref="adapterDeploymentContext" />
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
|
|
||||||
<constructor-arg name="logoutSuccessUrl" value="/" />
|
|
||||||
<constructor-arg name="handlers">
|
|
||||||
<list>
|
|
||||||
<ref bean="keycloakLogoutHandler" />
|
|
||||||
<bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
|
|
||||||
</list>
|
|
||||||
</constructor-arg>
|
|
||||||
<property name="logoutRequestMatcher">
|
|
||||||
<bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
|
|
||||||
<constructor-arg name="pattern" value="/sso/logout**" />
|
|
||||||
<constructor-arg name="httpMethod" value="GET" />
|
|
||||||
</bean>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<security:http auto-config="false" entry-point-ref="keycloakAuthenticationEntryPoint">
|
|
||||||
<security:custom-filter ref="keycloakPreAuthActionsFilter" before="LOGOUT_FILTER" />
|
|
||||||
<security:custom-filter ref="keycloakAuthenticationProcessingFilter" before="FORM_LOGIN_FILTER" />
|
|
||||||
<security:custom-filter ref="keycloakSecurityContextRequestFilter" after="FORM_LOGIN_FILTER" />
|
|
||||||
<security:intercept-url pattern="/customers**" access="ROLE_USER" />
|
|
||||||
<security:intercept-url pattern="/admin**" access="ROLE_ADMIN" />
|
|
||||||
<security:custom-filter ref="logoutFilter" position="LOGOUT_FILTER" />
|
|
||||||
</security:http>
|
|
||||||
|
|
||||||
</beans>
|
|
||||||
----
|
|
||||||
|
|
||||||
===== Multi Tenancy
|
|
||||||
|
|
||||||
The Keycloak Spring Security adapter also supports Multi Tenancy.
|
|
||||||
Instead of injecting `AdapterDeploymentContextFactoryBean` with the path to `keycloak.json` you can inject an implementation of the `KeycloakConfigResolver` interface.
|
|
||||||
More details on how to implement the `KeycloakConfigResolver` can be found in <<_multi_tenancy,Multi Tenancy>>.
|
|
||||||
|
|
||||||
===== Naming security roles
|
|
||||||
|
|
||||||
Spring Security, when using role-based authentication, requires that role names start with `ROLE_`.
|
|
||||||
For example, an administrator role must be declared in Keycloak as `ROLE_ADMIN` or similar, not simply `ADMIN`.
|
|
||||||
|
|
||||||
The class `org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider` supports an optional `org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper` which can be used to map roles coming from Keycloak to roles recognized by Spring Security.
|
|
||||||
Use, for example, `org.springframework.security.core.authority.mapping.SimpleAuthorityMapper`, which allows for case conversion and the addition of a prefix (which defaults to `ROLE_`).
|
|
||||||
The following code will convert the role names to upper case and, by default, add the `ROLE_` prefix to them:
|
|
||||||
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
@KeycloakConfiguration
|
|
||||||
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public void configureGlobal(AuthenticationManagerBuilder auth) {
|
|
||||||
auth.authenticationProvider(getKeycloakAuthenticationProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeycloakAuthenticationProvider getKeycloakAuthenticationProvider() {
|
|
||||||
KeycloakAuthenticationProvider authenticationProvider = keycloakAuthenticationProvider();
|
|
||||||
SimpleAuthorityMapper mapper = new SimpleAuthorityMapper();
|
|
||||||
mapper.setConvertToUpperCase(true);
|
|
||||||
authenticationProvider.setGrantedAuthoritiesMapper(mapper);
|
|
||||||
|
|
||||||
return authenticationProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
===== Client to Client Support
|
|
||||||
|
|
||||||
To simplify communication between clients, Keycloak provides an extension of Spring's `RestTemplate` that handles bearer token authentication for you.
|
|
||||||
To enable this feature your security configuration must add the `KeycloakRestTemplate` bean.
|
|
||||||
Note that it must be scoped as a prototype to function correctly.
|
|
||||||
|
|
||||||
For Java configuration:
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
|
|
||||||
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public KeycloakClientRequestFactory keycloakClientRequestFactory;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
|
||||||
public KeycloakRestTemplate keycloakRestTemplate() {
|
|
||||||
return new KeycloakRestTemplate(keycloakClientRequestFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
For XML configuration:
|
|
||||||
[source,xml]
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
<bean id="keycloakRestTemplate" class="org.keycloak.adapters.springsecurity.client.KeycloakRestTemplate" scope="prototype">
|
|
||||||
<constructor-arg name="factory" ref="keycloakClientRequestFactory" />
|
|
||||||
</bean>
|
|
||||||
----
|
|
||||||
|
|
||||||
Your application code can then use `KeycloakRestTemplate` any time it needs to make a call to another client.
|
|
||||||
For example:
|
|
||||||
[source,java]
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class RemoteProductService implements ProductService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private KeycloakRestTemplate template;
|
|
||||||
|
|
||||||
private String endpoint;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getProducts() {
|
|
||||||
ResponseEntity<String[]> response = template.getForEntity(endpoint, String[].class);
|
|
||||||
return Arrays.asList(response.getBody());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----
|
|
|
@ -15,7 +15,6 @@ ifeval::[{project_community}==true]
|
||||||
* {quickstartRepo_link}/tree/latest/spring/rest-authz-resource-server[Spring Boot]
|
* {quickstartRepo_link}/tree/latest/spring/rest-authz-resource-server[Spring Boot]
|
||||||
* <<_jboss_adapter, {project_name} Wildfly Adapter>> (Deprecated)
|
* <<_jboss_adapter, {project_name} Wildfly Adapter>> (Deprecated)
|
||||||
* <<_servlet_filter_adapter,{project_name} Servlet Filter>> (Deprecated)
|
* <<_servlet_filter_adapter,{project_name} Servlet Filter>> (Deprecated)
|
||||||
* <<_spring_security_adapter,{project_name} Spring Security>> (Deprecated)
|
|
||||||
endif::[]
|
endif::[]
|
||||||
|
|
||||||
===== JavaScript (client-side)
|
===== JavaScript (client-side)
|
||||||
|
|
Loading…
Reference in a new issue