Merge pull request #1222 from Smartling/KEYCLOAK-1273
Improve Spring Security adapter client to client authorization
This commit is contained in:
commit
53716697ca
12 changed files with 575 additions and 15 deletions
|
@ -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);
|
||||
|
|
|
@ -36,10 +36,35 @@ public class KeycloakClientRequestFactory extends HttpComponentsClientHttpReques
|
|||
|
||||
@Override
|
||||
protected void postProcessHttpRequest(HttpUriRequest request) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
|
||||
KeycloakSecurityContext context = token.getAccount().getKeycloakSecurityContext();
|
||||
|
||||
KeycloakSecurityContext context = this.getKeycloakSecurityContext();
|
||||
request.setHeader(AUTHORIZATION_HEADER, "Bearer " + context.getTokenString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link KeycloakSecurityContext} from the Spring {@link SecurityContextHolder}'s {@link Authentication}.
|
||||
*
|
||||
* @return the current <code>KeycloakSecurityContext</code>
|
||||
*/
|
||||
protected KeycloakSecurityContext getKeycloakSecurityContext() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
KeycloakAuthenticationToken token;
|
||||
KeycloakSecurityContext context;
|
||||
|
||||
if (authentication == null) {
|
||||
throw new IllegalStateException("Cannot set authorization header because there is no authenticated principal");
|
||||
}
|
||||
|
||||
if (!KeycloakAuthenticationToken.class.isAssignableFrom(authentication.getClass())) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Cannot set authorization header because Authentication is of type %s but %s is required",
|
||||
authentication.getClass(), KeycloakAuthenticationToken.class)
|
||||
);
|
||||
}
|
||||
|
||||
token = (KeycloakAuthenticationToken) authentication;
|
||||
context = token.getAccount().getKeycloakSecurityContext();
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue