You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lh...@apache.org on 2010/04/23 03:37:14 UTC

svn commit: r937127 - in /incubator/shiro/trunk/web/src: main/java/org/apache/shiro/web/ main/java/org/apache/shiro/web/mgt/ main/java/org/apache/shiro/web/servlet/ main/java/org/apache/shiro/web/session/ test/java/org/apache/shiro/web/ test/java/org/a...

Author: lhazlewood
Date: Fri Apr 23 01:37:14 2010
New Revision: 937127

URL: http://svn.apache.org/viewvc?rev=937127&view=rev
Log:
SHIRO-128:

Added new Cookie interface and SimpleCookie implementation.  Created new CookieRememberMeManager implementation that did the same thing as the old WebRememberMeManager but with easier-to-understand Cookie semantics instead of the obscure and confusing WebAttribute concept.

The addition of this interface and simple implementation allowed the removal of all the convenience methods related to 1) remember me cookies and 2) session id cookies

The new Cookie concept can be configured like any other pojo in INI configuration (or any other compatible JavaBeans config mechanism)

This accounts for almost half of the convenience methods to remove.   Other half still needs to be completed.

Added:
    incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java
    incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/Cookie.java
    incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java
    incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/mgt/
    incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java
Modified:
    incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/DefaultWebSecurityManager.java
    incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DefaultWebSessionManager.java
    incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DelegatingWebSecurityManagerTest.java

Modified: incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/DefaultWebSecurityManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/DefaultWebSecurityManager.java?rev=937127&r1=937126&r2=937127&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/DefaultWebSecurityManager.java (original)
+++ incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/DefaultWebSecurityManager.java Fri Apr 23 01:37:14 2010
@@ -20,11 +20,10 @@ package org.apache.shiro.web;
 
 import org.apache.shiro.mgt.DefaultSecurityManager;
 import org.apache.shiro.realm.Realm;
-import org.apache.shiro.session.mgt.SessionManager;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.subject.SubjectContext;
 import org.apache.shiro.util.LifecycleUtils;
-import org.apache.shiro.web.attr.CookieAttribute;
+import org.apache.shiro.web.mgt.CookieRememberMeManager;
 import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
 import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
 import org.apache.shiro.web.session.DefaultWebSessionManager;
@@ -63,7 +62,7 @@ public class DefaultWebSecurityManager e
     public DefaultWebSecurityManager() {
         super();
         setSubjectFactory(new DefaultWebSubjectFactory());
-        setRememberMeManager(new WebRememberMeManager());
+        setRememberMeManager(new CookieRememberMeManager());
         setSessionManager(new ServletContainerSessionManager());
     }
 
@@ -77,7 +76,6 @@ public class DefaultWebSecurityManager e
         setRealms(realms);
     }
 
-
     @Override
     protected SubjectContext createSubjectContext() {
         return new DefaultWebSubjectContext();
@@ -92,116 +90,6 @@ public class DefaultWebSecurityManager e
         }
     }
 
-    //TODO - yuck - create an interface
-
-    protected WebRememberMeManager getRememberMeManagerForCookieAttributes() {
-        if (!(getRememberMeManager() instanceof WebRememberMeManager)) {
-            String msg = "Currently the " + getClass().getName() + " implementation only allows setting " +
-                    "rememberMe cookie attributes directly if the underlying RememberMeManager implementation " +
-                    "is an " + WebRememberMeManager.class.getName() + " instance.";
-            throw new IllegalStateException(msg);
-        }
-        return (WebRememberMeManager) getRememberMeManager();
-    }
-
-    protected CookieAttribute<String> getRememberMeCookieAttribute() {
-        return (CookieAttribute<String>) getRememberMeManagerForCookieAttributes().getIdentityAttribute();
-    }
-
-    public void setRememberMeCookieAttribute(CookieAttribute<String> cookieAttribute) {
-        getRememberMeManagerForCookieAttributes().setIdentityAttribute(cookieAttribute);
-    }
-
-    public void setRememberMeCookieName(String name) {
-        getRememberMeCookieAttribute().setName(name);
-    }
-
-    public void setRememberMeCookieDomain(String domain) {
-        getRememberMeCookieAttribute().setDomain(domain);
-    }
-
-    /**
-     * Sets the path used to store the remember me cookie.  This determines which paths
-     * are able to view the remember me cookie.
-     *
-     * @param rememberMeCookiePath the path to use for the remember me cookie.
-     */
-    public void setRememberMeCookiePath(String rememberMeCookiePath) {
-        getRememberMeCookieAttribute().setPath(rememberMeCookiePath);
-    }
-
-    /**
-     * Sets the maximum age allowed for the remember me cookie.  This basically sets how long
-     * a user will be remembered by the "remember me" feature.  Used when calling
-     * {@link javax.servlet.http.Cookie#setMaxAge(int) maxAge}.  Please see that JavaDoc for the semantics on the
-     * repercussions of negative, zero, and positive values for the maxAge.                           i
-     *
-     * @param rememberMeMaxAge the maximum age for the remember me cookie.
-     */
-    public void setRememberMeCookieMaxAge(Integer rememberMeMaxAge) {
-        getRememberMeCookieAttribute().setMaxAge(rememberMeMaxAge);
-    }
-
-    public void setRememberMeCookieVersion(int version) {
-        getRememberMeCookieAttribute().setVersion(version);
-    }
-
-    public void setRememberMeCookieSecure(boolean secure) {
-        getRememberMeCookieAttribute().setSecure(secure);
-    }
-
-    public void setRememberMeCookieComment(String comment) {
-        getRememberMeCookieAttribute().setComment(comment);
-    }
-
-    private DefaultWebSessionManager getSessionManagerForCookieAttributes() {
-        SessionManager sessionManager = getSessionManager();
-        if (!(sessionManager instanceof DefaultWebSessionManager)) {
-            String msg = "The convenience passthrough methods for setting session id cookie attributes " +
-                    "are only available when the underlying SessionManager implementation is " +
-                    DefaultWebSessionManager.class.getName() + ", which is enabled by default when the " +
-                    "sessionMode is 'shiro'.";
-            throw new IllegalStateException(msg);
-        }
-        return (DefaultWebSessionManager) sessionManager;
-    }
-
-    protected CookieAttribute<Serializable> getSessionIdCookieAttribute() {
-        return getSessionManagerForCookieAttributes().getSessionIdCookieAttribute();
-    }
-
-    public void setSessionIdCookieAttribute(CookieAttribute<Serializable> cookieAttribute) {
-        getSessionManagerForCookieAttributes().setSessionIdCookieAttribute(cookieAttribute);
-    }
-
-    public void setSessionIdCookieName(String name) {
-        getSessionIdCookieAttribute().setName(name);
-    }
-
-    public void setSessionIdCookieDomain(String domain) {
-        getSessionIdCookieAttribute().setDomain(domain);
-    }
-
-    public void setSessionIdCookiePath(String path) {
-        getSessionIdCookieAttribute().setPath(path);
-    }
-
-    public void setSessionIdCookieMaxAge(int maxAge) {
-        getSessionIdCookieAttribute().setMaxAge(maxAge);
-    }
-
-    public void setSessionIdCookieVersion(int version) {
-        getSessionIdCookieAttribute().setVersion(version);
-    }
-
-    public void setSessionIdCookieSecure(boolean secure) {
-        getSessionIdCookieAttribute().setSecure(secure);
-    }
-
-    public void setSessionIdCookieComment(String comment) {
-        getSessionIdCookieAttribute().setComment(comment);
-    }
-
     public String getSessionMode() {
         return sessionMode;
     }
@@ -257,6 +145,7 @@ public class DefaultWebSecurityManager e
                 sessionId = ((WebSessionManager) getSessionManager()).getSessionId(request, response);
             }
         }
+
         return sessionId;
     }
 

Added: incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java?rev=937127&view=auto
==============================================================================
--- incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java (added)
+++ incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java Fri Apr 23 01:37:14 2010
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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
+ *
+ *     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.apache.shiro.web.mgt;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.mgt.AbstractRememberMeManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.SubjectContext;
+import org.apache.shiro.util.CollectionUtils;
+import org.apache.shiro.web.WebUtils;
+import org.apache.shiro.web.servlet.Cookie;
+import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
+import org.apache.shiro.web.servlet.SimpleCookie;
+import org.apache.shiro.web.subject.WebSubject;
+import org.apache.shiro.web.subject.WebSubjectContext;
+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;
+
+
+/**
+ * Remembers a Subject's identity by saving the Subject's {@link Subject#getPrincipals() principals} to a {@link Cookie}
+ * for later retrieval.
+ * <p/>
+ * Cookie attributes (path, domain, maxAge, etc) may be set on this class's default
+ * {@link #getCookie() cookie} attribute.  The cookie's default name is {@code rememberMe}.
+ * <p/>
+ * Note that because this class subclasses the {@link AbstractRememberMeManager} which already provides serialization
+ * and encryption logic, this class utilizes both for added security before setting the cookie value.
+ *
+ * @author Les Hazlewood
+ * @author Luis Arias
+ * @since 1.0
+ */
+public class CookieRememberMeManager extends AbstractRememberMeManager {
+
+    //TODO - complete JavaDoc
+
+    private static transient final Logger log = LoggerFactory.getLogger(CookieRememberMeManager.class);
+
+    /**
+     * The default name of the underlying rememberMe cookie which is {@code rememberMe}.
+     */
+    public static final String DEFAULT_REMEMBER_ME_COOKIE_NAME = "rememberMe";
+
+    private Cookie cookie;
+
+    public CookieRememberMeManager() {
+        Cookie cookie = new SimpleCookie(DEFAULT_REMEMBER_ME_COOKIE_NAME);
+        cookie.setPath(Cookie.ROOT_PATH);
+        //Peter (Apache Shiro developer) said that Jetty didn't like the CookieAttribute.INDEFINITE value
+        // (Tomcat was ok with it), so just default to a few years for now.  If anyone doesn't visit a site in 3 years
+        // after last login, I doubt any Shiro users would mind their end-users to be forced to log in. - LAH.
+        cookie.setMaxAge(Cookie.ONE_YEAR * 3);
+        this.cookie = cookie;
+    }
+
+    public Cookie getCookie() {
+        return cookie;
+    }
+
+    public void setCookie(Cookie cookie) {
+        this.cookie = cookie;
+    }
+
+    protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
+        WebSubject webSubject = (WebSubject) subject;
+        ServletRequest servletRequest = webSubject.getServletRequest();
+        ServletResponse servletResponse = webSubject.getServletResponse();
+        HttpServletRequest request = WebUtils.toHttp(servletRequest);
+        HttpServletResponse response = WebUtils.toHttp(servletResponse);
+
+        //base 64 encode it and store as a cookie:
+        String base64 = Base64.encodeToString(serialized);
+
+        Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
+        Cookie cookie = new SimpleCookie(template);
+        cookie.setValue(base64);
+        cookie.saveTo(request, response);
+    }
+
+    private ServletRequest getServletRequest(SubjectContext subjectContext) {
+        ServletRequest request = null;
+        if (subjectContext != null && subjectContext instanceof WebSubjectContext) {
+            request = ((WebSubjectContext) subjectContext).getServletRequest();
+        }
+        return request;
+    }
+
+    private ServletResponse getServletResponse(SubjectContext subjectContext) {
+        ServletResponse response = null;
+        if (subjectContext != null && subjectContext instanceof WebSubjectContext) {
+            response = ((WebSubjectContext) subjectContext).getServletResponse();
+        }
+        return response;
+    }
+
+    protected boolean isIdentityRemoved(SubjectContext subjectContext) {
+        ServletRequest request = getServletRequest(subjectContext);
+        if (request != null) {
+            Boolean removed = (Boolean) request.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY);
+            return removed != null && removed;
+        }
+        return false;
+    }
+
+    protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {
+
+        if (CollectionUtils.isEmpty(subjectContext)) {
+            if (log.isTraceEnabled()) {
+                log.trace("Null or empty SubjectContext - unable to retrieve request/response pair to obtain " +
+                        "a request-based identity.  Returning null.");
+            }
+            return null;
+        }
+
+        if (isIdentityRemoved(subjectContext)) {
+            return null;
+        }
+
+        ServletRequest servletRequest = getServletRequest(subjectContext);
+        ServletResponse servletResponse = getServletResponse(subjectContext);
+        HttpServletRequest request = WebUtils.toHttp(servletRequest);
+        HttpServletResponse response = WebUtils.toHttp(servletResponse);
+
+        String base64 = getCookie().readValue(request, response);
+
+        if (base64 != null) {
+            base64 = ensurePadding(base64);
+            if (log.isTraceEnabled()) {
+                log.trace("Acquired Base64 encoded identity [" + base64 + "]");
+            }
+            byte[] decoded = Base64.decode(base64);
+            if (log.isTraceEnabled()) {
+                log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
+            }
+            return decoded;
+        } else {
+            //no cookie set - new site visitor?
+            return null;
+        }
+    }
+
+    /**
+     * Sometimes a user agent will send the rememberMe cookie value without padding,
+     * most likely because {@code =} is a separator in the cookie header.
+     * <p/>
+     * Contributed by Luis Arias.  Thanks Luis!
+     *
+     * @param base64 the base64 encoded String that may need to be padded
+     * @return the base64 String padded if necessary.
+     */
+    private String ensurePadding(String base64) {
+        int length = base64.length();
+        if (length % 4 != 0) {
+            StringBuffer sb = new StringBuffer(base64);
+            for (int i = 0; i < length % 4; ++i) {
+                sb.append('=');
+            }
+            base64 = sb.toString();
+        }
+        return base64;
+    }
+
+    protected void forgetIdentity(Subject subject) {
+        WebSubject webSubject = (WebSubject) subject;
+        ServletRequest request = webSubject.getServletRequest();
+        ServletResponse response = webSubject.getServletResponse();
+        forgetIdentity(request, response);
+    }
+
+    protected void forgetIdentity(SubjectContext subjectContext) {
+        ServletRequest request = getServletRequest(subjectContext);
+        ServletResponse response = getServletResponse(subjectContext);
+        forgetIdentity(request, response);
+    }
+
+    protected void forgetIdentity(ServletRequest request, ServletResponse response) {
+        getCookie().removeFrom(WebUtils.toHttp(request), WebUtils.toHttp(response));
+    }
+}
+

Added: incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/Cookie.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/Cookie.java?rev=937127&view=auto
==============================================================================
--- incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/Cookie.java (added)
+++ incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/Cookie.java Fri Apr 23 01:37:14 2010
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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
+ *
+ *     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.apache.shiro.web.servlet;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * TODO - Class JavaDoc
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public interface Cookie {
+
+    /**
+     * The number of seconds in one year (= 60 * 60 * 24 * 365).
+     */
+    public static final int ONE_YEAR = 60 * 60 * 24 * 365;
+
+    /**
+     * Root path to use when the path hasn't been set and request context root is empty or null.
+     */
+    public static final String ROOT_PATH = "/";
+
+    String getName();
+
+    void setName(String name);
+
+    String getValue();
+
+    void setValue(String value);
+
+    String getComment();
+
+    void setComment(String comment);
+
+    String getDomain();
+
+    void setDomain(String domain);
+
+    int getMaxAge();
+
+    void setMaxAge(int maxAge);
+
+    String getPath();
+
+    void setPath(String path);
+
+    boolean isSecure();
+
+    void setSecure(boolean secure);
+
+    int getVersion();
+
+    void setVersion(int version);
+
+    void saveTo(HttpServletRequest request, HttpServletResponse response);
+
+    void removeFrom(HttpServletRequest request, HttpServletResponse response);
+
+    String readValue(HttpServletRequest request, HttpServletResponse response);
+}

Added: incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java?rev=937127&view=auto
==============================================================================
--- incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java (added)
+++ incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java Fri Apr 23 01:37:14 2010
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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
+ *
+ *     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.apache.shiro.web.servlet;
+
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * TODO - Class JavaDoc
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public class SimpleCookie implements Cookie {
+
+    /**
+     * {@code -1}, indicating the cookie should expire when the browser closes.
+     */
+    public static final int DEFAULT_MAX_AGE = -1;
+
+    /**
+     * {@code -1} indicating that no version property should be set on the cookie.
+     */
+    public static final int DEFAULT_VERSION = -1;
+
+    private static final transient Logger log = LoggerFactory.getLogger(SimpleCookie.class);
+
+    private String name;
+    private String value;
+    private String comment;
+    private String domain;
+    private String path;
+    private int maxAge;
+    private int version;
+    private boolean secure;
+
+    public SimpleCookie() {
+        this.maxAge = DEFAULT_MAX_AGE;
+        this.version = DEFAULT_VERSION;
+    }
+
+    public SimpleCookie(String name) {
+        this();
+        this.name = name;
+    }
+
+    public SimpleCookie(Cookie cookie) {
+        this.name = cookie.getName();
+        this.value = cookie.getValue();
+        this.comment = cookie.getComment();
+        this.domain = cookie.getDomain();
+        this.path = cookie.getPath();
+        this.maxAge = cookie.getMaxAge();
+        this.version = cookie.getVersion();
+        this.secure = cookie.isSecure();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    public String getDomain() {
+        return domain;
+    }
+
+    public void setDomain(String domain) {
+        this.domain = domain;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    public int getMaxAge() {
+        return maxAge;
+    }
+
+    public void setMaxAge(int maxAge) {
+        this.maxAge = Math.max(-1, maxAge);
+    }
+
+    public int getVersion() {
+        return version;
+    }
+
+    public void setVersion(int version) {
+        this.version = Math.max(-1, version);
+    }
+
+    public boolean isSecure() {
+        return secure;
+    }
+
+    public void setSecure(boolean secure) {
+        this.secure = secure;
+    }
+
+    /**
+     * Returns the Cookie's calculated path setting.  If the {@link javax.servlet.http.Cookie#getPath() path} is {@code null}, then the
+     * {@code request}'s {@link javax.servlet.http.HttpServletRequest#getContextPath() context path}
+     * will be returned. If getContextPath() is the empty string or null then the ROOT_PATH constant is returned.
+     *
+     * @param request the incoming HttpServletRequest
+     * @return the path to be used as the path when the cookie is created or removed
+     */
+    private String calculatePath(HttpServletRequest request) {
+        String path = StringUtils.clean(getPath());
+        if (!StringUtils.hasText(path)) {
+            path = StringUtils.clean(request.getContextPath());
+        }
+
+        //fix for http://issues.apache.org/jira/browse/JSEC-34:
+        if (path == null) {
+            path = ROOT_PATH;
+        }
+        log.trace("calculated path: {}", path);
+        return path;
+    }
+
+    public void saveTo(HttpServletRequest request, HttpServletResponse response) {
+
+        String name = getName();
+        String value = getValue();
+        String comment = getComment();
+        String domain = getDomain();
+        int version = getVersion();
+        int maxAge = getMaxAge();
+        String path = calculatePath(request);
+
+        javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(name, value);
+        cookie.setMaxAge(maxAge);
+        cookie.setPath(path);
+
+        if (comment != null) {
+            cookie.setComment(comment);
+        }
+        if (domain != null) {
+            cookie.setDomain(domain);
+        }
+        if (version > DEFAULT_VERSION) {
+            cookie.setVersion(version);
+        }
+
+        if (isSecure()) {
+            cookie.setSecure(true);
+        }
+
+        response.addCookie(cookie);
+
+        if (log.isDebugEnabled()) {
+            log.debug("Added Cookie [{}] to path [{}] with value [{}] to the HttpServletResponse",
+                    new Object[]{name, path, value});
+        }
+    }
+
+    /**
+     * Returns the cookie with the given name from the request or {@code null} if no cookie
+     * with that name could be found.
+     *
+     * @param request    the current executing http request.
+     * @param cookieName the name of the cookie to find and return.
+     * @return the cookie with the given name from the request or {@code null} if no cookie
+     *         with that name could be found.
+     */
+    private static javax.servlet.http.Cookie getCookie(HttpServletRequest request, String cookieName) {
+        javax.servlet.http.Cookie cookies[] = request.getCookies();
+        if (cookies != null) {
+            for (javax.servlet.http.Cookie cookie : cookies) {
+                if (cookie.getName().equals(cookieName)) {
+                    return cookie;
+                }
+            }
+        }
+        return null;
+    }
+
+    public void removeFrom(HttpServletRequest request, HttpServletResponse response) {
+        javax.servlet.http.Cookie cookie = getCookie(request, getName());
+
+        if (cookie != null) {
+            cookie.setMaxAge(0);
+            cookie.setValue("deleteMe");
+
+            //JSEC-94: Must set the path on the outgoing cookie (some browsers don't retain it from the
+            //retrieved cookie?)
+            // my testing shows none of these browsers will remove cookie if setPath() is not invoked: FF3, Chrome, IE7, Safari windows
+            String path = calculatePath(request);
+            cookie.setPath(path);
+
+            String domain = getDomain();
+            if (domain != null) {
+                cookie.setDomain(domain);
+            }
+
+            response.addCookie(cookie);
+            log.trace("Removed cookie[" + getName() + "] with path [" + path + "] from HttpServletResponse.");
+        }
+    }
+
+    public String readValue(HttpServletRequest request, HttpServletResponse ignored) {
+        String name = getName();
+        String value = null;
+        javax.servlet.http.Cookie cookie = getCookie(request, name);
+        if (cookie != null) {
+            value = cookie.getValue();
+            log.debug("Found string value [{}] from Cookie [{}]", value, name);
+        } else {
+            log.trace("No value found in request Cookies under cookie name [{}]", name);
+        }
+
+        return value;
+    }
+}

Modified: incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DefaultWebSessionManager.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DefaultWebSessionManager.java?rev=937127&r1=937126&r2=937127&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DefaultWebSessionManager.java (original)
+++ incubator/shiro/trunk/web/src/main/java/org/apache/shiro/web/session/DefaultWebSessionManager.java Fri Apr 23 01:37:14 2010
@@ -21,11 +21,10 @@ package org.apache.shiro.web.session;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.mgt.DefaultSessionManager;
 import org.apache.shiro.web.WebUtils;
-import org.apache.shiro.web.attr.CookieAttribute;
-import org.apache.shiro.web.attr.RequestParamAttribute;
-import org.apache.shiro.web.attr.WebAttribute;
+import org.apache.shiro.web.servlet.Cookie;
 import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
 import org.apache.shiro.web.servlet.ShiroHttpSession;
+import org.apache.shiro.web.servlet.SimpleCookie;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -35,7 +34,7 @@ import java.io.Serializable;
 
 
 /**
- * Web-application capable <tt>SessionManager</tt> implementation.
+ * Web-application capable {@link org.apache.shiro.session.mgt.SessionManager SessionManager} implementation.
  *
  * @author Les Hazlewood
  * @since 0.9
@@ -46,58 +45,19 @@ public class DefaultWebSessionManager ex
 
     private static final Logger log = LoggerFactory.getLogger(DefaultWebSessionManager.class);
 
-    private CookieAttribute<Serializable> sessionIdCookieAttribute = null;
-    private RequestParamAttribute<Serializable> sessionIdRequestParamAttribute = null;
+    private Cookie sessionIdCookie;
 
     public DefaultWebSessionManager() {
-        this.sessionIdCookieAttribute = new CookieAttribute<Serializable>(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
-        this.sessionIdCookieAttribute.setCheckRequestParams(false);
-        this.sessionIdRequestParamAttribute =
-                new RequestParamAttribute<Serializable>(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
+        this.sessionIdCookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
+        this.sessionIdCookie.setPath(Cookie.ROOT_PATH);
     }
 
-    public CookieAttribute<Serializable> getSessionIdCookieAttribute() {
-        return sessionIdCookieAttribute;
+    public Cookie getSessionIdCookie() {
+        return sessionIdCookie;
     }
 
-    public void setSessionIdCookieAttribute(CookieAttribute<Serializable> sessionIdCookieAttribute) {
-        this.sessionIdCookieAttribute = sessionIdCookieAttribute;
-    }
-
-    public RequestParamAttribute<Serializable> getSessionIdRequestParamAttribute() {
-        return sessionIdRequestParamAttribute;
-    }
-
-    public void setSessionIdRequestParamAttribute(RequestParamAttribute<Serializable> sessionIdRequestParamAttribute) {
-        this.sessionIdRequestParamAttribute = sessionIdRequestParamAttribute;
-    }
-
-    public void setSessionIdCookieName(String name) {
-        getSessionIdCookieAttribute().setName(name);
-    }
-
-    public void setSessionIdCookieDomain(String domain) {
-        getSessionIdCookieAttribute().setDomain(domain);
-    }
-
-    public void setSessionIdCookiePath(String path) {
-        getSessionIdCookieAttribute().setPath(path);
-    }
-
-    public void setSessionIdCookieMaxAge(int maxAge) {
-        getSessionIdCookieAttribute().setMaxAge(maxAge);
-    }
-
-    public void setSessionIdCookieVersion(int version) {
-        getSessionIdCookieAttribute().setVersion(version);
-    }
-
-    public void setSessionIdCookieSecure(boolean secure) {
-        getSessionIdCookieAttribute().setSecure(secure);
-    }
-
-    public void setSessionIdCookieComment(String comment) {
-        getSessionIdCookieAttribute().setComment(comment);
+    public void setSessionIdCookie(Cookie sessionIdCookie) {
+        this.sessionIdCookie = sessionIdCookie;
     }
 
     protected void storeSessionId(Serializable currentId, ServletRequest request, ServletResponse response) {
@@ -105,7 +65,10 @@ public class DefaultWebSessionManager ex
             String msg = "sessionId cannot be null when persisting for subsequent requests.";
             throw new IllegalArgumentException(msg);
         }
-        getSessionIdCookieAttribute().storeValue(currentId, request, response);
+        Cookie template = getSessionIdCookie();
+        Cookie cookie = new SimpleCookie(template);
+        cookie.setValue(currentId.toString());
+        cookie.saveTo(WebUtils.toHttp(request), WebUtils.toHttp(response));
     }
 
     private void markSessionIdValid(Serializable sessionId, ServletRequest request) {
@@ -117,17 +80,16 @@ public class DefaultWebSessionManager ex
     }
 
     private void removeSessionIdCookie(ServletRequest request, ServletResponse response) {
-        getSessionIdCookieAttribute().removeValue(request, response);
+        getSessionIdCookie().removeFrom(WebUtils.toHttp(request), WebUtils.toHttp(response));
     }
 
     protected Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
-        WebAttribute<Serializable> cookieSessionIdAttribute = getSessionIdCookieAttribute();
-        Serializable id = cookieSessionIdAttribute.retrieveValue(request, response);
+        String id = getSessionIdCookie().readValue(WebUtils.toHttp(request), WebUtils.toHttp(response));
         if (id != null) {
             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                     ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
         } else {
-            id = getSessionIdRequestParamAttribute().retrieveValue(request, response);
+            id = request.getParameter(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
             if (id != null) {
                 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                         ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);

Modified: incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DelegatingWebSecurityManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DelegatingWebSecurityManagerTest.java?rev=937127&r1=937126&r2=937127&view=diff
==============================================================================
--- incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DelegatingWebSecurityManagerTest.java (original)
+++ incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/DelegatingWebSecurityManagerTest.java Fri Apr 23 01:37:14 2010
@@ -24,10 +24,7 @@ import org.apache.shiro.session.Session;
 import org.apache.shiro.session.mgt.AbstractSessionManager;
 import org.apache.shiro.subject.Subject;
 import org.easymock.EasyMock;
-import static org.easymock.EasyMock.*;
 import org.junit.After;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -39,6 +36,10 @@ import java.io.Serializable;
 import java.util.Map;
 import java.util.UUID;
 
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
 /**
  * Unit test for the {@link org.apache.shiro.web.DelegatingWebSecurityManager} implementation.
  *
@@ -73,7 +74,6 @@ public class DelegatingWebSecurityManage
         HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
 
         expect(mockRequest.getCookies()).andReturn(null);
-        expect(mockRequest.getContextPath()).andReturn("/");
 
         String host = "192.168.1.1";
 

Added: incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java?rev=937127&view=auto
==============================================================================
--- incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java (added)
+++ incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java Fri Apr 23 01:37:14 2010
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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
+ *
+ *     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.apache.shiro.web.mgt;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.crypto.CryptoException;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
+import org.apache.shiro.web.subject.WebSubject;
+import org.apache.shiro.web.subject.WebSubjectContext;
+import org.apache.shiro.web.subject.support.DefaultWebSubjectContext;
+import org.junit.Test;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * TODO - Class JavaDoc
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public class CookieRememberMeManagerTest {
+
+    @Test
+    public void onSuccessfulLogin() {
+
+        HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
+        WebSubject mockSubject = createNiceMock(WebSubject.class);
+        expect(mockSubject.getServletRequest()).andReturn(mockRequest).anyTimes();
+        expect(mockSubject.getServletResponse()).andReturn(mockResponse).anyTimes();
+
+        CookieRememberMeManager mgr = new CookieRememberMeManager();
+        UsernamePasswordToken token = new UsernamePasswordToken("user", "secret");
+        token.setRememberMe(true);
+        AuthenticationInfo account = new SimpleAuthenticationInfo("user", "secret", "test");
+
+        expect(mockRequest.getCookies()).andReturn(null);
+
+        replay(mockSubject);
+        replay(mockRequest);
+        mgr.onSuccessfulLogin(mockSubject, token, account);
+        verify(mockRequest);
+        verify(mockSubject);
+    }
+
+    // SHIRO-69
+
+    @Test
+    public void getRememberedPrincipals() {
+        HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
+        WebSubjectContext context = new DefaultWebSubjectContext();
+        context.setServletRequest(mockRequest);
+        context.setServletResponse(mockResponse);
+
+        expect(mockRequest.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY)).andReturn(null);
+
+        //The following base64 string was determined from the log output of the above 'onSuccessfulLogin' test.
+        //This will have to change any time the PrincipalCollection implementation changes:
+        final String userPCAesBase64 = "qk7spFqO1zoNLgq3qArE7bc8+J+Zvm1jz8lDSUmRiRlDQQx7jxG4+" +
+                "QImiRpR7zO0d9oHH+7C3VeN9OvGMdjxtpbInMsLcGz4Q0u3M1fmyErn5Mr61chmNzQ8cLegpIKE3M+xMY" +
+                "5JB1PRw7aEJdRxtHh80kiXZ5jeALvDP3hmFM7OF2CDKLIIa83XuBQvyrKGI9GhsxGTLkmNFknbfRsmN7v" +
+                "NIDorceeaMkAetYf6GxDOw1ZK7yEbsydIHnqVWNHLen6DHC8pLkqMNOoGwXLeBroD6mRpoFf76J0VKBcd" +
+                "C54Mg73S2R7wx9ZzSNJJrCi1KAilmThzm3Rm97EidUnYlWI0TM+zvMzNsLynIK4PoIG6HYQQfEI35qVRI" +
+                "bCdbTlTnjfM/fPf7RWO8s4Z7KzszSQMJE9LgBudcyzrld5ZrWb11cianskNZMI8kzOITezjjqvWn5U4jg" +
+                "Mb9a6qcpaNJcgaxV6NZRmof8cnet54wwE=";
+
+        Cookie[] cookies = new Cookie[]{
+                new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, userPCAesBase64)
+        };
+
+        expect(mockRequest.getCookies()).andReturn(cookies);
+        replay(mockRequest);
+
+        CookieRememberMeManager mgr = new CookieRememberMeManager();
+        PrincipalCollection collection = mgr.getRememberedPrincipals(context);
+
+        verify(mockRequest);
+
+        assertTrue(collection != null);
+        //noinspection ConstantConditions
+        assertTrue(collection.iterator().next().equals("user"));
+    }
+
+    // SHIRO-69
+
+    @Test
+    public void getRememberedPrincipalsDecryptionError() {
+        HttpServletRequest mockRequest = createNiceMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createNiceMock(HttpServletResponse.class);
+
+        WebSubjectContext context = new DefaultWebSubjectContext();
+        context.setServletRequest(mockRequest);
+        context.setServletResponse(mockResponse);
+
+        expect(mockRequest.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY)).andReturn(null);
+
+        // Simulate a bad return value here (for example if this was encrypted with a different key
+        final String userPCAesBase64 = "garbage";
+        Cookie[] cookies = new Cookie[]{
+                new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, userPCAesBase64)
+        };
+
+        expect(mockRequest.getCookies()).andReturn(cookies).anyTimes();
+        replay(mockRequest);
+
+        CookieRememberMeManager mgr = new CookieRememberMeManager();
+        PrincipalCollection collection = null;
+
+        CryptoException ce = null;
+        try {
+            collection = mgr.getRememberedPrincipals(context);
+        } catch (CryptoException expected) {
+            ce = expected;
+        }
+        assertNotNull(ce);
+
+        verify(mockRequest);
+
+        // Collection should be null since there was an error decrypting it
+        assertTrue(collection == null);
+    }
+
+    @Test
+    public void onLogout() {
+        HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
+        WebSubject mockSubject = createNiceMock(WebSubject.class);
+        expect(mockSubject.getServletRequest()).andReturn(mockRequest).anyTimes();
+        expect(mockSubject.getServletResponse()).andReturn(mockResponse).anyTimes();
+
+        Cookie cookie = new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, "");
+        cookie.setMaxAge(0);
+        Cookie[] cookies = new Cookie[]{cookie};
+
+        expect(mockRequest.getCookies()).andReturn(cookies);
+        expect(mockRequest.getContextPath()).andReturn(null).anyTimes();
+        mockResponse.addCookie(eq(cookie));
+
+        replay(mockRequest);
+        replay(mockResponse);
+        replay(mockSubject);
+
+        CookieRememberMeManager mgr = new CookieRememberMeManager();
+        mgr.onLogout(mockSubject);
+
+        verify(mockSubject);
+        verify(mockRequest);
+        verify(mockResponse);
+    }
+}