Merge pull request #2295 from patriot1burke/master
unsecure url has principal
This commit is contained in:
commit
84de9cca8e
16 changed files with 156 additions and 8 deletions
|
@ -18,6 +18,8 @@
|
|||
package org.keycloak.adapters.jetty.core;
|
||||
|
||||
import org.eclipse.jetty.security.DefaultUserIdentity;
|
||||
import org.eclipse.jetty.security.IdentityService;
|
||||
import org.eclipse.jetty.security.LoginService;
|
||||
import org.eclipse.jetty.security.ServerAuthException;
|
||||
import org.eclipse.jetty.security.UserAuthentication;
|
||||
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
|
||||
|
@ -135,10 +137,44 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
|
|||
return new DefaultUserIdentity(theSubject, principal, theRoles);
|
||||
}
|
||||
|
||||
private class DummyLoginService implements LoginService {
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserIdentity login(String username, Object credentials) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserIdentity user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityService getIdentityService() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentityService(IdentityService service) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(UserIdentity user) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(AuthConfiguration configuration) {
|
||||
//super.setConfiguration(configuration);
|
||||
initializeKeycloak();
|
||||
// need this so that getUserPrincipal does not throw NPE
|
||||
_loginService = new DummyLoginService();
|
||||
String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
|
||||
setErrorPage(error);
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
|
|||
|
||||
protected void cleanSession(HttpSession session) {
|
||||
session.removeAttribute(KeycloakAccount.class.getName());
|
||||
session.removeAttribute(KeycloakSecurityContext.class.getName());
|
||||
clearSavedRequest(session);
|
||||
}
|
||||
|
||||
|
@ -160,6 +161,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
|
|||
SerializableKeycloakAccount sAccount = new SerializableKeycloakAccount(roles, account.getPrincipal(), securityContext);
|
||||
HttpSession httpSession = request.getSession();
|
||||
httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
|
||||
httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext());
|
||||
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId());
|
||||
//String username = securityContext.getToken().getSubject();
|
||||
//log.fine("userSessionManagement.login: " + username);
|
||||
|
|
|
@ -69,12 +69,22 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple
|
|||
// just in case session got serialized
|
||||
if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
|
||||
|
||||
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
|
||||
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
|
||||
request.setAttribute(KeycloakSecurityContext.class.getName(), session);
|
||||
request.setUserPrincipal(account.getPrincipal());
|
||||
request.setAuthType("KEYCLOAK");
|
||||
return;
|
||||
}
|
||||
|
||||
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
|
||||
// not be updated
|
||||
boolean success = session.refreshExpiredToken(false);
|
||||
if (success && session.isActive()) return;
|
||||
if (success && session.isActive()) {
|
||||
request.setAttribute(KeycloakSecurityContext.class.getName(), session);
|
||||
request.setUserPrincipal(account.getPrincipal());
|
||||
request.setAuthType("KEYCLOAK");
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
|
||||
log.fine("Cleanup and expire session " + catalinaSession.getId() + " after failed refresh");
|
||||
|
@ -85,6 +95,8 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple
|
|||
}
|
||||
|
||||
protected void cleanSession(Session catalinaSession) {
|
||||
catalinaSession.getSession().removeAttribute(KeycloakSecurityContext.class.getName());
|
||||
catalinaSession.getSession().removeAttribute(SerializableKeycloakAccount.class.getName());
|
||||
catalinaSession.getSession().removeAttribute(OidcKeycloakAccount.class.getName());
|
||||
catalinaSession.setPrincipal(null);
|
||||
catalinaSession.setAuthType(null);
|
||||
|
@ -164,6 +176,7 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple
|
|||
session.setPrincipal(principal);
|
||||
session.setAuthType("KEYCLOAK");
|
||||
session.getSession().setAttribute(SerializableKeycloakAccount.class.getName(), sAccount);
|
||||
session.getSession().setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
|
||||
String username = securityContext.getToken().getSubject();
|
||||
log.fine("userSessionManagement.login: " + username);
|
||||
this.sessionManagement.login(session);
|
||||
|
|
|
@ -92,7 +92,8 @@ public class ServletSessionTokenStore implements AdapterTokenStore {
|
|||
} else {
|
||||
log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
|
||||
try {
|
||||
session.setAttribute(KeycloakUndertowAccount.class.getName(), null);
|
||||
session.removeAttribute(KeycloakUndertowAccount.class.getName());
|
||||
session.removeAttribute(KeycloakSecurityContext.class.getName());
|
||||
session.invalidate();
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to invalidate session, might already be invalidated");
|
||||
|
@ -106,6 +107,7 @@ public class ServletSessionTokenStore implements AdapterTokenStore {
|
|||
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
|
||||
HttpSession session = getSession(true);
|
||||
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
|
||||
session.setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
|
||||
sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import io.undertow.server.HttpServerExchange;
|
|||
import io.undertow.server.session.Session;
|
||||
import io.undertow.util.Sessions;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
|
@ -101,6 +102,7 @@ public class UndertowSessionTokenStore implements AdapterTokenStore {
|
|||
public void saveAccountInfo(OidcKeycloakAccount account) {
|
||||
Session session = Sessions.getOrCreateSession(exchange);
|
||||
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
|
||||
session.setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
|
||||
sessionManagement.login(session.getSessionManager());
|
||||
}
|
||||
|
||||
|
@ -111,6 +113,7 @@ public class UndertowSessionTokenStore implements AdapterTokenStore {
|
|||
KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
|
||||
if (account == null) return;
|
||||
session.removeAttribute(KeycloakUndertowAccount.class.getName());
|
||||
session.removeAttribute(KeycloakSecurityContext.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
package org.keycloak.adapters.saml.jetty;
|
||||
|
||||
import org.eclipse.jetty.security.DefaultUserIdentity;
|
||||
import org.eclipse.jetty.security.IdentityService;
|
||||
import org.eclipse.jetty.security.LoginService;
|
||||
import org.eclipse.jetty.security.ServerAuthException;
|
||||
import org.eclipse.jetty.security.UserAuthentication;
|
||||
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
|
||||
|
@ -135,12 +137,46 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
|
|||
|
||||
}
|
||||
|
||||
private class DummyLoginService implements LoginService {
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserIdentity login(String username, Object credentials) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserIdentity user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityService getIdentityService() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentityService(IdentityService service) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(UserIdentity user) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setConfiguration(AuthConfiguration configuration) {
|
||||
//super.setConfiguration(configuration);
|
||||
initializeKeycloak();
|
||||
// need this so that getUserPrincipal does not throw NPE
|
||||
_loginService = new DummyLoginService();
|
||||
String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
|
||||
setErrorPage(error);
|
||||
}
|
||||
|
|
|
@ -167,9 +167,9 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
|
|||
@Override
|
||||
public void invoke(Request request, Response response) throws IOException, ServletException {
|
||||
log.fine("*********************** SAML ************");
|
||||
CatalinaHttpFacade facade = new CatalinaHttpFacade(response, request);
|
||||
SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||
if (request.getRequestURI().substring(request.getContextPath().length()).endsWith("/saml")) {
|
||||
CatalinaHttpFacade facade = new CatalinaHttpFacade(response, request);
|
||||
SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||
if (deployment != null && deployment.isConfigured()) {
|
||||
SamlSessionStore tokenStore = getSessionStore(request, facade, deployment);
|
||||
SamlAuthenticator authenticator = new CatalinaSamlEndpoint(facade, deployment, tokenStore);
|
||||
|
@ -180,6 +180,7 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
|
|||
}
|
||||
|
||||
try {
|
||||
getSessionStore(request, facade, deployment).isLoggedIn(); // sets request UserPrincipal if logged in. we do this so that the UserPrincipal is available on unsecured, unconstrainted URLs
|
||||
super.invoke(request, response);
|
||||
} finally {
|
||||
}
|
||||
|
|
|
@ -28,6 +28,9 @@ import java.io.ObjectOutputStream;
|
|||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Available in secured requests under HttpServlerRequest.getAttribute()
|
||||
* Also available in HttpSession.getAttribute under the classname of this class
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
|
|
12
docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml
Executable file
12
docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml
Executable file
|
@ -0,0 +1,12 @@
|
|||
<chapter>
|
||||
<title>KeycloakSecurityContext</title>
|
||||
<para>
|
||||
The <literal>KeycloakSecurityContext</literal> interface is available if you need to look at the access token directly. This context is also useful if you need to
|
||||
get the encoded access token so you can make additional REST invocations. In servlet environments it is available in secured invocations as an attribute in HttpServletRequest.
|
||||
Or, it is available in secure and insecure requests in the HttpSession for browser apps.
|
||||
<programlisting>
|
||||
httpServletRequest.getAttribute(KeycloakSecurityContext.class.getName());
|
||||
httpServletRequest.getSession().getAttribute(KeycloakSecurityContext.class.getName());
|
||||
</programlisting>
|
||||
</para>
|
||||
</chapter>
|
|
@ -138,6 +138,12 @@ public class AdapterTestStrategy extends ExternalResource {
|
|||
String pageSource = driver.getPageSource();
|
||||
System.out.println(pageSource);
|
||||
Assert.assertTrue(pageSource.contains("parameter=hello"));
|
||||
// test that user principal and KeycloakSecurityContext available
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal/insecure");
|
||||
System.out.println("insecure: ");
|
||||
System.out.println(driver.getPageSource());
|
||||
Assert.assertTrue(driver.getPageSource().contains("Insecure Page"));
|
||||
if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertTrue(driver.getPageSource().contains("UserPrincipal"));
|
||||
|
||||
// test logout
|
||||
|
||||
|
|
|
@ -100,6 +100,7 @@ public class FilterAdapterTest {
|
|||
|
||||
@Test
|
||||
public void testSavedPostRequest() throws Exception {
|
||||
System.setProperty("insecure.user.principal.unsupported", "true");
|
||||
testStrategy.testSavedPostRequest();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
package org.keycloak.testsuite.adapter;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -35,6 +38,17 @@ public class InputServlet extends HttpServlet {
|
|||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String appBase = System.getProperty("app.server.base.url", "http://localhost:8081");
|
||||
if (req.getRequestURI().endsWith("insecure")) {
|
||||
if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertNotNull(req.getUserPrincipal());
|
||||
if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertNotNull(req.getAttribute(KeycloakSecurityContext.class.getName()));
|
||||
resp.setContentType("text/html");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
pw.printf("<html><head><title>%s</title></head><body>", "Insecure Page");
|
||||
if (req.getUserPrincipal() != null) pw.printf("UserPrincipal: " + req.getUserPrincipal().getName());
|
||||
pw.print("</body></html>");
|
||||
pw.flush();
|
||||
return;
|
||||
}
|
||||
String actionUrl = appBase + "/input-portal/secured/post";
|
||||
|
||||
|
||||
|
|
|
@ -48,8 +48,8 @@ public class ConcurrencyTest extends AbstractClientTest {
|
|||
|
||||
private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
|
||||
|
||||
private static final int DEFAULT_THREADS = 10;
|
||||
private static final int DEFAULT_ITERATIONS = 100;
|
||||
private static final int DEFAULT_THREADS = 5;
|
||||
private static final int DEFAULT_ITERATIONS = 20;
|
||||
|
||||
// If enabled only one request is allowed at the time. Useful for checking that test is working.
|
||||
private static final boolean SYNCHRONIZED = false;
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
package org.keycloak.testsuite.keycloaksaml;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -34,6 +37,16 @@ public class InputServlet extends HttpServlet {
|
|||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String appBase = System.getProperty("app.server.base.url", "http://localhost:8081");
|
||||
if (req.getRequestURI().endsWith("insecure")) {
|
||||
if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertNotNull(req.getUserPrincipal());
|
||||
resp.setContentType("text/html");
|
||||
PrintWriter pw = resp.getWriter();
|
||||
pw.printf("<html><head><title>%s</title></head><body>", "Insecure Page");
|
||||
if (req.getUserPrincipal() != null) pw.printf("UserPrincipal: " + req.getUserPrincipal().getName());
|
||||
pw.print("</body></html>");
|
||||
pw.flush();
|
||||
return;
|
||||
}
|
||||
String actionUrl = appBase + "/input-portal/secured/post";
|
||||
|
||||
|
||||
|
|
|
@ -152,6 +152,12 @@ public class SamlAdapterTestStrategy extends ExternalResource {
|
|||
String pageSource = driver.getPageSource();
|
||||
System.out.println(pageSource);
|
||||
Assert.assertTrue(pageSource.contains("parameter=hello"));
|
||||
// test that user principal and KeycloakSecurityContext available
|
||||
driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal/insecure");
|
||||
System.out.println("insecure: ");
|
||||
System.out.println(driver.getPageSource());
|
||||
Assert.assertTrue(driver.getPageSource().contains("Insecure Page"));
|
||||
if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertTrue(driver.getPageSource().contains("UserPrincipal"));
|
||||
|
||||
// test logout
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
"connectionsInfinispan": {
|
||||
"default": {
|
||||
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
|
||||
"async": "${keycloak.connectionsInfinispan.async:true}",
|
||||
"async": "${keycloak.connectionsInfinispan.async:false}",
|
||||
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue