Remove Spring adapters

Closes #28780

Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
Douglas Palmer 2024-04-23 09:30:34 -07:00 committed by Marek Posolda
parent bf2c97065f
commit 3e13b40648
64 changed files with 0 additions and 5397 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 + '\'' +
'}';
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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