Issue #152 Jetty container-based authn.

This commit is contained in:
Marvin S. Addison 2016-02-11 14:53:14 -05:00
parent e4b767700a
commit 53dbb48882
11 changed files with 765 additions and 0 deletions

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,106 @@
<?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>
<jetty.version>9.2.14.v20151106</jetty.version>
</properties>
<!--<build>-->
<!--<plugins>-->
<!--<plugin>-->
<!--<groupId>org.eclipse.jetty</groupId>-->
<!--<artifactId>jetty-maven-plugin</artifactId>-->
<!--<version>${jetty.version}</version>-->
<!--<configuration>-->
<!--<useTestScope>true</useTestScope>-->
<!--<webApp>-->
<!--<contextPath>/webapp</contextPath>-->
<!--</webApp>-->
<!--<webAppSourceDirectory>src/test/webapp</webAppSourceDirectory>-->
<!--<contextXml>${basedir}/src/test/resources/jetty/webapp.xml</contextXml>-->
<!--</configuration>-->
<!--<executions>-->
<!--<execution>-->
<!--<id>start-jetty</id>-->
<!--<phase>test-compile</phase>-->
<!--<goals>-->
<!--<goal>run</goal>-->
<!--</goals>-->
<!--</execution>-->
<!--<execution>-->
<!--<id>stop-jetty</id>-->
<!--<phase>prepare-package</phase>-->
<!--<goals>-->
<!--<goal>stop</goal>-->
<!--</goals>-->
<!--</execution>-->
<!--</executions>-->
<!--</plugin>-->
<!--</plugins>-->
<!--</build>-->
<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>

View File

@ -0,0 +1,45 @@
package org.jasig.cas.client.jetty;
import org.eclipse.jetty.security.UserAuthentication;
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()));
assert ticket != null : "Ticket cannot be null";
assert authenticator != null : "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);
}
}

View File

@ -0,0 +1,249 @@
/*
* 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.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, HttpSession> sessionMap = new ConcurrentHashMap<String, HttpSession>();
/** CAS ticket validator component. */
private TicketValidator ticketValidator;
/** Space-delimited ist 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) {
assert ticketValidator != null : "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 (!mandatory) {
if (authentication != null) {
return authentication;
}
return Authentication.UNAUTHENTICATED;
}
String ticket;
for (final Protocol protocol : Protocol.values()) {
ticket = request.getParameter(protocol.getArtifactParameterName());
if (ticket != null) {
try {
logger.debug("Attempting to validate {}", ticket);
final Assertion assertion = ticketValidator.validate(ticket, serviceUrl(request, response));
logger.debug("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;
}
redirectToCas(request, response);
return Authentication.SEND_CONTINUE;
}
@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) {
sessionMap.remove(ticket);
}
private void cacheAuthentication(final HttpServletRequest request, final CasAuthentication authentication) {
final HttpSession session = request.getSession(false);
if (session != null) {
session.setAttribute(CACHED_AUTHN_ATTRIBUTE, authentication);
sessionMap.put(authentication.getTicket(), 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);
}
}
}

View File

@ -0,0 +1,66 @@
package org.jasig.cas.client.jetty;
import org.eclipse.jetty.server.UserIdentity;
import org.jasig.cas.client.authentication.AttributePrincipal;
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) {
assert assertion != null : "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();
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,14 @@
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Get name="securityHandler">
<Set name="authenticator">
<New class="org.jasig.cas.client.jetty.CasAuthenticator">
<Set name="serverNames">localhost:8080</Set>
<Set name="ticketValidator">
<New class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
<Arg>http://localhost:8081/cas</Arg>
</New>
</Set>
</New>
</Set>
</Get>
</Configure>

View File

@ -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>

View File

@ -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>

View File

@ -252,6 +252,7 @@
<module>cas-client-integration-tomcat-common</module>
<module>cas-client-integration-tomcat-v6</module>
<module>cas-client-integration-tomcat-v7</module>
<module>cas-client-integration-jetty</module>
</modules>
<properties>