Details
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java
index bdbddc6..9eb49db 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java
@@ -15,11 +15,13 @@ public class ConstraintMatcherHandler implements HttpHandler {
protected SecurityPathMatches matcher;
protected HttpHandler securedHandler;
protected HttpHandler unsecuredHandler;
+ protected String errorPage;
- public ConstraintMatcherHandler(SecurityPathMatches matcher, HttpHandler securedHandler, HttpHandler unsecuredHandler) {
+ public ConstraintMatcherHandler(SecurityPathMatches matcher, HttpHandler securedHandler, HttpHandler unsecuredHandler, String errorPage) {
this.matcher = matcher;
this.securedHandler = securedHandler;
this.unsecuredHandler = unsecuredHandler;
+ this.errorPage = errorPage;
}
@Override
@@ -31,8 +33,16 @@ public class ConstraintMatcherHandler implements HttpHandler {
}
if (match.getRequiredRoles().isEmpty() && match.getEmptyRoleSemantic() == SecurityInfo.EmptyRoleSemantic.DENY) {
- exchange.setResponseCode(403);
- exchange.endExchange();
+ if (errorPage != null) {
+ exchange.setRequestPath(errorPage);
+ exchange.setRelativePath(errorPage);
+ exchange.setResolvedPath(errorPage);
+ unsecuredHandler.handleRequest(exchange);
+ } else {
+ exchange.setResponseCode(403);
+ exchange.endExchange();
+ }
+ return;
}
exchange.getSecurityContext().setAuthenticationRequired();
exchange.putAttachment(CONSTRAINT_KEY, match);
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
index f236579..16b8778 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
@@ -4,7 +4,6 @@ import io.undertow.Undertow;
import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.AuthenticationMode;
import io.undertow.security.handlers.AuthenticationCallHandler;
-import io.undertow.security.handlers.AuthenticationConstraintHandler;
import io.undertow.security.handlers.AuthenticationMechanismsHandler;
import io.undertow.security.handlers.SecurityInitialHandler;
import io.undertow.security.idm.Account;
@@ -72,7 +71,7 @@ public class ProxyServerBuilder {
proxyHandler = new HttpHandler() {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
- exchange.setRelativePath(exchange.getResolvedPath()); // need this otherwise proxy forwards to chopped off path
+ exchange.setRelativePath(exchange.getRequestPath()); // need this otherwise proxy forwards to chopped off path
handler.handleRequest(exchange);
}
};
@@ -93,12 +92,21 @@ public class ProxyServerBuilder {
protected String base;
protected SecurityPathMatches.Builder constraintBuilder = new SecurityPathMatches.Builder();
protected SecurityPathMatches matches;
+ protected String errorPage;
public ApplicationBuilder base(String base) {
this.base = base;
return this;
}
+ public ApplicationBuilder errorPage(String errorPage) {
+ if (errorPage != null && errorPage.startsWith("/")) {
+ errorPage = errorPage.substring(1);
+ }
+ this.errorPage = errorPage;
+ return this;
+ }
+
public ApplicationBuilder(AdapterConfig config) {
this.deployment = KeycloakDeploymentBuilder.build(config);
this.deploymentContext = new AdapterDeploymentContext(deployment);
@@ -172,8 +180,16 @@ public class ProxyServerBuilder {
private HttpHandler addSecurity(final HttpHandler toWrap) {
HttpHandler handler = toWrap;
handler = new UndertowAuthenticatedActionsHandler(deploymentContext, toWrap);
+ if (errorPage != null) {
+ if (base.endsWith("/")) {
+ errorPage = base + errorPage;
+ } else {
+ errorPage = base + "/" + errorPage;
+ }
+ }
+ handler = new ConstraintAuthorizationHandler(errorPage, handler);
handler = new AuthenticationCallHandler(handler);
- handler = new ConstraintMatcherHandler(matches, handler, toWrap);
+ handler = new ConstraintMatcherHandler(matches, handler, toWrap, errorPage);
final List<AuthenticationMechanism> mechanisms = new LinkedList<AuthenticationMechanism>();
mechanisms.add(new CachedAuthenticatedSessionMechanism());
mechanisms.add(new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, -1));
diff --git a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java
index ecd0d59..511e02d 100755
--- a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java
+++ b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java
@@ -98,7 +98,7 @@ public class ProxyTest {
Integer count = (Integer)req.getSession().getAttribute("counter");
if (count == null) count = new Integer(0);
req.getSession().setAttribute("counter", new Integer(count.intValue() + 1));
- stream.write(count.toString().getBytes());
+ stream.write(("count:"+count).getBytes());
@@ -108,6 +108,18 @@ public class ProxyTest {
doGet(req, resp);
}
}
+ public static class SendError extends HttpServlet {
+ @Override
+ protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+ resp.setContentType("text/plain");
+ OutputStream stream = resp.getOutputStream();
+ stream.write("access error".getBytes());
+ }
+ @Override
+ protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+ doGet(req, resp);
+ }
+ }
static Tomcat tomcat = null;
@@ -133,7 +145,7 @@ public class ProxyTest {
static Undertow proxyServer = null;
- //@BeforeClass
+ @BeforeClass
public static void initProxy() throws Exception {
initTomcat();
ProxyServerBuilder builder = new ProxyServerBuilder().addHttpListener(8080, "localhost");
@@ -142,8 +154,13 @@ public class ProxyTest {
builder.target("http://localhost:8082")
.application(config)
- .base("/customer-portal")
- .constraint("/*").add().add();
+ .base("/customer-portal")
+ .errorPage("/error.html")
+ .constraint("/users/*").role("user").add()
+ .constraint("/admins/*").role("admin").add()
+ .constraint("/users/permit").permit().add()
+ .constraint("/users/deny").deny().add()
+ .add();
proxyServer = builder.build();
proxyServer.start();
@@ -167,34 +184,57 @@ public class ProxyTest {
@Test
public void testLoginSSOAndLogout() throws Exception {
- initProxy();
- driver.navigate().to("http://localhost:8080/customer-portal");
+ driver.navigate().to("http://localhost:8080/customer-portal/users");
System.out.println("Current url: " + driver.getCurrentUrl());
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
loginPage.login("bburke@redhat.com", "password");
System.out.println("Current url: " + driver.getCurrentUrl());
- Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal");
+ Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/users");
String pageSource = driver.getPageSource();
System.out.println(pageSource);
- Assert.assertTrue(pageSource.contains("customer-portal"));
- Assert.assertTrue(pageSource.contains("0"));
- driver.navigate().to("http://localhost:8080/customer-portal");
- Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal");
+ Assert.assertTrue(pageSource.contains("customer-portal/users"));
+ Assert.assertTrue(pageSource.contains("count:0"));
+ driver.navigate().to("http://localhost:8080/customer-portal/users");
+ Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/users");
pageSource = driver.getPageSource();
System.out.println(pageSource);
- Assert.assertTrue(pageSource.contains("customer-portal"));
- Assert.assertTrue(pageSource.contains("1")); // test http session
+ Assert.assertTrue(pageSource.contains("customer-portal/users"));
+ Assert.assertTrue(pageSource.contains("count:1")); // test http session
+
+ driver.navigate().to("http://localhost:8080/customer-portal/users/deny");
+ Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/users/deny");
+ pageSource = driver.getPageSource();
+ System.out.println(pageSource);
+ Assert.assertTrue(pageSource.contains("access error"));
+
+ driver.navigate().to("http://localhost:8080/customer-portal/admins");
+ Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/admins");
+ pageSource = driver.getPageSource();
+ System.out.println(pageSource);
+ Assert.assertTrue(pageSource.contains("access error"));
+
+
// test logout
String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
- .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8080/customer-portal").build("demo").toString();
+ .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8080/customer-portal/users").build("demo").toString();
driver.navigate().to(logoutUri);
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
- driver.navigate().to("http://localhost:8080/customer-portal");
+ driver.navigate().to("http://localhost:8080/customer-portal/users");
String currentUrl = driver.getCurrentUrl();
Assert.assertTrue(currentUrl.startsWith(LOGIN_URL));
+ // test unsecured page
+ driver.navigate().to("http://localhost:8080/customer-portal") ;
+ pageSource = driver.getPageSource();
+ System.out.println(pageSource);
+ Assert.assertTrue(pageSource.contains("customer-portal"));
+ driver.navigate().to("http://localhost:8080/customer-portal/users/permit") ;
+ pageSource = driver.getPageSource();
+ System.out.println(pageSource);
+ Assert.assertTrue(pageSource.contains("customer-portal/users/permit"));
+
}
diff --git a/testsuite/proxy/src/test/resources/tomcat-test/demorealm.json b/testsuite/proxy/src/test/resources/tomcat-test/demorealm.json
index a4a6ec9..29de45c 100755
--- a/testsuite/proxy/src/test/resources/tomcat-test/demorealm.json
+++ b/testsuite/proxy/src/test/resources/tomcat-test/demorealm.json
@@ -24,6 +24,21 @@
{ "type" : "password",
"value" : "password" }
],
+ "realmRoles": [ "user" ],
+ "applicationRoles": {
+ "account": [ "manage-account" ]
+ }
+ },
+ {
+ "username" : "admin",
+ "enabled": true,
+ "email" : "admin.burke@redhat.com",
+ "firstName": "Admin",
+ "lastName": "Burke",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
"realmRoles": [ "user", "admin" ],
"applicationRoles": {
"account": [ "manage-account" ]
diff --git a/testsuite/proxy/src/test/resources/tomcat-test/webapp/WEB-INF/web.xml b/testsuite/proxy/src/test/resources/tomcat-test/webapp/WEB-INF/web.xml
index 7e0b269..8e76e3e 100755
--- a/testsuite/proxy/src/test/resources/tomcat-test/webapp/WEB-INF/web.xml
+++ b/testsuite/proxy/src/test/resources/tomcat-test/webapp/WEB-INF/web.xml
@@ -10,8 +10,16 @@
<servlet-name>SendUsername</servlet-name>
<servlet-class>org.keycloak.testsuite.ProxyTest$SendUsernameServlet</servlet-class>
</servlet>
+ <servlet>
+ <servlet-name>Error</servlet-name>
+ <servlet-class>org.keycloak.testsuite.ProxyTest$SendError</servlet-class>
+ </servlet>
<servlet-mapping>
+ <servlet-name>Error</servlet-name>
+ <url-pattern>/error.html</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
<servlet-name>SendUsername</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>