rounded out quickstart sample .war and implemented initial form-based AuthenticationWebInterceptor
git-svn-id: https://svn.apache.org/repos/asf/incubator/jsecurity/trunk@710810 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/samples/quickstart/WEB-INF/classes/log4j.properties b/samples/quickstart/WEB-INF/classes/log4j.properties
index 65abb3a..1f064c9 100644
--- a/samples/quickstart/WEB-INF/classes/log4j.properties
+++ b/samples/quickstart/WEB-INF/classes/log4j.properties
@@ -1,5 +1,5 @@
# This file is used to format all logging output
-log4j.rootLogger=TRACE, stdout
+log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
@@ -22,7 +22,8 @@
# JSecurity
# =============================================================================
# JSecurity security framework
-log4j.logger.org.jsecurity=TRACE
+log4j.logger.org.jsecurity.realm.text.PropertiesRealm=INFO
log4j.logger.org.jsecurity.cache.ehcache.EhCache=INFO
-log4j.logger.org.jsecurity.web.servlet=DEBUG
+log4j.logger.org.jsecurity.web.servlet.WebInterceptorFilter=INFO
+log4j.logger.org.jsecurity.web.WebSecurityManager=INFO
log4j.logger.org.jsecurity.util.ThreadContext=INFO
\ No newline at end of file
diff --git a/samples/quickstart/WEB-INF/web.xml b/samples/quickstart/WEB-INF/web.xml
index de10557..4e48ee5 100644
--- a/samples/quickstart/WEB-INF/web.xml
+++ b/samples/quickstart/WEB-INF/web.xml
@@ -11,16 +11,16 @@
<init-param><param-name>config</param-name><param-value>
# The JSecurityFilter configuration is very powerful and flexible, while still remaining succinct.
- # Please read the comprehensive example, with full comments and explaination, in the JavaDoc:
+ # Please read the comprehensive example, with full comments and explanations, in the JavaDoc:
#
# http://www.jsecurity.org/api/org/jsecurity/web/servlet/JSecurityFilter.html
-
- [main]
- [interceptors]
+ [interceptors]
+ authc.successUrl = /index.jsp
+
[urls]
- /account/** = authcBasic
- /remoting/** = authcBasic, roles[b2bClient], perms[remote:invoke:"lan,wan"]
+ /account/** = authc
+ /remoting/** = authc, roles[b2bClient], perms[remote:invoke:"lan,wan"]
</param-value></init-param>
</filter>
diff --git a/samples/quickstart/account/index.jsp b/samples/quickstart/account/index.jsp
index 1c74127..3a8eedd 100644
--- a/samples/quickstart/account/index.jsp
+++ b/samples/quickstart/account/index.jsp
@@ -8,11 +8,11 @@
<h2>Users only</h2>
- <p>You have successfully logged in.</p>
+ <p>You are currently logged in.</p>
<p><a href="<c:url value="/home.jsp"/>">Return to the home page.</a></p>
- <p><a href="<c:url value="/logoutjsp"/>">Log out.</a></p>
+ <p><a href="<c:url value="/logout.jsp"/>">Log out.</a></p>
</body>
</html>
\ No newline at end of file
diff --git a/samples/quickstart/home.jsp b/samples/quickstart/home.jsp
index ddb3618..91cccab 100644
--- a/samples/quickstart/home.jsp
+++ b/samples/quickstart/home.jsp
@@ -10,12 +10,16 @@
<p>Hi <jsec:guest>Guest</jsec:guest><jsec:user><jsec:principal/></jsec:user>!
( <jsec:user><a href="<c:url value="/logout.jsp"/>">Log out</a></jsec:user>
- <jsec:guest><a href="<c:url value="/account/"/>">Log in</a></jsec:guest> )
+ <jsec:guest><a href="<c:url value="/login.jsp"/>">Log in</a> (sample accounts provided)</jsec:guest> )
</p>
<p>Welcome to the JSecurity Quickstart sample application.
This page represents the home page of any web application.</p>
+ <jsec:user><p>Visit your <a href="<c:url value="/account"/>">account page</a>.</p></jsec:user>
+ <jsec:guest><p>If you want to access the user-only <a href="<c:url value="/account"/>">account page</a>,
+ you will need to log-in first.</p></jsec:guest>
+
<h2>Roles</h2>
<p>To show some taglibs, here are the roles you have and don't have. Log out and log back in under different user
@@ -24,7 +28,6 @@
<h3>Roles you have</h3>
<p>
- <jsec:hasRole name="guest">guest<br/></jsec:hasRole>
<jsec:hasRole name="root">root<br/></jsec:hasRole>
<jsec:hasRole name="president">president<br/></jsec:hasRole>
<jsec:hasRole name="darklord">darklord<br/></jsec:hasRole>
@@ -35,7 +38,6 @@
<h3>Roles you DON'T have</h3>
<p>
- <jsec:lacksRole name="guest">guest<br/></jsec:lacksRole>
<jsec:lacksRole name="root">root<br/></jsec:lacksRole>
<jsec:lacksRole name="president">president<br/></jsec:lacksRole>
<jsec:lacksRole name="darklord">darklord<br/></jsec:lacksRole>
diff --git a/samples/quickstart/login.jsp b/samples/quickstart/login.jsp
new file mode 100644
index 0000000..0a0b824
--- /dev/null
+++ b/samples/quickstart/login.jsp
@@ -0,0 +1,90 @@
+<%@ include file="include.jsp" %>
+
+<html>
+<head>
+ <link type="text/css" rel="stylesheet" href="<c:url value="/style.css"/>"/>
+</head>
+<body>
+
+<h2>Please Log in</h2>
+
+<jsec:guest>
+ <p>Here are a few sample accounts to play with in the default text-based Realm (used for this
+ demo and test installs only). Do you remember the movie these names came from? ;)</p>
+
+
+ <style type="text/css">
+ table.sample {
+ border-width: 1px;
+ border-style: outset;
+ border-color: blue;
+ border-collapse: separate;
+ background-color: rgb(255, 255, 240);
+ }
+ table.sample th {
+ border-width: 1px;
+ padding: 1px;
+ border-style: none;
+ border-color: blue;
+ background-color: rgb(255, 255, 240);
+ }
+ table.sample td {
+ border-width: 1px;
+ padding: 1px;
+ border-style: none;
+ border-color: blue;
+ background-color: rgb(255, 255, 240);
+ }
+ </style>
+
+
+ <table class="sample">
+ <thead>
+ <tr>
+ <th>Username</th>
+ <th>Password</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>root</td>
+ <td>secret</td>
+ </tr>
+ <tr>
+ <td>presidentskroob</td>
+ <td>12345</td>
+ </tr>
+ <tr>
+ <td>darkhelmet</td>
+ <td>ludicrousspeed</td>
+ </tr>
+ <tr>
+ <td>lonestarr</td>
+ <td>vespa</td>
+ </tr>
+ </tbody>
+ </table>
+ <br/><br/>
+</jsec:guest>
+
+<form action="" method="post">
+ <table align="left" border="0" cellspacing="0" cellpadding="3">
+ <tr>
+ <td>Username:</td>
+ <td><input type="text" name="username" maxlength="30"></td>
+ </tr>
+ <tr>
+ <td>Password:</td>
+ <td><input type="password" name="password" maxlength="30"></td>
+ </tr>
+ <tr>
+ <td colspan="2" align="left"><input type="checkbox" name="rememberMe"><font size="2">Remember Me</font></td>
+ </tr>
+ <tr>
+ <td colspan="2" align="right"><input type="submit" name="submit" value="Login"></td>
+ </tr>
+ </table>
+</form>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/org/jsecurity/realm/AuthorizingRealm.java b/src/org/jsecurity/realm/AuthorizingRealm.java
index f13a3e4..0b7b738 100644
--- a/src/org/jsecurity/realm/AuthorizingRealm.java
+++ b/src/org/jsecurity/realm/AuthorizingRealm.java
@@ -213,8 +213,8 @@
Account account = null;
- if (log.isDebugEnabled()) {
- log.debug("Retrieving Account for principal [" + principal + "]");
+ if (log.isTraceEnabled()) {
+ log.trace("Retrieving Account for principal [" + principal + "]");
}
Cache accountCache = getAccountCache();
diff --git a/src/org/jsecurity/web/interceptor/DefaultInterceptorBuilder.java b/src/org/jsecurity/web/interceptor/DefaultInterceptorBuilder.java
index 7958630..d9c7c90 100644
--- a/src/org/jsecurity/web/interceptor/DefaultInterceptorBuilder.java
+++ b/src/org/jsecurity/web/interceptor/DefaultInterceptorBuilder.java
@@ -20,6 +20,7 @@
import org.apache.commons.logging.LogFactory;
import org.jsecurity.util.ClassUtils;
import org.jsecurity.web.interceptor.authc.BasicHttpAuthenticationWebInterceptor;
+import org.jsecurity.web.interceptor.authc.FormAuthenticationWebInterceptor;
import org.jsecurity.web.interceptor.authz.PermissionsAuthorizationWebInterceptor;
import org.jsecurity.web.interceptor.authz.RolesAuthorizationWebInterceptor;
@@ -37,6 +38,7 @@
public Map<String, Object> buildDefaultInterceptors() {
Map<String, Object> interceptors = new LinkedHashMap<String, Object>();
+ interceptors.put("authc", new FormAuthenticationWebInterceptor());
interceptors.put("authcBasic", new BasicHttpAuthenticationWebInterceptor());
interceptors.put("roles", new RolesAuthorizationWebInterceptor());
interceptors.put("perms", new PermissionsAuthorizationWebInterceptor());
diff --git a/src/org/jsecurity/web/interceptor/authc/AuthenticationWebInterceptor.java b/src/org/jsecurity/web/interceptor/authc/AuthenticationWebInterceptor.java
index 177065c..9cbcc24 100644
--- a/src/org/jsecurity/web/interceptor/authc/AuthenticationWebInterceptor.java
+++ b/src/org/jsecurity/web/interceptor/authc/AuthenticationWebInterceptor.java
@@ -61,5 +61,5 @@
* @return true if the request should continue to be processed; false if the subclass will handle/render
* the response directly.
*/
- protected abstract boolean onUnauthenticatedRequest(ServletRequest request, ServletResponse response);
+ protected abstract boolean onUnauthenticatedRequest(ServletRequest request, ServletResponse response) throws Exception;
}
diff --git a/src/org/jsecurity/web/interceptor/authc/FormAuthenticationWebInterceptor.java b/src/org/jsecurity/web/interceptor/authc/FormAuthenticationWebInterceptor.java
new file mode 100644
index 0000000..44d2fee
--- /dev/null
+++ b/src/org/jsecurity/web/interceptor/authc/FormAuthenticationWebInterceptor.java
@@ -0,0 +1,174 @@
+package org.jsecurity.web.interceptor.authc;
+
+import org.jsecurity.JSecurityException;
+import org.jsecurity.authc.AuthenticationException;
+import org.jsecurity.authc.UsernamePasswordToken;
+import org.jsecurity.util.StringUtils;
+import org.jsecurity.web.RedirectView;
+import org.jsecurity.web.WebUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * @author Les Hazlewood
+ * @since 0.9
+ */
+public class FormAuthenticationWebInterceptor extends AuthenticationWebInterceptor {
+
+ public static final String DEFAULT_ERROR_KEY_ATTRIBUTE_NAME = FormAuthenticationWebInterceptor.class.getName() + "_AUTHC_FAILURE_KEY";
+
+ public static final String DEFAULT_LOGIN_URL = "/login.jsp";
+ public static final String DEFAULT_USERNAME_PARAM = "username";
+ public static final String DEFAULT_PASSWORD_PARAM = "password";
+ public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";
+
+ private String usernameParam = DEFAULT_USERNAME_PARAM;
+ private String passwordParam = DEFAULT_PASSWORD_PARAM;
+ private String rememberMeParam = DEFAULT_REMEMBER_ME_PARAM;
+
+ private String successUrl = DEFAULT_LOGIN_URL;
+ private String failureKeyAtribute = DEFAULT_ERROR_KEY_ATTRIBUTE_NAME;
+
+ public FormAuthenticationWebInterceptor() {
+ setUrl(DEFAULT_LOGIN_URL);
+ }
+
+ public String getUsernameParam() {
+ return usernameParam;
+ }
+
+ public void setUsernameParam(String usernameParam) {
+ this.usernameParam = usernameParam;
+ }
+
+ public String getPasswordParam() {
+ return passwordParam;
+ }
+
+ public void setPasswordParam(String passwordParam) {
+ this.passwordParam = passwordParam;
+ }
+
+ public String getRememberMeParam() {
+ return rememberMeParam;
+ }
+
+ public void setRememberMeParam(String rememberMeParam) {
+ this.rememberMeParam = rememberMeParam;
+ }
+
+ public String getSuccessUrl() {
+ return successUrl;
+ }
+
+ public void setSuccessUrl(String successUrl) {
+ this.successUrl = successUrl;
+ }
+
+ public String getFailureKeyAtribute() {
+ return failureKeyAtribute;
+ }
+
+ public void setFailureKeyAtribute(String failureKeyAtribute) {
+ this.failureKeyAtribute = failureKeyAtribute;
+ }
+
+ public void init() throws JSecurityException {
+ if ( log.isTraceEnabled() ) {
+ log.trace("Adding default login url to applied paths." );
+ }
+ this.appliedPaths.put(getUrl(),null);
+ }
+
+ protected boolean onUnauthenticatedRequest(ServletRequest request, ServletResponse response) throws Exception {
+ if ( isLoginRequest(request,response) ) {
+ if ( isLoginSubmission(request,response)) {
+ if ( log.isTraceEnabled() ) {
+ log.trace("Login submission detected. Attempting to execute login." );
+ }
+ return executeLogin(request, response);
+ } else {
+ if ( log.isTraceEnabled() ) {
+ log.trace("Login page view.");
+ }
+ //allow them to see the login page ;)
+ return true;
+ }
+ } else {
+ if ( log.isTraceEnabled() ) {
+ log.trace("Attempting to access a path which requires authentication. Forwarding to the " +
+ "Authentication url [" + getUrl() + "]" );
+ }
+ issueRedirect(request,response);
+ return false;
+ }
+ }
+
+ protected boolean isLoginSubmission(ServletRequest servletRequest, ServletResponse response ) {
+ return toHttp(servletRequest).getMethod().equalsIgnoreCase("POST");
+ }
+
+ protected boolean isLoginRequest(ServletRequest servletRequest, ServletResponse response ) {
+ HttpServletRequest request = toHttp(servletRequest);
+ String requestURI = WebUtils.getPathWithinApplication(request);
+ return pathMatcher.match( getUrl(), requestURI );
+ }
+
+ protected boolean executeLogin(ServletRequest request, ServletResponse response ) throws Exception {
+ String username = getUsername(request,response);
+ String password = getPassword(request,response);
+ boolean rememberMe = isRememberMe(request,response);
+ InetAddress inet = getInetAddress(request,response);
+ UsernamePasswordToken token = new UsernamePasswordToken(username, password.toCharArray(), rememberMe, inet );
+
+ try {
+ getSubject(request,response).login(token);
+ issueSuccessRedirect(request,response);
+ return false;
+ } catch (AuthenticationException e) {
+ String className = e.getClass().getName();
+ request.setAttribute(getFailureKeyAtribute(), className );
+ //login failed, let request continue back to the login page:
+ return true;
+ }
+ }
+
+ protected void issueSuccessRedirect( ServletRequest request, ServletResponse response ) throws Exception {
+ RedirectView view = new RedirectView( getSuccessUrl(), isContextRelative(), isHttp10Compatible() );
+ view.renderMergedOutputModel(getQueryParams(), toHttp(request), toHttp(response) );
+ }
+
+ protected String getUsername( ServletRequest request, ServletResponse response ) {
+ return StringUtils.clean(request.getParameter(getUsernameParam()));
+ }
+
+ protected String getPassword( ServletRequest request, ServletResponse response ) {
+ return StringUtils.clean(request.getParameter(getPasswordParam()));
+ }
+
+ protected boolean isRememberMe( ServletRequest request, ServletResponse response ) {
+ String rememberMe = StringUtils.clean(request.getParameter(getRememberMeParam()));
+ return rememberMe != null &&
+ (rememberMe.equalsIgnoreCase("true") ||
+ rememberMe.equalsIgnoreCase("1") ||
+ rememberMe.equalsIgnoreCase("y") ||
+ rememberMe.equalsIgnoreCase("yes" ) );
+ }
+
+ protected InetAddress getInetAddress( ServletRequest request, ServletResponse response ) {
+ if ( request instanceof HttpServletRequest ) {
+ try {
+ return InetAddress.getByName( toHttp(request).getRemoteAddr() );
+ } catch (UnknownHostException e) {
+ if ( log.isTraceEnabled() ) {
+ log.trace( "Unable to acquire host for HttpServlet request.", e );
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/org/jsecurity/web/servlet/JSecurityFilter.java b/src/org/jsecurity/web/servlet/JSecurityFilter.java
index 3361ffc..21df4a1 100644
--- a/src/org/jsecurity/web/servlet/JSecurityFilter.java
+++ b/src/org/jsecurity/web/servlet/JSecurityFilter.java
@@ -74,6 +74,19 @@
# interceptor's JavaDoc to fully understand what each does and how it works as well as how it would
# affect the user experience.
#
+ # Form Authentication interceptor: requires the requestiing user to be authenticated for the request to continue
+ # and if they are not, forces the user to login via a login page that you specify. If the login attempt fails
+ # the AuthenticationException fully qualified class name will be placed as a request attribute under the
+ # 'failureKeyAttribute' name below. This FQCN can then be used as an i18n key or lookup mechanism that can then
+ # be used to show the user why their login attempt failed (e.g. no account, incorrect password, etc).
+ #authc = org.jsecurity.web.interceptor.authc.FormAuthenticationWebInterceptor
+ #authc.url = /login.jsp
+ #authc.usernameParam = username
+ #authc.passwordParam = password
+ #authc.rememberMeParam = rememberMe
+ #authc.successUrl = /login.jsp
+ #authc.failureKeyAttribute = org.jsecurity.web.interceptor.authc.FormAuthenticationWebInterceptor_AUTHC_FAILURE_KEY
+ #
# Http BASIC Authentication interceptor: requires the requesting user to be authenticated for the request
# to continue, and if they're not, forces the user to login via the HTTP Basic protocol-specific challenge.
# Upon successful login, they're allowed to continue on to the requested resource/url.
diff --git a/src/org/jsecurity/web/servlet/WebInterceptorFilter.java b/src/org/jsecurity/web/servlet/WebInterceptorFilter.java
index 56503a5..73f8ce9 100644
--- a/src/org/jsecurity/web/servlet/WebInterceptorFilter.java
+++ b/src/org/jsecurity/web/servlet/WebInterceptorFilter.java
@@ -104,6 +104,7 @@
if (interceptor == null) {
throw new IllegalStateException("WebInterceptor property must be set.");
}
+ LifecycleUtils.init(interceptor);
}
public void destroy() {