Merge pull request #1191 from Smartling/KEYCLOAK-1238
Add Spring Security adapter
This commit is contained in:
commit
4a89fe93b5
32 changed files with 1926 additions and 0 deletions
|
@ -31,5 +31,6 @@
|
|||
<module>admin-client</module>
|
||||
<module>osgi-adapter</module>
|
||||
<module>spring-boot</module>
|
||||
<module>spring-security</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
135
integration/spring-security/pom.xml
Executable file
135
integration/spring-security/pom.xml
Executable file
|
@ -0,0 +1,135 @@
|
|||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-spring-security-adapter</artifactId>
|
||||
<name>Keycloak Spring Security Integration</name>
|
||||
<description/>
|
||||
|
||||
<properties>
|
||||
<spring.version>3.2.7.RELEASE</spring.version>
|
||||
<spring-security.version>3.2.7.RELEASE</spring-security.version>
|
||||
<mockito.version>1.9.5</mockito.version>
|
||||
<apache-httpcomponents.version>4.3.6</apache-httpcomponents.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
<version>${spring-security.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-web</artifactId>
|
||||
<version>${spring-security.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${apache-httpcomponents.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.iharder</groupId>
|
||||
<artifactId>base64</artifactId>
|
||||
<version>${base64.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>${bouncycastle.crypto.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-xc</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,58 @@
|
|||
package org.keycloak.adapters.springsecurity;
|
||||
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Bean holding the {@link KeycloakDeployment} and {@link AdapterDeploymentContext} for this
|
||||
* Spring application context. The Keycloak deployment is loaded from the required
|
||||
* <code>WEB-INF/keycloak.json</code> file generated by Keycloak.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AdapterDeploymentContextBean implements ApplicationContextAware, InitializingBean {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private AdapterDeploymentContext deploymentContext;
|
||||
private KeycloakDeployment deployment;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Resource resource = applicationContext.getResource("WEB-INF/keycloak.json");
|
||||
InputStream is = resource.getInputStream();
|
||||
this.deployment = KeycloakDeploymentBuilder.build(is);
|
||||
this.deploymentContext = new AdapterDeploymentContext(deployment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Keycloak {@link AdapterDeploymentContext} for this application context.
|
||||
*
|
||||
* @return the Keycloak {@link AdapterDeploymentContext} for this application context
|
||||
*/
|
||||
public AdapterDeploymentContext getDeploymentContext() {
|
||||
return deploymentContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link KeycloakDeployment} for this application context.
|
||||
*
|
||||
* @return the {@link KeycloakDeployment} for this application context
|
||||
*/
|
||||
public KeycloakDeployment getDeployment() {
|
||||
return deployment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package org.keycloak.adapters.springsecurity;
|
||||
|
||||
/**
|
||||
* Locator interface for Spring context component scanning.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface KeycloakSecurityComponents {
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package org.keycloak.adapters.springsecurity.account;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Represents an authority granted to an {@link Authentication} by the Keycloak server.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakRole implements GrantedAuthority {
|
||||
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* Creates a new granted authority from the given Keycloak role.
|
||||
*
|
||||
* @param role the name of this granted authority
|
||||
*/
|
||||
public KeycloakRole(String role) {
|
||||
Assert.notNull(role, "role cannot be null");
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof KeycloakRole)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KeycloakRole that = (KeycloakRole) o;
|
||||
|
||||
if (!role.equals(that.role)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 3 * role.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeycloakRole{" +
|
||||
"role='" + role + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.keycloak.adapters.springsecurity.account;
|
||||
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.security.Principal;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Concrete, serializable {@link KeycloakAccount} implementation.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SimpleKeycloakAccount implements KeycloakAccount, Serializable {
|
||||
|
||||
private Set<String> roles;
|
||||
private Principal principal;
|
||||
private RefreshableKeycloakSecurityContext securityContext;
|
||||
|
||||
public SimpleKeycloakAccount(Principal principal, Set<String> roles, RefreshableKeycloakSecurityContext securityContext) {
|
||||
this.principal = principal;
|
||||
this.roles = roles;
|
||||
this.securityContext = securityContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
|
||||
return securityContext;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.keycloak.adapters.springsecurity.authentication;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides a Keycloak {@link AuthenticationEntryPoint authentication entry point}.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
/**
|
||||
* Default Keycloak authentication login URI
|
||||
*/
|
||||
public static final String DEFAULT_LOGIN_URI = "/sso/login";
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(KeycloakAuthenticationEntryPoint.class);
|
||||
|
||||
private String loginUri = DEFAULT_LOGIN_URI;
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
|
||||
throws IOException, ServletException {
|
||||
|
||||
String contextAwareLoginUri = request.getContextPath() + loginUri;
|
||||
|
||||
log.debug("Redirecting to login URI {}", contextAwareLoginUri);
|
||||
response.sendRedirect(contextAwareLoginUri);
|
||||
}
|
||||
|
||||
public void setLoginUri(String loginUri) {
|
||||
Assert.notNull(loginUri, "loginUri cannot be null");
|
||||
this.loginUri = loginUri;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package org.keycloak.adapters.springsecurity.authentication;
|
||||
|
||||
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Performs authentication on a {@link KeycloakAuthenticationToken}.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
|
||||
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
|
||||
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
|
||||
|
||||
for (String role : token.getAccount().getRoles()) {
|
||||
grantedAuthorities.add(new KeycloakRole(role));
|
||||
}
|
||||
|
||||
return new KeycloakAuthenticationToken(token.getAccount(), grantedAuthorities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> aClass) {
|
||||
return KeycloakAuthenticationToken.class.isAssignableFrom(aClass);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package org.keycloak.adapters.springsecurity.authentication;
|
||||
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Logs the current user out of Keycloak.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakLogoutHandler implements LogoutHandler {
|
||||
|
||||
public static final String SSO_LOGOUT_COMPLETE_PARAM = "sso_complete";
|
||||
private static final Logger log = LoggerFactory.getLogger(KeycloakLogoutHandler.class);
|
||||
|
||||
private AdapterDeploymentContextBean deploymentContextBean;
|
||||
|
||||
public KeycloakLogoutHandler(AdapterDeploymentContextBean deploymentContextBean) {
|
||||
Assert.notNull(deploymentContextBean);
|
||||
this.deploymentContextBean = deploymentContextBean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
||||
|
||||
if (authentication instanceof AnonymousAuthenticationToken) {
|
||||
log.warn("Attempt to log out an anonymous authentication");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Boolean.valueOf(request.getParameter(SSO_LOGOUT_COMPLETE_PARAM))) {
|
||||
// already logged out
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
handleSingleSignOut(request, response);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unable to redirect to SSO url!", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected String createRedirectUrl(HttpServletRequest request) {
|
||||
|
||||
return UriComponentsBuilder.fromHttpUrl(request.getRequestURL().toString())
|
||||
.replaceQueryParam(SSO_LOGOUT_COMPLETE_PARAM, true).build().toUriString();
|
||||
}
|
||||
|
||||
protected void handleSingleSignOut(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
|
||||
KeycloakDeployment deployment = deploymentContextBean.getDeployment();
|
||||
String redirectUrl = createRedirectUrl(request);
|
||||
|
||||
response.sendRedirect(deployment.getLogoutUrl().queryParam("redirect_uri", redirectUrl).build().toASCIIString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package org.keycloak.adapters.springsecurity.authentication;
|
||||
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.security.Principal;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Request authenticator adapter for Spring Security.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SpringSecurityRequestAuthenticator extends RequestAuthenticator {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SpringSecurityRequestAuthenticator.class);
|
||||
private final HttpServletRequest request;
|
||||
|
||||
/**
|
||||
* Creates a new Spring Security request authenticator.
|
||||
*
|
||||
* @param facade the current <code>HttpFacade</code> (required)
|
||||
* @param request the current <code>HttpServletRequest</code> (required)
|
||||
* @param deployment the <code>KeycloakDeployment</code> (required)
|
||||
* @param tokenStore the <cdoe>AdapterTokenStore</cdoe> (required)
|
||||
* @param sslRedirectPort the SSL redirect port (required)
|
||||
*/
|
||||
public SpringSecurityRequestAuthenticator(
|
||||
HttpFacade facade,
|
||||
HttpServletRequest request,
|
||||
KeycloakDeployment deployment,
|
||||
AdapterTokenStore tokenStore,
|
||||
int sslRedirectPort) {
|
||||
|
||||
super(facade, deployment, tokenStore, sslRedirectPort);
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
|
||||
return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
|
||||
|
||||
final RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
|
||||
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
|
||||
final KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, securityContext);
|
||||
|
||||
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
|
||||
this.tokenStore.saveAccountInfo(account);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
|
||||
|
||||
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
|
||||
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
|
||||
final KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, securityContext);
|
||||
|
||||
logger.warn("Completing bearer authentication. Bearer roles: {} ",roles);
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(new KeycloakAuthenticationToken(account));
|
||||
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHttpSessionId(boolean create) {
|
||||
HttpSession session = request.getSession(create);
|
||||
return session != null ? session.getId() : null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.keycloak.adapters.springsecurity.client;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Factory for {@link ClientHttpRequest} objects created for server to server secured
|
||||
* communication using OAuth2 bearer tokens issued by Keycloak.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@Component
|
||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||
public class KeycloakClientRequestFactory extends HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory {
|
||||
|
||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
|
||||
public KeycloakClientRequestFactory() {
|
||||
super(HttpClients.custom()
|
||||
.disableCookieManagement()
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postProcessHttpRequest(HttpUriRequest request) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
|
||||
KeycloakSecurityContext context = token.getAccount().getKeycloakSecurityContext();
|
||||
|
||||
request.setHeader(AUTHORIZATION_HEADER, "Bearer " + context.getTokenString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.keycloak.adapters.springsecurity.client;
|
||||
|
||||
import org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Created by scott on 4/22/15.
|
||||
*/
|
||||
//@Service
|
||||
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||
public class KeycloakRestTemplate extends RestTemplate implements RestOperations {
|
||||
public KeycloakRestTemplate(KeycloakClientRequestFactory factory) {
|
||||
super(factory);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package org.keycloak.adapters.springsecurity.config;
|
||||
|
||||
import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakLogoutHandler;
|
||||
import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter;
|
||||
import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
|
||||
import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.authentication.logout.LogoutFilter;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||
|
||||
/**
|
||||
* Provides a convenient base class for creating a {@link WebSecurityConfigurer}
|
||||
* instance secured by Keycloak. This implementation allows customization by overriding methods.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*
|
||||
* @see EnableWebSecurity
|
||||
* @see EnableWebMvcSecurity
|
||||
*/
|
||||
public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
|
||||
|
||||
@Bean
|
||||
protected AdapterDeploymentContextBean adapterDeploymentContextBean() {
|
||||
return new AdapterDeploymentContextBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected AuthenticationEntryPoint authenticationEntryPoint()
|
||||
{
|
||||
return new KeycloakAuthenticationEntryPoint();
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
|
||||
return new KeycloakAuthenticationProvider();
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
|
||||
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean());
|
||||
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected KeycloakPreAuthActionsFilter keycloakPreAuthActionsFilter() {
|
||||
return new KeycloakPreAuthActionsFilter(httpSessionManager());
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected HttpSessionManager httpSessionManager() {
|
||||
return new HttpSessionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected KeycloakLogoutHandler keycloakLogoutHandler() {
|
||||
return new KeycloakLogoutHandler(adapterDeploymentContextBean());
|
||||
}
|
||||
|
||||
protected abstract SessionAuthenticationStrategy sessionAuthenticationStrategy();
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.sessionManagement()
|
||||
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
|
||||
.and()
|
||||
.addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class)
|
||||
.addFilterBefore(keycloakAuthenticationProcessingFilter(), X509AuthenticationFilter.class)
|
||||
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package org.keycloak.adapters.springsecurity.facade;
|
||||
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.facade.WrappedHttpServletRequest;
|
||||
import org.keycloak.adapters.springsecurity.facade.WrappedHttpServletResponse;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.security.cert.X509Certificate;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Simple {@link HttpFacade} wrapping an {@link HttpServletRequest} and {@link HttpServletResponse}.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SimpleHttpFacade implements HttpFacade {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
private final HttpServletResponse response;
|
||||
|
||||
/**
|
||||
* Creates a new simple HTTP facade for the given request and response.
|
||||
*
|
||||
* @param request the current <code>HttpServletRequest</code> (required)
|
||||
* @param response the current <code>HttpServletResponse</code> (required)
|
||||
*/
|
||||
public SimpleHttpFacade(HttpServletRequest request, HttpServletResponse response) {
|
||||
Assert.notNull(request, "HttpServletRequest required");
|
||||
Assert.notNull(response, "HttpServletResponse required");
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSecurityContext getSecurityContext() {
|
||||
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
|
||||
if (context != null && context.getAuthentication() != null) {
|
||||
return (KeycloakSecurityContext) context.getAuthentication().getDetails();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getRequest() {
|
||||
return new WrappedHttpServletRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response getResponse() {
|
||||
return new WrappedHttpServletResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain() {
|
||||
// TODO: implement me
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package org.keycloak.adapters.springsecurity.facade;
|
||||
|
||||
import org.keycloak.adapters.HttpFacade.Cookie;
|
||||
import org.keycloak.adapters.HttpFacade.Request;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Concrete Keycloak {@link Request request} implementation wrapping an {@link HttpServletRequest}.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
class WrappedHttpServletRequest implements Request {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
/**
|
||||
* Creates a new request for the given <code>HttpServletRequest</code>
|
||||
*
|
||||
* @param request the current <code>HttpServletRequest</code> (required)
|
||||
*/
|
||||
public WrappedHttpServletRequest(HttpServletRequest request) {
|
||||
Assert.notNull(request, "HttpServletRequest required");
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return request.getMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURI() {
|
||||
StringBuffer buf = request.getRequestURL();
|
||||
if (request.getQueryString() != null) {
|
||||
buf.append('?').append(request.getQueryString());
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return request.isSecure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryParamValue(String param) {
|
||||
return request.getParameter(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cookie getCookie(String cookieName) {
|
||||
|
||||
for (javax.servlet.http.Cookie cookie : request.getCookies()) {
|
||||
if (cookie.getName().equals(cookieName)) {
|
||||
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
return request.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name) {
|
||||
Enumeration<String> values = request.getHeaders(name);
|
||||
List<String> array = new ArrayList<String>();
|
||||
|
||||
while (values.hasMoreElements()) {
|
||||
array.add(values.nextElement());
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
try {
|
||||
return request.getInputStream();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to get request input stream", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package org.keycloak.adapters.springsecurity.facade;
|
||||
|
||||
import org.keycloak.adapters.HttpFacade.Response;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Concrete Keycloak {@link Response response} implementation wrapping an {@link HttpServletResponse}.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
class WrappedHttpServletResponse implements Response {
|
||||
|
||||
private final HttpServletResponse response;
|
||||
|
||||
/**
|
||||
* Creates a new response for the given <code>HttpServletResponse</code>.
|
||||
*
|
||||
* @param response the current <code>HttpServletResponse</code> (required)
|
||||
*/
|
||||
public WrappedHttpServletResponse(HttpServletResponse response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetCookie(String name, String path) {
|
||||
Cookie cookie = new Cookie(name, "");
|
||||
cookie.setMaxAge(0);
|
||||
if (path != null) {
|
||||
cookie.setPath(path);
|
||||
}
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
|
||||
Cookie cookie = new Cookie(name, value);
|
||||
|
||||
if (path != null) {
|
||||
cookie.setPath(path);
|
||||
}
|
||||
|
||||
if (domain != null) {
|
||||
cookie.setDomain(domain);
|
||||
}
|
||||
|
||||
cookie.setMaxAge(maxAge);
|
||||
cookie.setSecure(secure);
|
||||
cookie.setHttpOnly(httpOnly);
|
||||
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(int status) {
|
||||
response.setStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(String name, String value) {
|
||||
response.addHeader(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value) {
|
||||
response.setHeader(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
try {
|
||||
return response.getOutputStream();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to return response output stream", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int code, String message) {
|
||||
try {
|
||||
response.sendError(code, message);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to set HTTP status", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
// TODO: do we need this?
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Provides an {@link org.keycloak.adapters.HttpFacade} implementation.
|
||||
*/
|
||||
package org.keycloak.adapters.springsecurity.facade;
|
|
@ -0,0 +1,128 @@
|
|||
package org.keycloak.adapters.springsecurity.filter;
|
||||
|
||||
import org.keycloak.adapters.AuthChallenge;
|
||||
import org.keycloak.adapters.AuthOutcome;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
|
||||
import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator;
|
||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.token.SpringSecurityTokenStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides a Keycloak authentication processing filter.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
|
||||
|
||||
/**
|
||||
* Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
|
||||
* and any request with a <code>Authorization</code> header.
|
||||
*/
|
||||
public static final RequestMatcher DEFAULT_REQUEST_MATCHER =
|
||||
new OrRequestMatcher(new AntPathRequestMatcher("/sso/login"), new RequestHeaderRequestMatcher("Authorization"));
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticationProcessingFilter.class);
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private AdapterDeploymentContextBean adapterDeploymentContextBean;
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
/**
|
||||
* Creates a new Keycloak authentication processing filter with given {@link AuthenticationManager} and the
|
||||
* {@link KeycloakAuthenticationProcessingFilter#DEFAULT_REQUEST_MATCHER default request matcher}.
|
||||
*
|
||||
* @param authenticationManager the {@link AuthenticationManager} to authenticate requests (cannot be null)
|
||||
* @see KeycloakAuthenticationProcessingFilter#DEFAULT_REQUEST_MATCHER
|
||||
*/
|
||||
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
|
||||
this(authenticationManager, DEFAULT_REQUEST_MATCHER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Keycloak authentication processing filter with given {@link AuthenticationManager} and
|
||||
* {@link RequestMatcher}.
|
||||
* <p>
|
||||
* Note: the given request matcher must support matching the <code>Authorization</code> header if
|
||||
* bearer token authentication is to be accepted.
|
||||
* </p>
|
||||
*
|
||||
* @param authenticationManager the {@link AuthenticationManager} to authenticate requests (cannot be null)
|
||||
* @param requiresAuthenticationRequestMatcher the {@link RequestMatcher} used to determine if authentication
|
||||
* is required (cannot be null)
|
||||
*
|
||||
* @see RequestHeaderRequestMatcher
|
||||
* @see OrRequestMatcher
|
||||
*
|
||||
*/
|
||||
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager, RequestMatcher
|
||||
requiresAuthenticationRequestMatcher) {
|
||||
super(requiresAuthenticationRequestMatcher);
|
||||
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||
this.authenticationManager = authenticationManager;
|
||||
super.setAuthenticationManager(authenticationManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
adapterDeploymentContextBean= applicationContext.getBean(AdapterDeploymentContextBean.class);
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||
throws AuthenticationException, IOException, ServletException {
|
||||
|
||||
log.debug("Attempting Keycloak authentication");
|
||||
|
||||
KeycloakDeployment deployment = adapterDeploymentContextBean.getDeployment();
|
||||
SimpleHttpFacade facade = new SimpleHttpFacade(request, response);
|
||||
SpringSecurityTokenStore tokenStore = new SpringSecurityTokenStore(deployment, request);
|
||||
SpringSecurityRequestAuthenticator authenticator
|
||||
= new SpringSecurityRequestAuthenticator(facade, request, deployment, tokenStore, -1);
|
||||
|
||||
AuthOutcome result = authenticator.authenticate();
|
||||
AuthChallenge challenge = authenticator.getChallenge();
|
||||
|
||||
log.debug("Auth outcome: {}", result);
|
||||
|
||||
if (challenge != null) {
|
||||
challenge.challenge(facade);
|
||||
}
|
||||
|
||||
if (AuthOutcome.AUTHENTICATED.equals(result)) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
|
||||
return authenticationManager.authenticate(authentication);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package org.keycloak.adapters.springsecurity.filter;
|
||||
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
||||
import org.keycloak.adapters.UserSessionManagement;
|
||||
import org.keycloak.adapters.springsecurity.AdapterDeploymentContextBean;
|
||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Exposes a Keycloak adapter {@link PreAuthActionsHandler} as a Spring Security filter.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakPreAuthActionsFilter extends GenericFilterBean implements ApplicationContextAware {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(KeycloakPreAuthActionsFilter.class);
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private AdapterDeploymentContext deploymentContext;
|
||||
private UserSessionManagement userSessionManagement;
|
||||
|
||||
public KeycloakPreAuthActionsFilter() {
|
||||
super();
|
||||
}
|
||||
|
||||
public KeycloakPreAuthActionsFilter(UserSessionManagement userSessionManagement) {
|
||||
this.userSessionManagement = userSessionManagement;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initFilterBean() throws ServletException {
|
||||
AdapterDeploymentContextBean contextBean = applicationContext.getBean(AdapterDeploymentContextBean.class);
|
||||
deploymentContext = contextBean.getDeploymentContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
HttpFacade facade = new SimpleHttpFacade((HttpServletRequest)request, (HttpServletResponse)response);
|
||||
PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deploymentContext, facade);
|
||||
if (handler.handleRequest()) {
|
||||
log.info("Pre-auth filter handled request: {}", ((HttpServletRequest) request).getRequestURI());
|
||||
} else {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
public void setUserSessionManagement(UserSessionManagement userSessionManagement) {
|
||||
this.userSessionManagement = userSessionManagement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Provides Spring Security filters for Keycloak.
|
||||
*/
|
||||
package org.keycloak.adapters.springsecurity.filter;
|
|
@ -0,0 +1,57 @@
|
|||
package org.keycloak.adapters.springsecurity.management;
|
||||
|
||||
import org.keycloak.adapters.UserSessionManagement;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpSessionEvent;
|
||||
import javax.servlet.http.HttpSessionListener;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User session manager for handling logout of Spring Secured sessions.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@Component
|
||||
public class HttpSessionManager implements HttpSessionListener, UserSessionManagement {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HttpSessionManager.class);
|
||||
private SessionManagementStrategy sessions = new LocalSessionManagementStrategy();
|
||||
|
||||
@Override
|
||||
public void sessionCreated(HttpSessionEvent event) {
|
||||
log.debug("Session created: {}", event.getSession().getId());
|
||||
HttpSession session = event.getSession();
|
||||
sessions.store(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionDestroyed(HttpSessionEvent event) {
|
||||
sessions.remove(event.getSession().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logoutAll() {
|
||||
log.info("Received request to log out all users.");
|
||||
for (HttpSession session : sessions.getAll()) {
|
||||
session.invalidate();
|
||||
}
|
||||
sessions.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logoutHttpSessions(List<String> ids) {
|
||||
log.info("Received request to log out {} session(s): {}", ids.size(), ids);
|
||||
for (String id : ids) {
|
||||
HttpSession session = sessions.remove(id);
|
||||
if (session != null) {
|
||||
session.invalidate();
|
||||
}
|
||||
}
|
||||
sessions.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package org.keycloak.adapters.springsecurity.management;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Created by scott on 4/24/15.
|
||||
*/
|
||||
public class LocalSessionManagementStrategy implements SessionManagementStrategy {
|
||||
|
||||
private final Map<String, HttpSession> sessions = new ConcurrentHashMap<String, HttpSession>();
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
sessions.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<HttpSession> getAll() {
|
||||
return sessions.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(HttpSession session) {
|
||||
sessions.put(session.getId(), session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpSession remove(String id) {
|
||||
return sessions.remove(id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package org.keycloak.adapters.springsecurity.management;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Defines a session management strategy.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface SessionManagementStrategy {
|
||||
|
||||
/**
|
||||
* Removes all sessions.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Returns a collection containing all sessions.
|
||||
*
|
||||
* @return a <code>Collection</code> of all known <code>HttpSession</code>s, if any;
|
||||
* an empty <code>Collection</code> otherwise
|
||||
*/
|
||||
Collection<HttpSession> getAll();
|
||||
|
||||
/**
|
||||
* Stores the given session.
|
||||
*
|
||||
* @param session the <code>HttpSession</code> to store (required)
|
||||
*/
|
||||
void store(HttpSession session);
|
||||
|
||||
/**
|
||||
* The unique identifier for the session to remove.
|
||||
*
|
||||
* @param id the unique identifier for the session to remove (required)
|
||||
* @return the <code>HttpSession</code> if it exists; <code>null</code> otherwise
|
||||
*/
|
||||
HttpSession remove(String id);
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Provides a Keycloak adapter for Spring Security.
|
||||
*/
|
||||
package org.keycloak.adapters.springsecurity;
|
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.adapters.springsecurity.registration;
|
||||
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
||||
/**
|
||||
* Manages registration of application nodes.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface NodeManager {
|
||||
|
||||
/**
|
||||
* Registers the given deployment with the Keycloak server.
|
||||
*
|
||||
* @param deployment the deployment to register (required)
|
||||
*/
|
||||
void register(KeycloakDeployment deployment);
|
||||
|
||||
/**
|
||||
* Unregisters the give deployment from the Keycloak server
|
||||
* .
|
||||
* @param deployment the deployment to unregister (required)
|
||||
*/
|
||||
void unregister(KeycloakDeployment deployment);
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package org.keycloak.adapters.springsecurity.token;
|
||||
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Represents the token for a Keycloak authentication request or for an authenticated principal once the request has been
|
||||
* processed by the {@link AuthenticationManager#authenticate(Authentication)}.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakAuthenticationToken extends AbstractAuthenticationToken implements Authentication {
|
||||
|
||||
private Principal principal;
|
||||
|
||||
/**
|
||||
* Creates a new, unauthenticated Keycloak security token for the given account.
|
||||
*/
|
||||
public KeycloakAuthenticationToken(KeycloakAccount account) {
|
||||
super(null);
|
||||
Assert.notNull(account, "KeycloakAccount cannot be null");
|
||||
Assert.notNull(account.getPrincipal(), "KeycloakAccount.getPrincipal() cannot be null");
|
||||
this.principal = account.getPrincipal();
|
||||
this.setDetails(account);
|
||||
}
|
||||
|
||||
public KeycloakAuthenticationToken(KeycloakAccount account, Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.principal = account.getPrincipal();
|
||||
this.setDetails(account);
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return this.getAccount().getKeycloakSecurityContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
|
||||
public KeycloakAccount getAccount() {
|
||||
return (KeycloakAccount) this.getDetails();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package org.keycloak.adapters.springsecurity.token;
|
||||
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* Simple Spring {@link SecurityContext security context} aware {@link AdapterTokenStore adapter token store}.
|
||||
*
|
||||
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SpringSecurityTokenStore implements AdapterTokenStore {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SpringSecurityTokenStore.class);
|
||||
|
||||
private final KeycloakDeployment deployment;
|
||||
private final HttpServletRequest request;
|
||||
|
||||
public SpringSecurityTokenStore(KeycloakDeployment deployment, HttpServletRequest request) {
|
||||
Assert.notNull(deployment, "KeycloakDeployment is required");
|
||||
Assert.notNull(request, "HttpServletRequest is required");
|
||||
this.deployment = deployment;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCurrentToken() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCached(RequestAuthenticator authenticator) {
|
||||
|
||||
logger.debug("Checking if {} is cached", authenticator);
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
KeycloakAuthenticationToken token;
|
||||
KeycloakSecurityContext keycloakSecurityContext;
|
||||
|
||||
if (context == null || context.getAuthentication() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (KeycloakAuthenticationToken.class.isAssignableFrom(context.getAuthentication().getClass())) {
|
||||
logger.warn("Expected a KeycloakAuthenticationToken, but found {}", context.getAuthentication());
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info("Remote logged in already. Establishing state from security context.");
|
||||
token = (KeycloakAuthenticationToken) context.getAuthentication();
|
||||
keycloakSecurityContext = token.getAccount().getKeycloakSecurityContext();
|
||||
|
||||
if (!deployment.getRealm().equals(keycloakSecurityContext.getRealm())) {
|
||||
logger.info("Account from security context is from a different realm than for the request.");
|
||||
logout();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keycloakSecurityContext.getToken().isExpired()) {
|
||||
logger.warn("Security token expired ... not returning from cache");
|
||||
return false;
|
||||
}
|
||||
|
||||
request.setAttribute(KeycloakSecurityContext.class.getName(), keycloakSecurityContext);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAccountInfo(KeycloakAccount account) {
|
||||
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (authentication != null) {
|
||||
throw new IllegalStateException("Went to save Keycloak account {}, but already have {}");
|
||||
}
|
||||
|
||||
logger.debug("Saving account info {}", account);
|
||||
SecurityContextHolder.getContext().setAuthentication(new KeycloakAuthenticationToken(account));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout() {
|
||||
|
||||
logger.debug("Handling logout request");
|
||||
HttpSession session = request.getSession(false);
|
||||
|
||||
if (session != null) {
|
||||
session.setAttribute(KeycloakSecurityContext.class.getName(), null);
|
||||
}
|
||||
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveRequest() {
|
||||
// no-op, Spring Security will handle this
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean restoreRequest() {
|
||||
// no-op, Spring Security will handle this
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package org.keycloak.adapters.springsecurity.authentication;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Keycloak authentication entry point tests.
|
||||
*/
|
||||
public class KeycloakAuthenticationEntryPointTest {
|
||||
|
||||
private KeycloakAuthenticationEntryPoint authenticationEntryPoint;
|
||||
private MockHttpServletRequest request;
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
authenticationEntryPoint = new KeycloakAuthenticationEntryPoint();
|
||||
request = new MockHttpServletRequest();
|
||||
response = new MockHttpServletResponse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommence() throws Exception {
|
||||
authenticationEntryPoint.commence(request, response, null);
|
||||
assertEquals(HttpStatus.FOUND.value(), response.getStatus());
|
||||
assertEquals(KeycloakAuthenticationEntryPoint.DEFAULT_LOGIN_URI, response.getHeader("Location"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommenceNotRootContext() throws Exception {
|
||||
String contextPath = "/foo";
|
||||
request.setContextPath(contextPath);
|
||||
authenticationEntryPoint.commence(request, response, null);
|
||||
assertEquals(HttpStatus.FOUND.value(), response.getStatus());
|
||||
assertEquals(contextPath + KeycloakAuthenticationEntryPoint.DEFAULT_LOGIN_URI, response.getHeader("Location"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetLoginUri() throws Exception {
|
||||
final String logoutUri = "/foo";
|
||||
authenticationEntryPoint.setLoginUri(logoutUri);
|
||||
authenticationEntryPoint.commence(request, response, null);
|
||||
assertEquals(HttpStatus.FOUND.value(), response.getStatus());
|
||||
assertEquals(logoutUri, response.getHeader("Location"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package org.keycloak.adapters.springsecurity.authentication;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.mockito.internal.util.collections.Sets;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Keycloak authentication provider tests.
|
||||
*/
|
||||
public class KeycloakAuthenticationProviderTest {
|
||||
|
||||
private KeycloakAuthenticationProvider provider = new KeycloakAuthenticationProvider();
|
||||
private KeycloakAuthenticationToken token;
|
||||
private Set<String> roles = Sets.newSet("user", "admin");
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
Principal principal = mock(Principal.class);
|
||||
RefreshableKeycloakSecurityContext securityContext = mock(RefreshableKeycloakSecurityContext.class);
|
||||
KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, securityContext);
|
||||
|
||||
token = new KeycloakAuthenticationToken(account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate() throws Exception {
|
||||
Authentication result = provider.authenticate(token);
|
||||
assertNotNull(result);
|
||||
assertEquals(roles.size(), result.getAuthorities().size());
|
||||
assertTrue(result.isAuthenticated());
|
||||
assertNotNull(result.getPrincipal());
|
||||
assertNotNull(result.getCredentials());
|
||||
assertNotNull(result.getDetails());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupports() throws Exception {
|
||||
assertTrue(provider.supports(KeycloakAuthenticationToken.class));
|
||||
assertFalse(provider.supports(PreAuthenticatedAuthenticationToken.class));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package org.keycloak.adapters.springsecurity.authentication;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.internal.util.collections.Sets;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Spring Security request authenticator tests.
|
||||
*/
|
||||
public class SpringSecurityRequestAuthenticatorTest {
|
||||
|
||||
private SpringSecurityRequestAuthenticator authenticator;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
@Mock
|
||||
private KeycloakDeployment deployment;
|
||||
|
||||
@Mock
|
||||
private AdapterTokenStore tokenStore;
|
||||
|
||||
@Mock
|
||||
private KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal;
|
||||
|
||||
@Mock
|
||||
private AccessToken accessToken;
|
||||
|
||||
@Mock
|
||||
private AccessToken.Access access;
|
||||
|
||||
@Mock
|
||||
private RefreshableKeycloakSecurityContext refreshableKeycloakSecurityContext;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
request = spy(new MockHttpServletRequest());
|
||||
response = new MockHttpServletResponse();
|
||||
HttpFacade facade = new SimpleHttpFacade(request, response);
|
||||
|
||||
authenticator = new SpringSecurityRequestAuthenticator(facade, request, deployment, tokenStore, 443);
|
||||
|
||||
// mocks
|
||||
when(principal.getKeycloakSecurityContext()).thenReturn(refreshableKeycloakSecurityContext);
|
||||
|
||||
when(refreshableKeycloakSecurityContext.getDeployment()).thenReturn(deployment);
|
||||
when(refreshableKeycloakSecurityContext.getToken()).thenReturn(accessToken);
|
||||
|
||||
when(accessToken.getRealmAccess()).thenReturn(access);
|
||||
when(access.getRoles()).thenReturn(Sets.newSet("user", "admin"));
|
||||
|
||||
when(deployment.isUseResourceRoleMappings()).thenReturn(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateOAuthAuthenticator() throws Exception {
|
||||
OAuthRequestAuthenticator oathAuthenticator = authenticator.createOAuthAuthenticator();
|
||||
assertNotNull(oathAuthenticator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompleteOAuthAuthentication() throws Exception {
|
||||
authenticator.completeOAuthAuthentication(principal);
|
||||
verify(request).setAttribute(eq(KeycloakSecurityContext.class.getName()), eq(refreshableKeycloakSecurityContext));
|
||||
verify(tokenStore).saveAccountInfo(any(KeycloakAccount.class)); // FIXME: should verify account
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompleteBearerAuthentication() throws Exception {
|
||||
authenticator.completeBearerAuthentication(principal, "foo");
|
||||
verify(request).setAttribute(eq(KeycloakSecurityContext.class.getName()), eq(refreshableKeycloakSecurityContext));
|
||||
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
|
||||
assertTrue(KeycloakAuthenticationToken.class.isAssignableFrom(SecurityContextHolder.getContext().getAuthentication().getClass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHttpSessionIdTrue() throws Exception {
|
||||
String sessionId = authenticator.getHttpSessionId(true);
|
||||
assertNotNull(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHttpSessionIdFalse() throws Exception {
|
||||
String sessionId = authenticator.getHttpSessionId(false);
|
||||
assertNull(sessionId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package org.keycloak.adapters.springsecurity.facade;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Wrapped HTTP servlet request tests.
|
||||
*/
|
||||
public class WrappedHttpServletRequestTest {
|
||||
|
||||
private static final String COOKIE_NAME = "oreo";
|
||||
private static final String HEADER_MULTI_VALUE = "Multi";
|
||||
private static final String HEADER_SINGLE_VALUE = "Single";
|
||||
private static final String REQUEST_METHOD = RequestMethod.GET.name();
|
||||
private static final String REQUEST_URI = "/foo/bar";
|
||||
private static final String QUERY_PARM_1 = "code";
|
||||
private static final String QUERY_PARM_2 = "code2";
|
||||
|
||||
private WrappedHttpServletRequest request;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
|
||||
request = new WrappedHttpServletRequest(mockHttpServletRequest);
|
||||
|
||||
mockHttpServletRequest.setMethod(REQUEST_METHOD);
|
||||
mockHttpServletRequest.setRequestURI(REQUEST_URI);
|
||||
|
||||
mockHttpServletRequest.setSecure(true);
|
||||
mockHttpServletRequest.setScheme("https");
|
||||
|
||||
mockHttpServletRequest.addHeader(HEADER_SINGLE_VALUE, "baz");
|
||||
mockHttpServletRequest.addHeader(HEADER_MULTI_VALUE, "foo");
|
||||
mockHttpServletRequest.addHeader(HEADER_MULTI_VALUE, "bar");
|
||||
|
||||
mockHttpServletRequest.addParameter(QUERY_PARM_1, "java");
|
||||
mockHttpServletRequest.addParameter(QUERY_PARM_2, "groovy");
|
||||
mockHttpServletRequest.setQueryString(String.format("%s=%s&%s=%s", QUERY_PARM_1, "java", QUERY_PARM_2, "groovy"));
|
||||
mockHttpServletRequest.setCookies(new Cookie(COOKIE_NAME, "yum"));
|
||||
|
||||
mockHttpServletRequest.setContent("All work and no play makes Jack a dull boy".getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMethod() throws Exception {
|
||||
assertNotNull(request.getMethod());
|
||||
assertEquals(REQUEST_METHOD, request.getMethod());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetURI() throws Exception {
|
||||
assertEquals("https://localhost:80" + REQUEST_URI + "?code=java&code2=groovy" , request.getURI());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsSecure() throws Exception {
|
||||
assertTrue(request.isSecure());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetQueryParamValue() throws Exception {
|
||||
assertNotNull(request.getQueryParamValue(QUERY_PARM_1));
|
||||
assertNotNull(request.getQueryParamValue(QUERY_PARM_2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCookie() throws Exception {
|
||||
assertNotNull(request.getCookie(COOKIE_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeader() throws Exception {
|
||||
String header = request.getHeader(HEADER_SINGLE_VALUE);
|
||||
assertNotNull(header);
|
||||
assertEquals("baz", header);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeaders() throws Exception {
|
||||
List<String> headers = request.getHeaders(HEADER_MULTI_VALUE);
|
||||
assertNotNull(headers);
|
||||
assertEquals(2, headers.size());
|
||||
assertTrue(headers.contains("foo"));
|
||||
assertTrue(headers.contains("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInputStream() throws Exception {
|
||||
assertNotNull(request.getInputStream());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRemoteAddr() throws Exception {
|
||||
assertNotNull(request.getRemoteAddr());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package org.keycloak.adapters.springsecurity.facade;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class WrappedHttpServletResponseTest {
|
||||
|
||||
private static final String COOKIE_DOMAIN = ".keycloak.org";
|
||||
private static final String COOKIE_NAME = "foo";
|
||||
private static final String COOKIE_PATH = "/bar";
|
||||
private static final String COOKIE_VALUE = "onegreatcookie";
|
||||
private static final String HEADER = "Test";
|
||||
|
||||
private WrappedHttpServletResponse response;
|
||||
private MockHttpServletResponse mockResponse;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mockResponse = spy(new MockHttpServletResponse());
|
||||
response = new WrappedHttpServletResponse(mockResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetCookie() throws Exception {
|
||||
response.resetCookie(COOKIE_NAME, COOKIE_PATH);
|
||||
verify(mockResponse).addCookie(any(Cookie.class));
|
||||
assertEquals(COOKIE_NAME, mockResponse.getCookie(COOKIE_NAME).getName());
|
||||
assertEquals(COOKIE_PATH, mockResponse.getCookie(COOKIE_NAME).getPath());
|
||||
assertEquals(0, mockResponse.getCookie(COOKIE_NAME).getMaxAge());
|
||||
assertEquals("", mockResponse.getCookie(COOKIE_NAME).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCookie() throws Exception {
|
||||
int maxAge = 300;
|
||||
response.setCookie(COOKIE_NAME, COOKIE_VALUE, COOKIE_PATH, COOKIE_DOMAIN, maxAge, false, true);
|
||||
verify(mockResponse).addCookie(any(Cookie.class));
|
||||
assertEquals(COOKIE_NAME, mockResponse.getCookie(COOKIE_NAME).getName());
|
||||
assertEquals(COOKIE_PATH, mockResponse.getCookie(COOKIE_NAME).getPath());
|
||||
assertEquals(COOKIE_DOMAIN, mockResponse.getCookie(COOKIE_NAME).getDomain());
|
||||
assertEquals(maxAge, mockResponse.getCookie(COOKIE_NAME).getMaxAge());
|
||||
assertEquals(COOKIE_VALUE, mockResponse.getCookie(COOKIE_NAME).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetStatus() throws Exception {
|
||||
int status = HttpStatus.OK.value();
|
||||
response.setStatus(status);
|
||||
verify(mockResponse).setStatus(eq(status));
|
||||
assertEquals(status, mockResponse.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddHeader() throws Exception {
|
||||
String headerValue = "foo";
|
||||
response.addHeader(HEADER, headerValue);
|
||||
verify(mockResponse).addHeader(eq(HEADER), eq(headerValue));
|
||||
assertTrue(mockResponse.containsHeader(HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetHeader() throws Exception {
|
||||
String headerValue = "foo";
|
||||
response.setHeader(HEADER, headerValue);
|
||||
verify(mockResponse).setHeader(eq(HEADER), eq(headerValue));
|
||||
assertTrue(mockResponse.containsHeader(HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOutputStream() throws Exception {
|
||||
assertNotNull(response.getOutputStream());
|
||||
verify(mockResponse).getOutputStream();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendError() throws Exception {
|
||||
int status = HttpStatus.UNAUTHORIZED.value();
|
||||
String reason = HttpStatus.UNAUTHORIZED.getReasonPhrase();
|
||||
|
||||
response.sendError(status, reason);
|
||||
verify(mockResponse).sendError(eq(status), eq(reason));
|
||||
assertEquals(status, mockResponse.getStatus());
|
||||
assertEquals(reason, mockResponse.getErrorMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testEnd() throws Exception {
|
||||
// TODO: what is an ended response, one that's committed?
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue