keycloak-developers

Merge pull request #1222 from Smartling/KEYCLOAK-1273 Improve

5/8/2015 2:01:14 AM

Changes

Details

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
index 8b229ea..b224ccf 100644
--- 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
@@ -77,7 +77,7 @@ public class SpringSecurityRequestAuthenticator extends RequestAuthenticator {
         Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
         final KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, securityContext);
 
-        logger.warn("Completing bearer authentication. Bearer roles: {} ",roles);
+        logger.debug("Completing bearer authentication. Bearer roles: {} ",roles);
 
         SecurityContextHolder.getContext().setAuthentication(new KeycloakAuthenticationToken(account));
         request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
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
index 2dea071..72f32b7 100644
--- 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
@@ -36,10 +36,35 @@ public class KeycloakClientRequestFactory extends HttpComponentsClientHttpReques
 
     @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 = (KeycloakAuthenticationToken) authentication;
-        KeycloakSecurityContext context = token.getAccount().getKeycloakSecurityContext();
+        KeycloakAuthenticationToken token;
+        KeycloakSecurityContext context;
 
-        request.setHeader(AUTHORIZATION_HEADER, "Bearer " + context.getTokenString());
+        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;
     }
 }
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
index 3451717..fdf56d2 100644
--- 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
@@ -1,15 +1,32 @@
 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.
+ * 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 $
  */
-//@Service
-//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
 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);
     }
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
index 55cbd92..cf5f373 100644
--- 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
@@ -1,19 +1,23 @@
 package org.keycloak.adapters.springsecurity.filter;
 
+import org.keycloak.adapters.AdapterTokenStore;
 import org.keycloak.adapters.AuthChallenge;
 import org.keycloak.adapters.AuthOutcome;
 import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RequestAuthenticator;
 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.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.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.SecurityContextHolder;
@@ -24,6 +28,7 @@ import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 
+import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -37,6 +42,8 @@ import java.io.IOException;
  */
 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.
@@ -48,6 +55,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
 
     private ApplicationContext applicationContext;
     private AdapterDeploymentContextBean adapterDeploymentContextBean;
+    private AdapterTokenStoreFactory adapterTokenStoreFactory = new SpringSecurityAdapterTokenStoreFactory();
     private AuthenticationManager authenticationManager;
 
     /**
@@ -83,11 +91,13 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
         Assert.notNull(authenticationManager, "authenticationManager cannot be null");
         this.authenticationManager = authenticationManager;
         super.setAuthenticationManager(authenticationManager);
+        super.setAllowSessionCreation(false);
+        super.setContinueChainBeforeSuccessfulAuthentication(false);
     }
 
     @Override
     public void afterPropertiesSet() {
-        adapterDeploymentContextBean= applicationContext.getBean(AdapterDeploymentContextBean.class);
+        adapterDeploymentContextBean = applicationContext.getBean(AdapterDeploymentContextBean.class);
         super.afterPropertiesSet();
     }
 
@@ -99,8 +109,8 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
 
         KeycloakDeployment deployment = adapterDeploymentContextBean.getDeployment();
         SimpleHttpFacade facade = new SimpleHttpFacade(request, response);
-        SpringSecurityTokenStore tokenStore = new SpringSecurityTokenStore(deployment, request);
-        SpringSecurityRequestAuthenticator authenticator
+        AdapterTokenStore tokenStore = adapterTokenStoreFactory.createAdapterTokenStore(deployment, request);
+        RequestAuthenticator authenticator
                 = new SpringSecurityRequestAuthenticator(facade, request, deployment, tokenStore, -1);
 
         AuthOutcome result = authenticator.authenticate();
@@ -121,8 +131,92 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
         return null;
     }
 
+    /**
+     * Returns true if the request was made with a bearer token authorization header.
+     *
+     * @param request the current <code>HttpServletRequest</code>
+     *
+     * @return <code>true</code> if the <code>request</code> was made with a bearer token authorization header;
+     * <code>false</code> otherwise.
+     */
+    protected boolean isBearerTokenRequest(HttpServletRequest request) {
+        String authValue = request.getHeader(AUTHORIZATION_HEADER);
+        return authValue != null && authValue.startsWith("Bearer");
+    }
+
+    @Override
+    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
+            Authentication authResult) throws IOException, ServletException {
+
+        if (!this.isBearerTokenRequest(request)) {
+            super.successfulAuthentication(request, response, chain, authResult);
+            return;
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Authentication success using bearer token. Updating SecurityContextHolder to contain: {}", authResult);
+        }
+
+        SecurityContextHolder.getContext().setAuthentication(authResult);
+
+        // Fire event
+        if (this.eventPublisher != null) {
+            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
+        }
+
+        try {
+            chain.doFilter(request, response);
+        } finally {
+            SecurityContextHolder.clearContext();
+        }
+
+    }
+
+    @Override
+    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
+            AuthenticationException failed) throws IOException, ServletException {
+
+        if (this.isBearerTokenRequest(request)) {
+            SecurityContextHolder.clearContext();
+            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Unable to authenticate bearer token");
+            return;
+        }
+
+        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");
+    }
 }
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/AdapterTokenStoreFactory.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/AdapterTokenStoreFactory.java
new file mode 100644
index 0000000..a43c5ae
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/AdapterTokenStoreFactory.java
@@ -0,0 +1,26 @@
+package org.keycloak.adapters.springsecurity.token;
+
+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)
+     *
+     * @return a new <code>AdapterTokenStore</code> for the given <code>deployment</code> and <code>request</code>
+     * @throws IllegalArgumentException if either the <code>deployment</code> or <code>request</code> is <code>null</code>
+     */
+    AdapterTokenStore createAdapterTokenStore(KeycloakDeployment deployment, HttpServletRequest request);
+
+}
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
index c96ae7e..da4c192 100644
--- 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
@@ -34,6 +34,8 @@ public class KeycloakAuthenticationToken extends AbstractAuthenticationToken imp
 
     public KeycloakAuthenticationToken(KeycloakAccount account, 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);
         setAuthenticated(true);
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactory.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactory.java
new file mode 100644
index 0000000..f443f72
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactory.java
@@ -0,0 +1,19 @@
+package org.keycloak.adapters.springsecurity.token;
+
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakDeployment;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * {@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) {
+        return new SpringSecurityTokenStore(deployment, request);
+    }
+}
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
index 3c6af11..c16d73f 100644
--- 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
@@ -53,7 +53,7 @@ public class SpringSecurityTokenStore implements AdapterTokenStore {
             return false;
         }
 
-        if (KeycloakAuthenticationToken.class.isAssignableFrom(context.getAuthentication().getClass())) {
+        if (!KeycloakAuthenticationToken.class.isAssignableFrom(context.getAuthentication().getClass())) {
             logger.warn("Expected a KeycloakAuthenticationToken, but found {}", context.getAuthentication());
             return false;
         }
@@ -84,7 +84,7 @@ public class SpringSecurityTokenStore implements AdapterTokenStore {
         Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
 
         if (authentication != null) {
-            throw new IllegalStateException("Went to save Keycloak account {}, but already have {}");
+            throw new IllegalStateException(String.format("Went to save Keycloak account %s, but already have %s", account, authentication));
         }
 
         logger.debug("Saving account info {}", account);
@@ -99,6 +99,7 @@ public class SpringSecurityTokenStore implements AdapterTokenStore {
 
         if (session != null) {
             session.setAttribute(KeycloakSecurityContext.class.getName(), null);
+            session.invalidate();
         }
 
         SecurityContextHolder.clearContext();
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactoryTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactoryTest.java
new file mode 100644
index 0000000..e35efe6
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactoryTest.java
@@ -0,0 +1,81 @@
+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.KeycloakAccount;
+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.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Keycloak client request factory tests.
+ */
+public class KeycloakClientRequestFactoryTest {
+
+    @Spy
+    private KeycloakClientRequestFactory factory;
+
+    @Mock
+    private KeycloakAccount 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();
+    }
+}
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
new file mode 100644
index 0000000..4542033
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java
@@ -0,0 +1,152 @@
+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.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
+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.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.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Keycloak authentication process filter test cases.
+ */
+public class KeycloakAuthenticationProcessingFilterTest {
+
+    private KeycloakAuthenticationProcessingFilter filter;
+
+    @Mock
+    private AuthenticationManager authenticationManager;
+
+    @Mock
+    private AdapterDeploymentContextBean adapterDeploymentContextBean;
+
+    @Mock
+    private FilterChain chain;
+
+    private MockHttpServletRequest request;
+
+    @Mock
+    private HttpServletResponse response;
+
+    @Mock
+    private ApplicationContext applicationContext;
+
+    @Mock
+    private AuthenticationSuccessHandler successHandler;
+
+    @Mock
+    private AuthenticationFailureHandler failureHandler;
+
+    @Mock
+    private KeycloakAccount 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());
+        filter = new KeycloakAuthenticationProcessingFilter(authenticationManager);
+
+        filter.setApplicationContext(applicationContext);
+        filter.setAuthenticationSuccessHandler(successHandler);
+        filter.setAuthenticationFailureHandler(failureHandler);
+
+        when(applicationContext.getBean(eq(AdapterDeploymentContextBean.class))).thenReturn(adapterDeploymentContextBean);
+        when(adapterDeploymentContextBean.getDeployment()).thenReturn(keycloakDeployment);
+        when(keycloakAccount.getPrincipal()).thenReturn(
+                new KeycloakPrincipal<KeycloakSecurityContext>(UUID.randomUUID().toString(), keycloakSecurityContext));
+
+
+        filter.afterPropertiesSet();
+    }
+
+    @Test
+    public void testIsBearerTokenRequest() throws Exception {
+        assertFalse(filter.isBearerTokenRequest(request));
+        this.setAuthHeader(request);
+        assertTrue(filter.isBearerTokenRequest(request));
+    }
+
+    @Test
+    public void testSuccessfulAuthenticationInteractive() throws Exception {
+        Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, 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, authorities);
+        this.setAuthHeader(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.setAuthHeader(request);
+        filter.unsuccessfulAuthentication(request, response, exception);
+        verify(response).sendError(eq(HttpServletResponse.SC_FORBIDDEN), anyString());
+        verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
+                any(AuthenticationException.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 setAuthHeader(MockHttpServletRequest request) {
+        request.addHeader(KeycloakAuthenticationProcessingFilter.AUTHORIZATION_HEADER, "Bearer " + UUID.randomUUID().toString());
+    }
+
+}
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java
new file mode 100644
index 0000000..602f8ca
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java
@@ -0,0 +1,49 @@
+package org.keycloak.adapters.springsecurity.token;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import javax.servlet.http.HttpServletRequest;
+
+import static org.junit.Assert.*;
+
+/**
+ * Spring Security adapter token store factory tests.
+ */
+public class SpringSecurityAdapterTokenStoreFactoryTest {
+
+    private AdapterTokenStoreFactory factory = new SpringSecurityAdapterTokenStoreFactory();
+
+    @Mock
+    private KeycloakDeployment deployment;
+
+    @Mock
+    private HttpServletRequest request;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testCreateAdapterTokenStore() throws Exception {
+        AdapterTokenStore store = factory.createAdapterTokenStore(deployment, request);
+        assertNotNull(store);
+        assertTrue(store instanceof SpringSecurityTokenStore);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateAdapterTokenStoreNullDeployment() throws Exception {
+        factory.createAdapterTokenStore(null, request);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateAdapterTokenStoreNullRequest() throws Exception {
+        factory.createAdapterTokenStore(deployment, null);
+    }
+}
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStoreTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStoreTest.java
new file mode 100644
index 0000000..7088951
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStoreTest.java
@@ -0,0 +1,94 @@
+package org.keycloak.adapters.springsecurity.token;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+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 javax.servlet.http.HttpSession;
+
+import java.security.Principal;
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+/**
+ * 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 {
+        KeycloakAccount 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 {
+        KeycloakAccount 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());
+    }
+}