keycloak-uncached
Changes
integration/pom.xml 1(+1 -0)
integration/spring-security/pom.xml 135(+135 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/account/KeycloakRole.java 62(+62 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/account/SimpleKeycloakAccount.java 42(+42 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java 58(+58 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPoint.java 45(+45 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProvider.java 38(+38 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandler.java 69(+69 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/SpringSecurityRequestAuthenticator.java 91(+91 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactory.java 45(+45 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakRestTemplate.java 17(+17 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java 84(+84 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/package-info.java 4(+4 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/SimpleHttpFacade.java 66(+66 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java 101(+101 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponse.java 95(+95 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java 128(+128 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakPreAuthActionsFilter.java 77(+77 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/package-info.java 4(+4 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakSecurityComponents.java 10(+10 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/HttpSessionManager.java 57(+57 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/LocalSessionManagementStrategy.java 34(+34 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/SessionManagementStrategy.java 41(+41 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/package-info.java 4(+4 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/registration/NodeManager.java 27(+27 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/KeycloakAuthenticationToken.java 55(+55 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStore.java 122(+122 -0)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPointTest.java 51(+51 -0)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProviderTest.java 54(+54 -0)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/SpringSecurityRequestAuthenticatorTest.java 107(+107 -0)
Details
integration/pom.xml 1(+1 -0)
diff --git a/integration/pom.xml b/integration/pom.xml
index 1680a08..d19ad02 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -31,5 +31,6 @@
<module>admin-client</module>
<module>osgi-adapter</module>
<module>spring-boot</module>
+ <module>spring-security</module>
</modules>
</project>
integration/spring-security/pom.xml 135(+135 -0)
diff --git a/integration/spring-security/pom.xml b/integration/spring-security/pom.xml
new file mode 100755
index 0000000..8c13319
--- /dev/null
+++ b/integration/spring-security/pom.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+<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>1.2.0.RC1-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>3.2.7.RELEASE</spring.version>
+ <spring-security.version>3.2.7.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>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-config</artifactId>
+ <version>${spring-security.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-web</artifactId>
+ <version>${spring-security.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>${apache-httpcomponents.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.iharder</groupId>
+ <artifactId>base64</artifactId>
+ <version>${base64.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ <version>${bouncycastle.crypto.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-core-asl</artifactId>
+ <version>${jackson.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-mapper-asl</artifactId>
+ <version>${jackson.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-xc</artifactId>
+ <version>${jackson.version}</version>
+ <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>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/account/KeycloakRole.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/account/KeycloakRole.java
new file mode 100644
index 0000000..15108ea
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/account/KeycloakRole.java
@@ -0,0 +1,62 @@
+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 KeycloakRole)) {
+ return false;
+ }
+
+ KeycloakRole that = (KeycloakRole) o;
+
+ if (!role.equals(that.role)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return 3 * role.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "KeycloakRole{" +
+ "role='" + role + '\'' +
+ '}';
+ }
+
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/account/SimpleKeycloakAccount.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/account/SimpleKeycloakAccount.java
new file mode 100644
index 0000000..338a72f
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/account/SimpleKeycloakAccount.java
@@ -0,0 +1,42 @@
+package org.keycloak.adapters.springsecurity.account;
+
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Set;
+
+/**
+ * Concrete, serializable {@link KeycloakAccount} implementation.
+ *
+ * @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
+ * @version $Revision: 1 $
+ */
+public class SimpleKeycloakAccount implements KeycloakAccount, 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;
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java
new file mode 100644
index 0000000..29433ef
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextBean.java
@@ -0,0 +1,58 @@
+package org.keycloak.adapters.springsecurity;
+
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.io.Resource;
+
+import java.io.InputStream;
+
+/**
+ * Bean holding the {@link KeycloakDeployment} and {@link AdapterDeploymentContext} for this
+ * Spring application context. The Keycloak deployment is loaded from the required
+ * <code>WEB-INF/keycloak.json</code> file generated by Keycloak.
+ *
+ * @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
+ * @version $Revision: 1 $
+ */
+public class AdapterDeploymentContextBean implements ApplicationContextAware, InitializingBean {
+
+ private ApplicationContext applicationContext;
+ private AdapterDeploymentContext deploymentContext;
+ private KeycloakDeployment deployment;
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ Resource resource = applicationContext.getResource("WEB-INF/keycloak.json");
+ InputStream is = resource.getInputStream();
+ this.deployment = KeycloakDeploymentBuilder.build(is);
+ this.deploymentContext = new AdapterDeploymentContext(deployment);
+ }
+
+ /**
+ * Returns the Keycloak {@link AdapterDeploymentContext} for this application context.
+ *
+ * @return the Keycloak {@link AdapterDeploymentContext} for this application context
+ */
+ public AdapterDeploymentContext getDeploymentContext() {
+ return deploymentContext;
+ }
+
+ /**
+ * Returns the {@link KeycloakDeployment} for this application context.
+ *
+ * @return the {@link KeycloakDeployment} for this application context
+ */
+ public KeycloakDeployment getDeployment() {
+ return deployment;
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPoint.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPoint.java
new file mode 100644
index 0000000..3357806
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPoint.java
@@ -0,0 +1,45 @@
+package org.keycloak.adapters.springsecurity.authentication;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.util.Assert;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Provides a Keycloak {@link AuthenticationEntryPoint authentication entry point}.
+ *
+ * @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakAuthenticationEntryPoint implements AuthenticationEntryPoint {
+
+ /**
+ * Default Keycloak authentication login URI
+ */
+ public static final String DEFAULT_LOGIN_URI = "/sso/login";
+
+ private final static Logger log = LoggerFactory.getLogger(KeycloakAuthenticationEntryPoint.class);
+
+ private String loginUri = DEFAULT_LOGIN_URI;
+
+ @Override
+ public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
+ throws IOException, ServletException {
+
+ String contextAwareLoginUri = request.getContextPath() + loginUri;
+
+ log.debug("Redirecting to login URI {}", contextAwareLoginUri);
+ response.sendRedirect(contextAwareLoginUri);
+ }
+
+ public void setLoginUri(String loginUri) {
+ Assert.notNull(loginUri, "loginUri cannot be null");
+ this.loginUri = loginUri;
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProvider.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProvider.java
new file mode 100644
index 0000000..4e50685
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProvider.java
@@ -0,0 +1,38 @@
+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 java.util.ArrayList;
+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 {
+
+ @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(), grantedAuthorities);
+ }
+
+ @Override
+ public boolean supports(Class<?> aClass) {
+ return KeycloakAuthenticationToken.class.isAssignableFrom(aClass);
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandler.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandler.java
new file mode 100644
index 0000000..2879a06
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandler.java
@@ -0,0 +1,69 @@
+package org.keycloak.adapters.springsecurity.authentication;
+
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutHandler;
+import org.springframework.util.Assert;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 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 {
+
+ public static final String SSO_LOGOUT_COMPLETE_PARAM = "sso_complete";
+ private static final Logger log = LoggerFactory.getLogger(KeycloakLogoutHandler.class);
+
+ private AdapterDeploymentContextBean deploymentContextBean;
+
+ public KeycloakLogoutHandler(AdapterDeploymentContextBean deploymentContextBean) {
+ Assert.notNull(deploymentContextBean);
+ this.deploymentContextBean = deploymentContextBean;
+ }
+
+ @Override
+ public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
+
+ if (authentication instanceof AnonymousAuthenticationToken) {
+ log.warn("Attempt to log out an anonymous authentication");
+ return;
+ }
+
+ if (Boolean.valueOf(request.getParameter(SSO_LOGOUT_COMPLETE_PARAM))) {
+ // already logged out
+ return;
+ }
+
+ try {
+ handleSingleSignOut(request, response);
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to redirect to SSO url!", e);
+ }
+
+ }
+
+ protected String createRedirectUrl(HttpServletRequest request) {
+
+ return UriComponentsBuilder.fromHttpUrl(request.getRequestURL().toString())
+ .replaceQueryParam(SSO_LOGOUT_COMPLETE_PARAM, true).build().toUriString();
+ }
+
+ protected void handleSingleSignOut(HttpServletRequest request, HttpServletResponse response) throws IOException {
+
+ KeycloakDeployment deployment = deploymentContextBean.getDeployment();
+ String redirectUrl = createRedirectUrl(request);
+
+ response.sendRedirect(deployment.getLogoutUrl().queryParam("redirect_uri", redirectUrl).build().toASCIIString());
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/SpringSecurityRequestAuthenticator.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/SpringSecurityRequestAuthenticator.java
new file mode 100644
index 0000000..8b229ea
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/SpringSecurityRequestAuthenticator.java
@@ -0,0 +1,91 @@
+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.HttpFacade;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OAuthRequestAuthenticator;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+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.security.Principal;
+import java.util.Set;
+import java.util.logging.Level;
+
+/**
+ * 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 KeycloakAccount 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.warn("Completing bearer authentication. Bearer roles: {} ",roles);
+
+ SecurityContextHolder.getContext().setAuthentication(new KeycloakAuthenticationToken(account));
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ }
+
+ @Override
+ protected String getHttpSessionId(boolean create) {
+ HttpSession session = request.getSession(create);
+ return session != null ? session.getId() : null;
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactory.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactory.java
new file mode 100644
index 0000000..2dea071
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactory.java
@@ -0,0 +1,45 @@
+package org.keycloak.adapters.springsecurity.client;
+
+import org.apache.http.HttpHost;
+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) {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
+ KeycloakSecurityContext context = token.getAccount().getKeycloakSecurityContext();
+
+ request.setHeader(AUTHORIZATION_HEADER, "Bearer " + context.getTokenString());
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakRestTemplate.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakRestTemplate.java
new file mode 100644
index 0000000..3451717
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakRestTemplate.java
@@ -0,0 +1,17 @@
+package org.keycloak.adapters.springsecurity.client;
+
+import org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory;
+import org.springframework.web.client.RestOperations;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * Created by scott on 4/22/15.
+ */
+//@Service
+//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+public class KeycloakRestTemplate extends RestTemplate implements RestOperations {
+ public KeycloakRestTemplate(KeycloakClientRequestFactory factory) {
+ super(factory);
+ }
+
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
new file mode 100644
index 0000000..7a3d772
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
@@ -0,0 +1,84 @@
+package org.keycloak.adapters.springsecurity.config;
+
+import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
+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.KeycloakAuthenticationProcessingFilter;
+import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
+import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
+import org.springframework.context.annotation.Bean;
+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.preauth.x509.X509AuthenticationFilter;
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+
+/**
+ * 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> {
+
+ @Bean
+ protected AdapterDeploymentContextBean adapterDeploymentContextBean() {
+ return new AdapterDeploymentContextBean();
+ }
+
+ @Bean
+ protected AuthenticationEntryPoint authenticationEntryPoint()
+ {
+ return new KeycloakAuthenticationEntryPoint();
+ }
+
+ @Bean
+ 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());
+ }
+
+ @Bean
+ protected HttpSessionManager httpSessionManager() {
+ return new HttpSessionManager();
+ }
+
+ @Bean
+ protected KeycloakLogoutHandler keycloakLogoutHandler() {
+ return new KeycloakLogoutHandler(adapterDeploymentContextBean());
+ }
+
+ protected abstract SessionAuthenticationStrategy sessionAuthenticationStrategy();
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .sessionManagement()
+ .sessionAuthenticationStrategy(sessionAuthenticationStrategy())
+ .and()
+ .addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class)
+ .addFilterBefore(keycloakAuthenticationProcessingFilter(), X509AuthenticationFilter.class)
+ .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint());
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/package-info.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/package-info.java
new file mode 100644
index 0000000..586dcba
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Provides an {@link org.keycloak.adapters.HttpFacade} implementation.
+ */
+package org.keycloak.adapters.springsecurity.facade;
\ No newline at end of file
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/SimpleHttpFacade.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/SimpleHttpFacade.java
new file mode 100644
index 0000000..8c7a5b0
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/SimpleHttpFacade.java
@@ -0,0 +1,66 @@
+package org.keycloak.adapters.springsecurity.facade;
+
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.springsecurity.facade.WrappedHttpServletRequest;
+import org.keycloak.adapters.springsecurity.facade.WrappedHttpServletResponse;
+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 HttpFacade} wrapping an {@link HttpServletRequest} and {@link HttpServletResponse}.
+ *
+ * @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
+ * @version $Revision: 1 $
+ */
+public class SimpleHttpFacade implements HttpFacade {
+
+ 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) {
+ return (KeycloakSecurityContext) context.getAuthentication().getDetails();
+ }
+
+ 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];
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
new file mode 100644
index 0000000..478f182
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java
@@ -0,0 +1,101 @@
+package org.keycloak.adapters.springsecurity.facade;
+
+import org.keycloak.adapters.HttpFacade.Cookie;
+import org.keycloak.adapters.HttpFacade.Request;
+import org.springframework.util.Assert;
+
+import javax.servlet.http.HttpServletRequest;
+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;
+
+ /**
+ * 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 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 boolean isSecure() {
+ return request.isSecure();
+ }
+
+ @Override
+ public String getQueryParamValue(String param) {
+ return request.getParameter(param);
+ }
+
+ @Override
+ public Cookie getCookie(String cookieName) {
+
+ 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() {
+ try {
+ return request.getInputStream();
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to get request input stream", e);
+ }
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return request.getRemoteAddr();
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponse.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponse.java
new file mode 100644
index 0000000..c0b5759
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponse.java
@@ -0,0 +1,95 @@
+package org.keycloak.adapters.springsecurity.facade;
+
+import org.keycloak.adapters.HttpFacade.Response;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * 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 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);
+ cookie.setHttpOnly(httpOnly);
+
+ response.addCookie(cookie);
+ }
+
+ @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, 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?
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
new file mode 100644
index 0000000..55cbd92
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java
@@ -0,0 +1,128 @@
+package org.keycloak.adapters.springsecurity.filter;
+
+import org.keycloak.adapters.AuthChallenge;
+import org.keycloak.adapters.AuthOutcome;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
+import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
+import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator;
+import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
+import org.keycloak.adapters.springsecurity.token.SpringSecurityTokenStore;
+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.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+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;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 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 {
+
+ /**
+ * Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
+ * and any request with a <code>Authorization</code> header.
+ */
+ public static final RequestMatcher DEFAULT_REQUEST_MATCHER =
+ new OrRequestMatcher(new AntPathRequestMatcher("/sso/login"), new RequestHeaderRequestMatcher("Authorization"));
+
+ private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticationProcessingFilter.class);
+
+ private ApplicationContext applicationContext;
+ private AdapterDeploymentContextBean adapterDeploymentContextBean;
+ private AuthenticationManager authenticationManager;
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ adapterDeploymentContextBean= applicationContext.getBean(AdapterDeploymentContextBean.class);
+ super.afterPropertiesSet();
+ }
+
+ @Override
+ public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
+ throws AuthenticationException, IOException, ServletException {
+
+ log.debug("Attempting Keycloak authentication");
+
+ KeycloakDeployment deployment = adapterDeploymentContextBean.getDeployment();
+ SimpleHttpFacade facade = new SimpleHttpFacade(request, response);
+ SpringSecurityTokenStore tokenStore = new SpringSecurityTokenStore(deployment, request);
+ SpringSecurityRequestAuthenticator authenticator
+ = new SpringSecurityRequestAuthenticator(facade, request, deployment, tokenStore, -1);
+
+ AuthOutcome result = authenticator.authenticate();
+ AuthChallenge challenge = authenticator.getChallenge();
+
+ log.debug("Auth outcome: {}", result);
+
+ if (challenge != null) {
+ challenge.challenge(facade);
+ }
+
+ if (AuthOutcome.AUTHENTICATED.equals(result)) {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
+ return authenticationManager.authenticate(authentication);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakPreAuthActionsFilter.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakPreAuthActionsFilter.java
new file mode 100644
index 0000000..897010e
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakPreAuthActionsFilter.java
@@ -0,0 +1,77 @@
+package org.keycloak.adapters.springsecurity.filter;
+
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.PreAuthActionsHandler;
+import org.keycloak.adapters.UserSessionManagement;
+import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
+import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+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;
+import java.io.InputStream;
+
+/**
+ * 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 ApplicationContext applicationContext;
+ private AdapterDeploymentContext deploymentContext;
+ private UserSessionManagement userSessionManagement;
+
+ public KeycloakPreAuthActionsFilter() {
+ super();
+ }
+
+ public KeycloakPreAuthActionsFilter(UserSessionManagement userSessionManagement) {
+ this.userSessionManagement = userSessionManagement;
+ }
+
+ @Override
+ protected void initFilterBean() throws ServletException {
+ AdapterDeploymentContextBean contextBean = applicationContext.getBean(AdapterDeploymentContextBean.class);
+ deploymentContext = contextBean.getDeploymentContext();
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+
+ HttpFacade facade = new SimpleHttpFacade((HttpServletRequest)request, (HttpServletResponse)response);
+ PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deploymentContext, facade);
+ if (handler.handleRequest()) {
+ log.info("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;
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/package-info.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/package-info.java
new file mode 100644
index 0000000..3f73e8f
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Provides Spring Security filters for Keycloak.
+ */
+package org.keycloak.adapters.springsecurity.filter;
\ No newline at end of file
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakSecurityComponents.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakSecurityComponents.java
new file mode 100644
index 0000000..5adf433
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakSecurityComponents.java
@@ -0,0 +1,10 @@
+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 {
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/HttpSessionManager.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/HttpSessionManager.java
new file mode 100644
index 0000000..3492e38
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/HttpSessionManager.java
@@ -0,0 +1,57 @@
+package org.keycloak.adapters.springsecurity.management;
+
+import org.keycloak.adapters.UserSessionManagement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+import java.util.List;
+
+/**
+ * User session manager for handling logout of Spring Secured sessions.
+ *
+ * @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
+ * @version $Revision: 1 $
+ */
+@Component
+public class HttpSessionManager implements HttpSessionListener, UserSessionManagement {
+
+ private static final Logger log = LoggerFactory.getLogger(HttpSessionManager.class);
+ private SessionManagementStrategy sessions = new LocalSessionManagementStrategy();
+
+ @Override
+ public void sessionCreated(HttpSessionEvent event) {
+ log.debug("Session created: {}", event.getSession().getId());
+ HttpSession session = event.getSession();
+ sessions.store(session);
+ }
+
+ @Override
+ public void sessionDestroyed(HttpSessionEvent event) {
+ sessions.remove(event.getSession().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();
+ }
+ }
+ sessions.clear();
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/LocalSessionManagementStrategy.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/LocalSessionManagementStrategy.java
new file mode 100644
index 0000000..8676f1d
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/LocalSessionManagementStrategy.java
@@ -0,0 +1,34 @@
+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);
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/SessionManagementStrategy.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/SessionManagementStrategy.java
new file mode 100644
index 0000000..96d6704
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/SessionManagementStrategy.java
@@ -0,0 +1,41 @@
+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);
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/package-info.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/package-info.java
new file mode 100644
index 0000000..4df6bc3
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Provides a Keycloak adapter for Spring Security.
+ */
+package org.keycloak.adapters.springsecurity;
\ No newline at end of file
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/registration/NodeManager.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/registration/NodeManager.java
new file mode 100644
index 0000000..c6d341b
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/registration/NodeManager.java
@@ -0,0 +1,27 @@
+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);
+
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/KeycloakAuthenticationToken.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/KeycloakAuthenticationToken.java
new file mode 100644
index 0000000..c96ae7e
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/KeycloakAuthenticationToken.java
@@ -0,0 +1,55 @@
+package org.keycloak.adapters.springsecurity.token;
+
+import org.keycloak.adapters.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;
+
+ /**
+ * Creates a new, unauthenticated Keycloak security token for the given account.
+ */
+ public KeycloakAuthenticationToken(KeycloakAccount account) {
+ 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);
+ }
+
+ public KeycloakAuthenticationToken(KeycloakAccount account, Collection<? extends GrantedAuthority> authorities) {
+ super(authorities);
+ this.principal = account.getPrincipal();
+ this.setDetails(account);
+ setAuthenticated(true);
+ }
+
+ @Override
+ public Object getCredentials() {
+ return this.getAccount().getKeycloakSecurityContext();
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return principal;
+ }
+
+ public KeycloakAccount getAccount() {
+ return (KeycloakAccount) this.getDetails();
+ }
+}
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStore.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStore.java
new file mode 100644
index 0000000..3c6af11
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStore.java
@@ -0,0 +1,122 @@
+package org.keycloak.adapters.springsecurity.token;
+
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+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.info("Remote logged in already. Establishing state from security context.");
+ token = (KeycloakAuthenticationToken) context.getAuthentication();
+ keycloakSecurityContext = token.getAccount().getKeycloakSecurityContext();
+
+ if (!deployment.getRealm().equals(keycloakSecurityContext.getRealm())) {
+ logger.info("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(KeycloakAccount account) {
+
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+ if (authentication != null) {
+ throw new IllegalStateException("Went to save Keycloak account {}, but already have {}");
+ }
+
+ logger.debug("Saving account info {}", account);
+ SecurityContextHolder.getContext().setAuthentication(new KeycloakAuthenticationToken(account));
+ }
+
+ @Override
+ public void logout() {
+
+ logger.debug("Handling logout request");
+ HttpSession session = request.getSession(false);
+
+ if (session != null) {
+ session.setAttribute(KeycloakSecurityContext.class.getName(), null);
+ }
+
+ 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;
+ }
+}
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPointTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPointTest.java
new file mode 100644
index 0000000..b7204c3
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPointTest.java
@@ -0,0 +1,51 @@
+package org.keycloak.adapters.springsecurity.authentication;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import static org.junit.Assert.*;
+
+/**
+ * Keycloak authentication entry point tests.
+ */
+public class KeycloakAuthenticationEntryPointTest {
+
+ private KeycloakAuthenticationEntryPoint authenticationEntryPoint;
+ private MockHttpServletRequest request;
+ private MockHttpServletResponse response;
+
+ @Before
+ public void setUp() throws Exception {
+ authenticationEntryPoint = new KeycloakAuthenticationEntryPoint();
+ request = new MockHttpServletRequest();
+ response = new MockHttpServletResponse();
+ }
+
+ @Test
+ public void testCommence() throws Exception {
+ authenticationEntryPoint.commence(request, response, null);
+ assertEquals(HttpStatus.FOUND.value(), response.getStatus());
+ assertEquals(KeycloakAuthenticationEntryPoint.DEFAULT_LOGIN_URI, response.getHeader("Location"));
+ }
+
+ @Test
+ public void testCommenceNotRootContext() throws Exception {
+ 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 testSetLoginUri() throws Exception {
+ final String logoutUri = "/foo";
+ authenticationEntryPoint.setLoginUri(logoutUri);
+ authenticationEntryPoint.commence(request, response, null);
+ assertEquals(HttpStatus.FOUND.value(), response.getStatus());
+ assertEquals(logoutUri, response.getHeader("Location"));
+ }
+}
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProviderTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProviderTest.java
new file mode 100644
index 0000000..a94ea11
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProviderTest.java
@@ -0,0 +1,54 @@
+package org.keycloak.adapters.springsecurity.authentication;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+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.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+
+import java.security.Principal;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Keycloak authentication provider tests.
+ */
+public class KeycloakAuthenticationProviderTest {
+
+ private KeycloakAuthenticationProvider provider = new KeycloakAuthenticationProvider();
+ private KeycloakAuthenticationToken token;
+ 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);
+ }
+
+ @Test
+ public void testAuthenticate() throws Exception {
+ Authentication result = provider.authenticate(token);
+ assertNotNull(result);
+ assertEquals(roles.size(), result.getAuthorities().size());
+ assertTrue(result.isAuthenticated());
+ assertNotNull(result.getPrincipal());
+ assertNotNull(result.getCredentials());
+ assertNotNull(result.getDetails());
+ }
+
+ @Test
+ public void testSupports() throws Exception {
+ assertTrue(provider.supports(KeycloakAuthenticationToken.class));
+ assertFalse(provider.supports(PreAuthenticatedAuthenticationToken.class));
+ }
+}
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/SpringSecurityRequestAuthenticatorTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/SpringSecurityRequestAuthenticatorTest.java
new file mode 100644
index 0000000..cdd305a
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/SpringSecurityRequestAuthenticatorTest.java
@@ -0,0 +1,107 @@
+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.HttpFacade;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OAuthRequestAuthenticator;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+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.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * 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(KeycloakAccount.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.getHttpSessionId(true);
+ assertNotNull(sessionId);
+ }
+
+ @Test
+ public void testGetHttpSessionIdFalse() throws Exception {
+ String sessionId = authenticator.getHttpSessionId(false);
+ assertNull(sessionId);
+ }
+}
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequestTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequestTest.java
new file mode 100644
index 0000000..91ec35b
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequestTest.java
@@ -0,0 +1,103 @@
+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.*;
+
+/**
+ * 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;
+
+ @Before
+ public void setUp() throws Exception {
+ MockHttpServletRequest 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 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());
+ }
+}
\ No newline at end of file
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponseTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponseTest.java
new file mode 100644
index 0000000..408d9c5
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponseTest.java
@@ -0,0 +1,99 @@
+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.*;
+import static org.mockito.Mockito.*;
+
+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());
+ }
+
+ @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?
+ }
+}
\ No newline at end of file