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
+ }
+}