Merge pull request #153 from serac/jetty
CAS Jetty Container Authentication Support
This commit is contained in:
commit
b20d4d241c
|
|
@ -22,6 +22,7 @@ import java.beans.BeanInfo;
|
|||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
/**
|
||||
|
|
@ -148,4 +149,35 @@ public final class ReflectUtils {
|
|||
throw new RuntimeException("Error setting property " + propertyName, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the given declared field on the target object or any of its superclasses.
|
||||
*
|
||||
* @param fieldName Name of field to get.
|
||||
* @param target Target object that possesses field.
|
||||
*
|
||||
* @return Field value.
|
||||
*/
|
||||
public static Object getField(final String fieldName, final Object target) {
|
||||
Class<?> clazz = target.getClass();
|
||||
Field field = null;
|
||||
do {
|
||||
try {
|
||||
field = clazz.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
} while (field == null && clazz != null);
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException(fieldName + " does not exist on " + target);
|
||||
}
|
||||
try {
|
||||
if (!field.isAccessible()) {
|
||||
field.setAccessible(true);
|
||||
}
|
||||
return field.get(target);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Error getting field " + fieldName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,18 @@ public class ReflectUtilsTests extends TestCase {
|
|||
assertTrue(bean.isFlag());
|
||||
}
|
||||
|
||||
public void testGetField() {
|
||||
final TestBean bean = new TestBean();
|
||||
bean.setName("bob");
|
||||
assertEquals(bean.getName(), ReflectUtils.getField("name", bean));
|
||||
}
|
||||
|
||||
public void testGetFieldSuperclass() {
|
||||
final TestSubBean bean = new TestSubBean();
|
||||
bean.setName("bob");
|
||||
assertEquals(bean.getName(), ReflectUtils.getField("name", bean));
|
||||
}
|
||||
|
||||
static class TestBean {
|
||||
private int count;
|
||||
private boolean flag;
|
||||
|
|
@ -102,4 +114,16 @@ public class ReflectUtilsTests extends TestCase {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestSubBean extends TestBean {
|
||||
private String state;
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>cas-client</artifactId>
|
||||
<groupId>org.jasig.cas.client</groupId>
|
||||
<version>3.4.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.jasig.cas.client</groupId>
|
||||
<artifactId>cas-client-integration-jetty</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Jasig CAS Client for Java - Jetty Container Integration</name>
|
||||
|
||||
<properties>
|
||||
<!-- Note Jetty 9.2.x is the last version to support Java SE 7 -->
|
||||
<jetty.version>9.2.14.v20151106</jetty.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jasig.cas.client</groupId>
|
||||
<artifactId>cas-client-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.jasig.cas.client</groupId>
|
||||
<artifactId>cas-client-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-plus</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-annotations</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>apache-jsp</artifactId>
|
||||
<version>${jetty.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package org.jasig.cas.client.jetty;
|
||||
|
||||
import org.eclipse.jetty.security.UserAuthentication;
|
||||
import org.jasig.cas.client.util.CommonUtils;
|
||||
import org.jasig.cas.client.validation.Assertion;
|
||||
|
||||
/**
|
||||
* CAS-specific user authentication.
|
||||
*
|
||||
* @author Marvin S. Addison
|
||||
*/
|
||||
public class CasAuthentication extends UserAuthentication {
|
||||
|
||||
/** CAS authenticator that produced this authentication. */
|
||||
private final CasAuthenticator authenticator;
|
||||
|
||||
/** CAS ticket that was successfully validated to permit authentication. */
|
||||
private final String ticket;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param authenticator The authenticator that produced this authentication.
|
||||
* @param ticket The CAS ticket that was successfully validated to permit authentication.
|
||||
* @param assertion The CAS assertion produced from successful ticket validation.
|
||||
*/
|
||||
public CasAuthentication(final CasAuthenticator authenticator, final String ticket, final Assertion assertion) {
|
||||
super(authenticator.getAuthMethod(), new CasUserIdentity(assertion, authenticator.getRoleAttribute()));
|
||||
CommonUtils.assertNotNull(ticket, "Ticket cannot be null");
|
||||
CommonUtils.assertNotNull(authenticator, "CasAuthenticator cannot be null");
|
||||
this.authenticator = authenticator;
|
||||
this.ticket = ticket;
|
||||
}
|
||||
|
||||
/** @return The CAS ticket that was successfully validated to permit authentication. */
|
||||
public String getTicket() {
|
||||
return ticket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout() {
|
||||
super.logout();
|
||||
this.authenticator.clearCachedAuthentication(ticket);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* Licensed to Jasig under one or more contributor license
|
||||
* agreements. See the NOTICE file distributed with this work
|
||||
* for additional information regarding copyright ownership.
|
||||
* Jasig licenses this file to you under the Apache License,
|
||||
* Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a
|
||||
* copy of the License at the following location:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jasig.cas.client.jetty;
|
||||
|
||||
import org.eclipse.jetty.security.Authenticator;
|
||||
import org.eclipse.jetty.security.ServerAuthException;
|
||||
import org.eclipse.jetty.server.Authentication;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.jasig.cas.client.Protocol;
|
||||
import org.jasig.cas.client.util.CommonUtils;
|
||||
import org.jasig.cas.client.util.ReflectUtils;
|
||||
import org.jasig.cas.client.validation.AbstractCasProtocolUrlBasedTicketValidator;
|
||||
import org.jasig.cas.client.validation.AbstractUrlBasedTicketValidator;
|
||||
import org.jasig.cas.client.validation.Assertion;
|
||||
import org.jasig.cas.client.validation.TicketValidator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* Jetty authenticator component for container-managed CAS authentication.
|
||||
* <p><em>NOTE:</em> This component does not support CAS gateway mode.</p>
|
||||
*
|
||||
* @author Marvin S. Addison
|
||||
* @since 3.4.2
|
||||
*/
|
||||
public class CasAuthenticator extends AbstractLifeCycle implements Authenticator {
|
||||
|
||||
/** Name of authentication method provided by this authenticator. */
|
||||
public static final String AUTH_METHOD = "CAS";
|
||||
|
||||
/** Session attribute used to cache CAS authentication data. */
|
||||
private static final String CACHED_AUTHN_ATTRIBUTE = "org.jasig.cas.client.jetty.Authentication";
|
||||
|
||||
/** Logger instance. */
|
||||
private final Logger logger = LoggerFactory.getLogger(CasAuthenticator.class);
|
||||
|
||||
/** Map of tickets to sessions. */
|
||||
private final ConcurrentMap<String, WeakReference<HttpSession>> sessionMap =
|
||||
new ConcurrentHashMap<String, WeakReference<HttpSession>>();
|
||||
|
||||
/** CAS ticket validator component. */
|
||||
private TicketValidator ticketValidator;
|
||||
|
||||
/** Space-delimited list of server names. */
|
||||
private String serverNames;
|
||||
|
||||
/** CAS principal attribute containing role data. */
|
||||
private String roleAttribute;
|
||||
|
||||
/** URL to /login URI on CAS server. */
|
||||
private String casServerLoginUrl;
|
||||
|
||||
/** Protocol used by ticket validator. */
|
||||
private Protocol protocol;
|
||||
|
||||
/** CAS renew parameter. */
|
||||
private boolean renew;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the CAS ticket validator component.
|
||||
*
|
||||
* @param ticketValidator Ticket validator, MUST NOT be null.
|
||||
*/
|
||||
public void setTicketValidator(final TicketValidator ticketValidator) {
|
||||
CommonUtils.assertNotNull(ticketValidator, "TicketValidator cannot be null");
|
||||
if (ticketValidator instanceof AbstractUrlBasedTicketValidator) {
|
||||
if (ticketValidator instanceof AbstractCasProtocolUrlBasedTicketValidator) {
|
||||
protocol = Protocol.CAS2;
|
||||
} else {
|
||||
protocol = Protocol.SAML11;
|
||||
}
|
||||
casServerLoginUrl = ReflectUtils.getField("casServerUrlPrefix", ticketValidator) + "/login";
|
||||
renew = (Boolean) ReflectUtils.getField("renew", ticketValidator);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported ticket validator " + ticketValidator);
|
||||
}
|
||||
this.ticketValidator = ticketValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the names of the server host running Jetty.
|
||||
*
|
||||
* @param nameList Space-delimited list of one or more server names, e.g. "www1.example.com www2.example.com".
|
||||
* MUST NOT be blank.
|
||||
*/
|
||||
public void setServerNames(final String nameList) {
|
||||
CommonUtils.isNotBlank(nameList);
|
||||
this.serverNames = nameList;
|
||||
}
|
||||
|
||||
/** @return The name of the CAS principal attribute that contains role data. */
|
||||
public String getRoleAttribute() {
|
||||
return roleAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the CAS principal attribute that contains role data.
|
||||
*
|
||||
* @param roleAttribute Role attribute name. MUST NOT be blank.
|
||||
*/
|
||||
public void setRoleAttribute(final String roleAttribute) {
|
||||
CommonUtils.isNotBlank(roleAttribute);
|
||||
this.roleAttribute = roleAttribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(final AuthConfiguration configuration) {
|
||||
// Nothing to do
|
||||
// All configuration must be via CAS-specific setter methods
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthMethod() {
|
||||
return AUTH_METHOD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareRequest(final ServletRequest request) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication validateRequest(
|
||||
final ServletRequest servletRequest, final ServletResponse servletResponse, final boolean mandatory)
|
||||
throws ServerAuthException {
|
||||
|
||||
final HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
final HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
|
||||
CasAuthentication authentication = fetchCachedAuthentication(request);
|
||||
if (authentication != null) {
|
||||
return authentication;
|
||||
}
|
||||
|
||||
final String ticket = request.getParameter(protocol.getArtifactParameterName());
|
||||
if (ticket != null && mandatory) {
|
||||
try {
|
||||
logger.debug("Attempting to validate {}", ticket);
|
||||
final Assertion assertion = ticketValidator.validate(ticket, serviceUrl(request, response));
|
||||
logger.info("Successfully authenticated {}", assertion.getPrincipal());
|
||||
authentication = new CasAuthentication(this, ticket, assertion);
|
||||
cacheAuthentication(request, authentication);
|
||||
} catch (Exception e) {
|
||||
throw new ServerAuthException("CAS ticket validation failed", e);
|
||||
}
|
||||
}
|
||||
if (authentication != null) {
|
||||
return authentication;
|
||||
} else if (mandatory) {
|
||||
redirectToCas(request, response);
|
||||
return Authentication.SEND_CONTINUE;
|
||||
}
|
||||
return Authentication.UNAUTHENTICATED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean secureResponse(
|
||||
final ServletRequest request,
|
||||
final ServletResponse response,
|
||||
final boolean mandatory,
|
||||
final Authentication.User user) throws ServerAuthException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception {
|
||||
if (ticketValidator == null) {
|
||||
throw new RuntimeException("TicketValidator cannot be null");
|
||||
}
|
||||
if (serverNames == null) {
|
||||
throw new RuntimeException("ServerNames cannot be null");
|
||||
}
|
||||
}
|
||||
|
||||
protected void clearCachedAuthentication(final String ticket) {
|
||||
final WeakReference<HttpSession> sessionRef = sessionMap.remove(ticket);
|
||||
if (sessionRef != null && sessionRef.get() != null) {
|
||||
sessionRef.get().removeAttribute(CACHED_AUTHN_ATTRIBUTE);
|
||||
}
|
||||
}
|
||||
|
||||
private void cacheAuthentication(final HttpServletRequest request, final CasAuthentication authentication) {
|
||||
final HttpSession session = request.getSession(true);
|
||||
if (session != null) {
|
||||
session.setAttribute(CACHED_AUTHN_ATTRIBUTE, authentication);
|
||||
sessionMap.put(authentication.getTicket(), new WeakReference<HttpSession>(session));
|
||||
}
|
||||
}
|
||||
|
||||
private CasAuthentication fetchCachedAuthentication(final HttpServletRequest request) {
|
||||
final HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
return (CasAuthentication) session.getAttribute(CACHED_AUTHN_ATTRIBUTE);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String serviceUrl(final HttpServletRequest request, final HttpServletResponse response) {
|
||||
return CommonUtils.constructServiceUrl(
|
||||
request,
|
||||
response,
|
||||
null,
|
||||
serverNames,
|
||||
protocol.getServiceParameterName(),
|
||||
protocol.getArtifactParameterName(),
|
||||
true);
|
||||
}
|
||||
|
||||
private void redirectToCas(
|
||||
final HttpServletRequest request, final HttpServletResponse response) throws ServerAuthException {
|
||||
try {
|
||||
final String redirectUrl = CommonUtils.constructRedirectUrl(
|
||||
casServerLoginUrl, protocol.getServiceParameterName(), serviceUrl(request, response), renew, false);
|
||||
logger.debug("Redirecting to {}", redirectUrl);
|
||||
response.sendRedirect(redirectUrl);
|
||||
} catch (IOException e) {
|
||||
logger.debug("Redirect to CAS failed with error: {}", e);
|
||||
throw new ServerAuthException("Redirect to CAS failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package org.jasig.cas.client.jetty;
|
||||
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.jasig.cas.client.authentication.AttributePrincipal;
|
||||
import org.jasig.cas.client.util.CommonUtils;
|
||||
import org.jasig.cas.client.validation.Assertion;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import java.security.Principal;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* CAS user identity backed by assertion data.
|
||||
*
|
||||
* @author Marvin S. Addison
|
||||
*/
|
||||
public class CasUserIdentity implements UserIdentity {
|
||||
|
||||
/** CAS principal. */
|
||||
private AttributePrincipal principal;
|
||||
|
||||
/** Assertion attribute containing role data. */
|
||||
private String roleAttribute;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance from a CAS assertion containing principal information.
|
||||
*
|
||||
* @param assertion CAS assertion resulting from successful ticket validation.
|
||||
* @param roleAttribute Principal attribute containing role data.
|
||||
*/
|
||||
public CasUserIdentity(final Assertion assertion, final String roleAttribute) {
|
||||
CommonUtils.assertNotNull(assertion, "Assertion cannot be null");
|
||||
this.principal = assertion.getPrincipal();
|
||||
this.roleAttribute = roleAttribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subject getSubject() {
|
||||
final Subject subject = new Subject();
|
||||
subject.getPrincipals().add(principal);
|
||||
return subject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserInRole(final String role, final Scope scope) {
|
||||
if (roleAttribute != null) {
|
||||
final Object value = principal.getAttributes().get(roleAttribute);
|
||||
if (value instanceof Collection) {
|
||||
return ((Collection) value).contains(role);
|
||||
} else if (value instanceof String) {
|
||||
return value.equals(role);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return principal.getName();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
package org.jasig.cas.client.jetty;
|
||||
|
||||
import org.apache.tomcat.InstanceManager;
|
||||
import org.apache.tomcat.SimpleInstanceManager;
|
||||
import org.eclipse.jetty.annotations.ServletContainerInitializersStarter;
|
||||
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
|
||||
import org.eclipse.jetty.jsp.JettyJspServlet;
|
||||
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
|
||||
import org.eclipse.jetty.security.ConstraintMapping;
|
||||
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
import org.jasig.cas.client.PublicTestHttpServer;
|
||||
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Unit test for {@link CasAuthenticator}.
|
||||
*
|
||||
* @author Marvin S. Addison
|
||||
*/
|
||||
public class CasAuthenticatorTest {
|
||||
|
||||
private static final Server server = new Server(8080);
|
||||
private static final CasAuthenticator authenticator = new CasAuthenticator();
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
final WebAppContext context = new WebAppContext();
|
||||
context.setContextPath("/webapp");
|
||||
String workingDir = new File(".").getAbsolutePath();
|
||||
workingDir = workingDir.substring(0, workingDir.length() - 2);
|
||||
final String webappDir;
|
||||
if (workingDir.endsWith("/cas-client-integration-jetty")) {
|
||||
webappDir = workingDir + "/src/test/webapp";
|
||||
} else {
|
||||
webappDir = workingDir + "/cas-client-integration-jetty/src/test/webapp";
|
||||
}
|
||||
context.setWar(webappDir);
|
||||
|
||||
|
||||
// JSP config from https://github.com/jetty-project/embedded-jetty-jsp/
|
||||
System.setProperty("org.apache.jasper.compiler.disablejsr199", "false");
|
||||
context.setAttribute("javax.servlet.context.tempdir", getScratchDir());
|
||||
context.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
|
||||
".*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/.*taglibs.*\\.jar$");
|
||||
context.setAttribute("org.eclipse.jetty.containerInitializers", jspInitializers());
|
||||
context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
|
||||
context.addBean(new ServletContainerInitializersStarter(context), true);
|
||||
context.addServlet(jspServletHolder(), "*.jsp");
|
||||
|
||||
// Wire up CAS authentication
|
||||
authenticator.setServerNames("localhost:8080");
|
||||
authenticator.setTicketValidator(new Cas20ServiceTicketValidator("http://localhost:8081/cas"));
|
||||
|
||||
// Configure security handling for webapp context
|
||||
final ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
|
||||
final Constraint constraint = new Constraint("CasRealm", Constraint.ANY_AUTH);
|
||||
constraint.setAuthenticate(true);
|
||||
final ConstraintMapping secureMapping = new ConstraintMapping();
|
||||
secureMapping.setPathSpec("/secure.jsp");
|
||||
secureMapping.setConstraint(constraint);
|
||||
securityHandler.addConstraintMapping(secureMapping);
|
||||
securityHandler.setAuthenticator(authenticator);
|
||||
context.setSecurityHandler(securityHandler);
|
||||
|
||||
// Add webapp context and start the server
|
||||
server.setHandler(context);
|
||||
server.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRequestPublicPageNoTicket() throws Exception {
|
||||
final HttpURLConnection uc = openConnection("http://localhost:8080/webapp/");
|
||||
try {
|
||||
assertEquals(200, uc.getResponseCode());
|
||||
assertTrue(readOutput(uc).contains("Welcome everyone"));
|
||||
} finally {
|
||||
uc.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRequestPublicPageWithTicket() throws Exception {
|
||||
final HttpURLConnection uc = openConnection("http://localhost:8080/webapp/?ticket=ST-12345");
|
||||
try {
|
||||
assertEquals(200, uc.getResponseCode());
|
||||
assertTrue(readOutput(uc).contains("Welcome everyone"));
|
||||
} finally {
|
||||
uc.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRequestSecurePageNoTicket() throws Exception {
|
||||
final HttpURLConnection uc = openConnection("http://localhost:8080/webapp/secure.jsp");
|
||||
try {
|
||||
assertEquals(302, uc.getResponseCode());
|
||||
assertEquals(
|
||||
"http://localhost:8081/cas/login?service=http%3A%2F%2Flocalhost%3A8080%2Fwebapp%2Fsecure.jsp",
|
||||
uc.getHeaderField("Location"));
|
||||
} finally {
|
||||
uc.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRequestSecurePageWithTicket() throws Exception {
|
||||
final String successResponse = "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>" +
|
||||
"<cas:authenticationSuccess>" +
|
||||
"<cas:user>bob</cas:user>" +
|
||||
"</cas:authenticationSuccess>" +
|
||||
"</cas:serviceResponse>";
|
||||
final PublicTestHttpServer server = PublicTestHttpServer.instance(8081);
|
||||
server.content = successResponse.getBytes(StandardCharsets.UTF_8);
|
||||
final HttpURLConnection uc = openConnection("http://localhost:8080/webapp/secure.jsp?ticket=ST-12345");
|
||||
try {
|
||||
assertEquals(200, uc.getResponseCode());
|
||||
assertTrue(readOutput(uc).contains("Hello bob"));
|
||||
} finally {
|
||||
uc.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
server.stop();
|
||||
}
|
||||
|
||||
private String readOutput(final URLConnection connection) throws IOException {
|
||||
final InputStreamReader reader = new InputStreamReader(connection.getInputStream());
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final CharBuffer buffer = CharBuffer.allocate(1024);
|
||||
try {
|
||||
while (reader.read(buffer) > 0) {
|
||||
builder.append(buffer.flip());
|
||||
buffer.clear();
|
||||
}
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static File getScratchDir() throws IOException
|
||||
{
|
||||
final File tempDir = new File(System.getProperty("java.io.tmpdir"));
|
||||
final File scratchDir = new File(tempDir.toString(), "embedded-jetty-jsp");
|
||||
|
||||
if (!scratchDir.exists())
|
||||
{
|
||||
if (!scratchDir.mkdirs())
|
||||
{
|
||||
throw new IOException("Unable to create scratch directory: " + scratchDir);
|
||||
}
|
||||
}
|
||||
return scratchDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the jsp engine is initialized correctly
|
||||
*/
|
||||
private static List<ContainerInitializer> jspInitializers()
|
||||
{
|
||||
return Collections.singletonList(new ContainerInitializer(new JettyJasperInitializer(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create JSP Servlet (must be named "jsp")
|
||||
*/
|
||||
private static ServletHolder jspServletHolder()
|
||||
{
|
||||
final ServletHolder holderJsp = new ServletHolder("jsp", JettyJspServlet.class);
|
||||
holderJsp.setInitOrder(0);
|
||||
holderJsp.setInitParameter("logVerbosityLevel", "DEBUG");
|
||||
holderJsp.setInitParameter("fork", "false");
|
||||
holderJsp.setInitParameter("xpoweredBy", "false");
|
||||
holderJsp.setInitParameter("compilerTargetVM", "1.7");
|
||||
holderJsp.setInitParameter("compilerSourceVM", "1.7");
|
||||
holderJsp.setInitParameter("keepgenerated", "true");
|
||||
return holderJsp;
|
||||
}
|
||||
|
||||
private static HttpURLConnection openConnection(final String url) throws IOException {
|
||||
final HttpURLConnection uc;
|
||||
try {
|
||||
uc = (HttpURLConnection) new URL(url).openConnection();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Invalid URL: " + url, e);
|
||||
}
|
||||
uc.setInstanceFollowRedirects(false);
|
||||
uc.connect();
|
||||
return uc;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
|
||||
|
||||
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
|
||||
<Set name="contextPath">/</Set>
|
||||
<Set name="war"><SystemProperty name="jetty.base"/>/webapps/yourapp</Set>
|
||||
<Get name="securityHandler">
|
||||
<Set name="authenticator">
|
||||
<New class="org.jasig.cas.client.jetty.CasAuthenticator">
|
||||
<Set name="serverNames">app.example.com</Set>
|
||||
<Set name="ticketValidator">
|
||||
<New class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
|
||||
<Arg>https://cas.example.com/cas</Arg>
|
||||
<!--<Set name="renew">true</Set>-->
|
||||
</New>
|
||||
</Set>
|
||||
</New>
|
||||
</Set>
|
||||
</Get>
|
||||
</Configure>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
|
||||
|
||||
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
|
||||
<Set name="contextPath">/</Set>
|
||||
<Set name="war"><SystemProperty name="jetty.base"/>/webapps/yourapp</Set>
|
||||
<Get name="securityHandler">
|
||||
<Set name="authenticator">
|
||||
<New class="org.jasig.cas.client.jetty.CasAuthenticator">
|
||||
<Set name="serverNames">app.example.com</Set>
|
||||
<Set name="roleAttribute">memberOf</Set>
|
||||
<Set name="ticketValidator">
|
||||
<New class="org.jasig.cas.client.validation.Saml11TicketValidator">
|
||||
<Arg>https://cas.example.com/cas</Arg>
|
||||
<!--<Set name="renew">true</Set>-->
|
||||
</New>
|
||||
</Set>
|
||||
</New>
|
||||
</Set>
|
||||
</Get>
|
||||
</Configure>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Welcome Page</title>
|
||||
</head>
|
||||
<body><h1>Welcome everyone</h1></body>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Secure Page</title>
|
||||
</head>
|
||||
<body><h1>Hello <%=request.getUserPrincipal()%></h1></body>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue