keycloak-developers
Changes
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/SpringSecurityRequestAuthenticator.java 2(+1 -1)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactory.java 31(+28 -3)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakRestTemplate.java 25(+21 -4)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java 102(+98 -4)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/AdapterTokenStoreFactory.java 26(+26 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/KeycloakAuthenticationToken.java 2(+2 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactory.java 19(+19 -0)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStore.java 5(+3 -2)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactoryTest.java 81(+81 -0)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java 152(+152 -0)
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());
+ }
+}