You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by th...@apache.org on 2012/02/14 14:44:08 UTC

svn commit: r1243917 - in /cocoon/cocoon3/trunk: cocoon-shiro-sample/src/main/java/org/apache/cocoon/shiro/sample/rest/LoginUser.java cocoon-shiro/src/main/java/org/apache/cocoon/shiro/rest/AbstractShiroLogin.java

Author: thorsten
Date: Tue Feb 14 13:44:08 2012
New Revision: 1243917

URL: http://svn.apache.org/viewvc?rev=1243917&view=rev
Log:
COCOON3-89
Reporter:
    Ajay Deshwal 
Add feature to limit invalid login attempts

Thanks Ajay for your patch

Modified:
    cocoon/cocoon3/trunk/cocoon-shiro-sample/src/main/java/org/apache/cocoon/shiro/sample/rest/LoginUser.java
    cocoon/cocoon3/trunk/cocoon-shiro/src/main/java/org/apache/cocoon/shiro/rest/AbstractShiroLogin.java

Modified: cocoon/cocoon3/trunk/cocoon-shiro-sample/src/main/java/org/apache/cocoon/shiro/sample/rest/LoginUser.java
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-shiro-sample/src/main/java/org/apache/cocoon/shiro/sample/rest/LoginUser.java?rev=1243917&r1=1243916&r2=1243917&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-shiro-sample/src/main/java/org/apache/cocoon/shiro/sample/rest/LoginUser.java (original)
+++ cocoon/cocoon3/trunk/cocoon-shiro-sample/src/main/java/org/apache/cocoon/shiro/sample/rest/LoginUser.java Tue Feb 14 13:44:08 2012
@@ -18,8 +18,12 @@
  */
 package org.apache.cocoon.shiro.sample.rest;
 
+import java.net.MalformedURLException;
+import java.util.Map;
 
 import org.apache.cocoon.rest.controller.annotation.RESTController;
+import org.apache.cocoon.rest.controller.response.RestResponse;
+import org.apache.cocoon.rest.controller.response.URLResponse;
 import org.apache.cocoon.shiro.rest.AbstractShiroLogin;
 
 @RESTController
@@ -28,8 +32,9 @@ public class LoginUser extends AbstractS
 
     private static final String LOGIN_PAGE = "servlet:/screen/login";
 
-    protected String getLoginPage() {
-        return LOGIN_PAGE;
+    protected RestResponse getLoginPage(Map<String, Object> data)
+            throws MalformedURLException {
+        return new URLResponse(LOGIN_PAGE, data);
     }
 
     protected String getDefaultTo() {
@@ -37,7 +42,7 @@ public class LoginUser extends AbstractS
     }
 
     protected String getErrorLogin() {
-        return getLoginPage();
+        return LOGIN_PAGE;
     }
 
 }

Modified: cocoon/cocoon3/trunk/cocoon-shiro/src/main/java/org/apache/cocoon/shiro/rest/AbstractShiroLogin.java
URL: http://svn.apache.org/viewvc/cocoon/cocoon3/trunk/cocoon-shiro/src/main/java/org/apache/cocoon/shiro/rest/AbstractShiroLogin.java?rev=1243917&r1=1243916&r2=1243917&view=diff
==============================================================================
--- cocoon/cocoon3/trunk/cocoon-shiro/src/main/java/org/apache/cocoon/shiro/rest/AbstractShiroLogin.java (original)
+++ cocoon/cocoon3/trunk/cocoon-shiro/src/main/java/org/apache/cocoon/shiro/rest/AbstractShiroLogin.java Tue Feb 14 13:44:08 2012
@@ -31,6 +31,8 @@ import org.apache.cocoon.rest.controller
 import org.apache.cocoon.rest.controller.response.URLResponse;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.ExcessiveAttemptsException;
 import org.apache.shiro.authc.IncorrectCredentialsException;
 import org.apache.shiro.authc.UnknownAccountException;
 import org.apache.shiro.authc.UsernamePasswordToken;
@@ -42,12 +44,14 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @RESTController
-public abstract class AbstractShiroLogin implements Post, Get{
-    
-    protected abstract String getErrorLogin() ;
-    protected abstract String getDefaultTo();
-    protected abstract String getLoginPage() ;
-    
+public abstract class AbstractShiroLogin implements Post, Get {
+
+    protected final String LOGIN_ATTEMPTS_EXCEEDED = "shiroLoginAttemptExceeded";
+
+    protected final String LOGIN_ATTEMPTS_COUNTER = "shiroLoginAttemptCounter";
+
+    private Integer allowedAttempts = 5;
+
     @RequestParameter
     private String username;
     @RequestParameter
@@ -56,7 +60,16 @@ public abstract class AbstractShiroLogin
     private boolean rememberMe;
     @RequestParameter
     protected String to;
-    protected static final Logger LOG = LoggerFactory.getLogger(AbstractShiroLogin.class);
+
+    protected static final Logger LOG = LoggerFactory
+            .getLogger(AbstractShiroLogin.class);
+
+    protected abstract String getErrorLogin();
+
+    protected abstract String getDefaultTo();
+
+    protected abstract RestResponse getLoginPage(Map<String, Object> data)
+            throws Exception;
 
     public RestResponse doPost() throws Exception {
         // create a UsernamePasswordToken using the
@@ -65,15 +78,35 @@ public abstract class AbstractShiroLogin
                 password, rememberMe);
         Subject subject = SecurityUtils.getSubject();
         boolean error = true;
+        Boolean attemptsExceeded = (Boolean) subject.getSession().getAttribute(
+                LOGIN_ATTEMPTS_EXCEEDED);
+        if (attemptsExceeded == null) {
+            attemptsExceeded = false;
+        }
+        boolean invalidCredentials = false;
+        Map<String, Object> errorData = null;
         try {
-            subject.login(token);
-            error = false;
+            errorData = validatePreLogin();
+            if (errorData == null || errorData.isEmpty()) {
+                subject.login(token);
+                error = false;
+                subject.getSession().removeAttribute(LOGIN_ATTEMPTS_EXCEEDED);
+                subject.getSession().removeAttribute(LOGIN_ATTEMPTS_COUNTER);
+            }
+        } catch (ExcessiveAttemptsException eae) {
+            LOG.error(eae.getMessage());
+            attemptsExceeded = true;
         } catch (UnknownAccountException ex) {
+            invalidCredentials = true;
             LOG.error("UnknownAccountException", ex);
         } catch (IncorrectCredentialsException ex) {
+            invalidCredentials = true;
             // password provided did not match password found in database
             // for the username provided
             LOG.error("IncorrectCredentialsException", ex);
+        } catch (AuthenticationException authEx) {
+            invalidCredentials = true;
+            LOG.error("AuthenticationException", authEx);
         } catch (Exception e) {
             LOG.error("Exception", e);
         } finally {
@@ -81,21 +114,73 @@ public abstract class AbstractShiroLogin
         }
         // clear the information stored in the token
         if (error) {
-            Map<String, Object> data = new HashMap<String, Object>();
-            data.put("error", true);
-            data.put("to", getTo());
-            return getErrorResponse(data);
+            if (invalidCredentials && !attemptsExceeded) {
+                attemptsExceeded = isAttemptsExceeded(subject);
+            }
+            subject.getSession().setAttribute(LOGIN_ATTEMPTS_EXCEEDED,
+                    attemptsExceeded);
+            if (errorData == null) {
+                errorData = new HashMap<String, Object>();
+            }
+            errorData.put("error", true);
+            errorData.put("to", getTo());
+            errorData.put("loginAttemptExceeded", attemptsExceeded);
+            return getErrorResponse(errorData);
         } else {
             return getSuccessResponse();
         }
     }
 
-    protected RestResponse getSuccessResponse(){
-      return new RedirectResponse(getTo());
+    /**
+     * Checks if login attempts exceeded allowed wrong attempts.
+     * 
+     * @param subject
+     *            the shiro subject
+     * @return true, if login attempts exceeded
+     */
+    private boolean isAttemptsExceeded(Subject subject) {
+        Integer attemptCount = (Integer) subject.getSession().getAttribute(
+                LOGIN_ATTEMPTS_COUNTER);
+        if (attemptCount == null) {
+            attemptCount = 0;
+        }
+        if (attemptCount >= getAllowedWrongAttempts()) {
+            return true;
+        } else {
+            attemptCount++;
+        }
+        subject.getSession().setAttribute(LOGIN_ATTEMPTS_COUNTER, attemptCount);
+        return false;
+    }
+
+    /**
+     * Gets the number of maximum allowed wrong login attempts.
+     * 
+     * @return the allowed wrong attempts count
+     */
+    private Integer getAllowedWrongAttempts() {
+        return allowedAttempts;
+    }
+
+    /**
+     * A validation method that is invoked before initiating login. If this
+     * method returns a non-empty map, then login is skipped and map data is
+     * added to UrlResponse. It can be overridden in extending classes to
+     * perform validations before login like captcha.
+     * 
+     * @return the data map that will be passed to error URL Rest response
+     */
+    protected Map<String, Object> validatePreLogin() {
+        return null;
+    }
+
+    protected RestResponse getSuccessResponse() {
+        return new RedirectResponse(getTo());
     }
 
-    protected RestResponse getErrorResponse(Map<String, Object> data) throws MalformedURLException{
-       return new URLResponse(getErrorLogin(), data);
+    protected RestResponse getErrorResponse(Map<String, Object> data)
+            throws MalformedURLException {
+        return new URLResponse(getErrorLogin(), data);
     }
 
     public RestResponse doGet() throws Exception {
@@ -111,7 +196,9 @@ public abstract class AbstractShiroLogin
         Map<String, Object> data = new HashMap<String, Object>();
         data.put("to", getTo());
         data.put("error", false);
-        return new URLResponse(getLoginPage(), data);
+        data.put("loginAttemptExceeded",
+                subject.getSession().getAttribute(LOGIN_ATTEMPTS_EXCEEDED));
+        return getLoginPage(data);
     }
 
     protected String getTo() {