You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by bd...@apache.org on 2016/10/21 14:26:37 UTC

[9/9] shiro git commit: SHIRO-595 - Allow for POST method only logouts via the LogoutFilter

SHIRO-595 - Allow for POST method only logouts via the LogoutFilter

Setting 'shiro.postOnlyLogout' to false will restrict logout requests to only POST requests.


Project: http://git-wip-us.apache.org/repos/asf/shiro/repo
Commit: http://git-wip-us.apache.org/repos/asf/shiro/commit/921753e8
Tree: http://git-wip-us.apache.org/repos/asf/shiro/tree/921753e8
Diff: http://git-wip-us.apache.org/repos/asf/shiro/diff/921753e8

Branch: refs/heads/1.4.x
Commit: 921753e82de6b7e55277a3be378e68035eea3af5
Parents: 84685c6
Author: Brian Demers <bd...@apache.org>
Authored: Thu Oct 20 17:57:07 2016 -0400
Committer: Brian Demers <bd...@apache.org>
Committed: Fri Oct 21 10:15:45 2016 -0400

----------------------------------------------------------------------
 samples/web/src/main/webapp/WEB-INF/shiro.ini   |  1 +
 samples/web/src/main/webapp/account/index.jsp   |  4 +-
 samples/web/src/main/webapp/home.jsp            |  4 +-
 samples/web/src/main/webapp/logout.jsp          | 34 +++++++
 .../shiro/web/filter/authc/LogoutFilter.java    | 65 +++++++++++++
 .../web/filter/authc/LogoutFilterTest.groovy    | 99 ++++++++++++++++++++
 6 files changed, 203 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/shiro/blob/921753e8/samples/web/src/main/webapp/WEB-INF/shiro.ini
----------------------------------------------------------------------
diff --git a/samples/web/src/main/webapp/WEB-INF/shiro.ini b/samples/web/src/main/webapp/WEB-INF/shiro.ini
index 33c7586..f517f20 100644
--- a/samples/web/src/main/webapp/WEB-INF/shiro.ini
+++ b/samples/web/src/main/webapp/WEB-INF/shiro.ini
@@ -24,6 +24,7 @@
 listener = org.apache.shiro.config.event.LoggingBeanEventListener
 
 shiro.loginUrl = /login.jsp
+shiro.postOnlyLogout = true
 
 # We need to set the cipherKey, if you want the rememberMe cookie to work after restarting or on multiple nodes.
 # YOU MUST SET THIS TO A UNIQUE STRING

http://git-wip-us.apache.org/repos/asf/shiro/blob/921753e8/samples/web/src/main/webapp/account/index.jsp
----------------------------------------------------------------------
diff --git a/samples/web/src/main/webapp/account/index.jsp b/samples/web/src/main/webapp/account/index.jsp
index 4f6c9d8..489d5de 100644
--- a/samples/web/src/main/webapp/account/index.jsp
+++ b/samples/web/src/main/webapp/account/index.jsp
@@ -30,7 +30,7 @@
 
 <p><a href="<c:url value="/home.jsp"/>">Return to the home page.</a></p>
 
-<p><a href="<c:url value="/logout"/>">Log out.</a></p>
-
+<p><a href="<c:url value="/logout"/>" onclick="document.getElementById('logout_form').submit();return false;">Log out.</a></p>
+<form id="logout_form" action="<c:url value="/logout"/>" method="post"></form>
 </body>
 </html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/shiro/blob/921753e8/samples/web/src/main/webapp/home.jsp
----------------------------------------------------------------------
diff --git a/samples/web/src/main/webapp/home.jsp b/samples/web/src/main/webapp/home.jsp
index 61dee25..ae46cde 100644
--- a/samples/web/src/main/webapp/home.jsp
+++ b/samples/web/src/main/webapp/home.jsp
@@ -28,7 +28,7 @@
 <h1>Apache Shiro Quickstart</h1>
 
 <p>Hi <shiro:guest>Guest</shiro:guest><shiro:user><shiro:principal/></shiro:user>!
-    ( <shiro:user><a href="<c:url value="/logout"/>">Log out</a></shiro:user>
+    ( <shiro:user>  <a href="<c:url value="/logout"/>" onclick="document.getElementById('logout_form').submit();return false;">logout</a> </shiro:user>
     <shiro:guest><a href="<c:url value="/login.jsp"/>">Log in</a> (sample accounts provided)</shiro:guest> )
 </p>
 
@@ -64,6 +64,6 @@
     <shiro:lacksRole name="schwartz">schwartz<br/></shiro:lacksRole>
 </p>
 
-
+<form id="logout_form" action="<c:url value="/logout"/>" method="post"></form>
 </body>
 </html>

http://git-wip-us.apache.org/repos/asf/shiro/blob/921753e8/samples/web/src/main/webapp/logout.jsp
----------------------------------------------------------------------
diff --git a/samples/web/src/main/webapp/logout.jsp b/samples/web/src/main/webapp/logout.jsp
new file mode 100644
index 0000000..440ec1c
--- /dev/null
+++ b/samples/web/src/main/webapp/logout.jsp
@@ -0,0 +1,34 @@
+<%--
+  ~ 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.
+  --%>
+<%@ include file="include.jsp" %>
+
+<html>
+<head>
+    <link type="text/css" rel="stylesheet" href="<c:url value="/style.css"/>"/>
+</head>
+<body onload="document.getElementById('logout_form').submit();return false;">
+
+<h2>If you are not automatically redirected, click the 'Logout' button.</h2>
+
+<form id="logout_form" name="logout_form" action="<c:url value="/logout"/>" method="post">
+    <input type="submit" value="Logout">
+</form>
+
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/shiro/blob/921753e8/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java
----------------------------------------------------------------------
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java
index c0d695b..e1da70e 100644
--- a/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java
@@ -21,6 +21,7 @@ package org.apache.shiro.web.filter.authc;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.session.SessionException;
 import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.StringUtils;
 import org.apache.shiro.web.servlet.AdviceFilter;
 import org.apache.shiro.web.util.WebUtils;
 import org.slf4j.Logger;
@@ -28,6 +29,11 @@ import org.slf4j.LoggerFactory;
 
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import java.util.Locale;
+
+import static org.apache.shiro.web.filter.mgt.DefaultFilter.logout;
 
 /**
  * Simple Filter that, upon receiving a request, will immediately log-out the currently executing
@@ -52,6 +58,13 @@ public class LogoutFilter extends AdviceFilter {
     private String redirectUrl = DEFAULT_REDIRECT_URL;
 
     /**
+     * Due to browser pre-fetching, using a GET requests for logout my cause a user to be logged accidentally, for example:
+     * out while typing in an address bar.  If <code>postOnlyLogout</code> is <code>true</code>. Only POST requests will cause
+     * a logout to occur.
+     */
+    private boolean postOnlyLogout = false;
+
+    /**
      * Acquires the currently executing {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject},
      * a potentially Subject or request-specific
      * {@link #getRedirectUrl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, org.apache.shiro.subject.Subject) redirectUrl},
@@ -64,7 +77,18 @@ public class LogoutFilter extends AdviceFilter {
      */
     @Override
     protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
+
         Subject subject = getSubject(request, response);
+
+        // Check if POST only logout is enabled
+        if (isPostOnlyLogout()) {
+
+            // check if the current request's method is a POST, if not redirect
+            if (!WebUtils.toHttp(request).getMethod().toUpperCase(Locale.ENGLISH).equals("POST")) {
+               return onLogoutRequestNotAPost(request, response);
+            }
+        }
+
         String redirectUrl = getRedirectUrl(request, response, subject);
         //try/catch added for SHIRO-298:
         try {
@@ -140,7 +164,48 @@ public class LogoutFilter extends AdviceFilter {
      *
      * @param redirectUrl the url to where the user will be redirected after logout
      */
+    @SuppressWarnings("unused")
     public void setRedirectUrl(String redirectUrl) {
         this.redirectUrl = redirectUrl;
     }
+
+
+    /**
+     * This method is called when <code>postOnlyLogout</code> is <code>true</code>, and the request was NOT a <code>POST</code>.
+     * For example if this filter is bound to '/logout' and the caller makes a GET request, this method would be invoked.
+     * <p>
+     *     The default implementation sets the response code to a 405, and sets the 'Allow' header to 'POST', and
+     *     always returns false.
+     * </p>
+     *
+     * @return The return value indicates if the processing should continue in this filter chain.
+     */
+    protected boolean onLogoutRequestNotAPost(ServletRequest request, ServletResponse response) {
+
+        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
+        httpServletResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+        httpServletResponse.setHeader("Allow", "POST");
+        return false;
+    }
+
+    /**
+     * Due to browser pre-fetching, using a GET requests for logout my cause a user to be logged accidentally, for example:
+     * out while typing in an address bar.  If <code>postOnlyLogout</code> is <code>true</code>. Only POST requests will cause
+     * a logout to occur.
+     *
+     * @return Returns true if POST only logout is enabled
+     */
+    public boolean isPostOnlyLogout() {
+        return postOnlyLogout;
+    }
+
+    /**
+     * Due to browser pre-fetching, using a GET requests for logout my cause a user to be logged accidentally, for example:
+     * out while typing in an address bar.  If <code>postOnlyLogout</code> is <code>true</code>. Only POST requests will cause
+     * a logout to occur.
+     * @param postOnlyLogout enable or disable POST only logout.
+     */
+    public void setPostOnlyLogout(boolean postOnlyLogout) {
+        this.postOnlyLogout = postOnlyLogout;
+    }
 }

http://git-wip-us.apache.org/repos/asf/shiro/blob/921753e8/web/src/test/groovy/org/apache/shiro/web/filter/authc/LogoutFilterTest.groovy
----------------------------------------------------------------------
diff --git a/web/src/test/groovy/org/apache/shiro/web/filter/authc/LogoutFilterTest.groovy b/web/src/test/groovy/org/apache/shiro/web/filter/authc/LogoutFilterTest.groovy
new file mode 100644
index 0000000..ff861f0
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/filter/authc/LogoutFilterTest.groovy
@@ -0,0 +1,99 @@
+package org.apache.shiro.web.filter.authc
+
+import org.apache.shiro.subject.Subject
+import org.junit.Test
+
+import javax.servlet.ServletRequest
+import javax.servlet.ServletResponse
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+import static org.easymock.EasyMock.*
+import static org.junit.Assert.*
+
+/**
+ * Tests for {@link LogoutFilterTest}.
+ */
+class LogoutFilterTest {
+
+    @Test
+    void testLogoutViaGetMethod() {
+
+        def request = mock(HttpServletRequest)
+        def response = mock(HttpServletResponse)
+        def subject = mock(Subject)
+
+        // expect
+        subject.logout()
+        expect(request.getContextPath()).andReturn("")
+        expect(response.encodeRedirectURL("/")).andReturn("/").anyTimes()
+        response.sendRedirect("/")
+
+        replay request, response, subject
+
+        def filter = new LogoutFilter() {
+            @Override
+            protected Subject getSubject(ServletRequest servletRequest, ServletResponse servletResponse) {
+                return subject
+            }
+        };
+
+        filter.preHandle(request, response)
+
+        verify request, response, subject
+    }
+
+    @Test
+    void testLogoutViaGetMethodWhenPostOnlyEnabled() {
+
+        def request = mock(HttpServletRequest)
+        def response = mock(HttpServletResponse)
+        def subject = mock(Subject)
+
+        // expect
+        expect(request.getMethod()).andReturn("GET")
+        expect(response.setStatus(405))
+        expect(response.setHeader("Allow", "POST"))
+
+        replay request, response, subject
+
+        def filter = new LogoutFilter() {
+            @Override
+            protected Subject getSubject(ServletRequest servletRequest, ServletResponse servletResponse) {
+                return subject
+            }
+        };
+        filter.setPostOnlyLogout(true)
+        filter.preHandle(request, response)
+
+        verify request, response, subject
+    }
+
+    @Test
+    void testLogoutViaPostMethodWhenPostOnlyEnabled() {
+
+        def request = mock(HttpServletRequest)
+        def response = mock(HttpServletResponse)
+        def subject = mock(Subject)
+
+        // expect
+        expect(request.getMethod()).andReturn("Post")
+        subject.logout()
+        expect(request.getContextPath()).andReturn("")
+        expect(response.encodeRedirectURL("/")).andReturn("/").anyTimes()
+        response.sendRedirect("/")
+
+        replay request, response, subject
+
+        def filter = new LogoutFilter() {
+            @Override
+            protected Subject getSubject(ServletRequest servletRequest, ServletResponse servletResponse) {
+                return subject
+            }
+        };
+        filter.setPostOnlyLogout(true)
+        filter.preHandle(request, response)
+
+        verify request, response, subject
+    }
+}