【问题标题】:How to exclude some page from session management (timeout/concurrency check) in Spring Security?如何从 Spring Security 中的会话管理(超时/并发检查)中排除某些页面?
【发布时间】:2016-09-23 00:54:07
【问题描述】:

我在spring-security.xml 中添加了这段代码来启用会话超时检查和并发检查。

<sec:http>
    <sec:form-login login-page="/login" login-processing-url="/authentication"
                    default-target-url="/home" always-use-default-target="true"
                    authentication-failure-url="/login?error=true"
                    username-parameter="userid" password-parameter="password"/>
    <sec:logout logout-url="/logout" logout-success-url="/login" delete-cookies="JSESSIONID" invalidate-session="true" />

    <!-- User login (URL not View Name) -->
    <sec:intercept-url pattern="/login" access="permitAll" />

    <!-- User change password -->
    <sec:intercept-url pattern="/change_password" access="permitAll" />


    <sec:session-management invalid-session-url="/session_timeout">
        <sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="false" expired-url="/session_conflict"/>
    </sec:session-management>

    <sec:headers>
        <sec:frame-options policy="SAMEORIGIN" />
    </sec:headers>
</sec:http>

但问题是,

  1. 我需要从会话检查(超时和并发)中排除一些页面,例如 login change_password

  2. 如果我有一个可供登录用户或未登录用户访问的页面。但我只需要在用户登录时进行会话超时和并发检查。

我应该如何实现这个?

非常感谢。

【问题讨论】:

    标签: java spring spring-mvc spring-security spring-boot


    【解决方案1】:

    Session 的创建与 spring、security 等无关。 session 背后的机制是全自动的,你不需要改变或做任何事情。您可以使用普通的 javax.servlet API 并完成您需要的工作,而不是深入研究 Spring 细节。

    您需要做的很简单:借助会话绑定的自定义对象来区分匿名会话和经过身份验证的用户会话。

    Session 可以保存对象,服务器维护绑定到特定会话的对象(通常通过会话 cookie jsessionid)。此类对象的示例如下代码:

    public class SessionUsr implements java.io.Serializable {
        private static final long serialVersionUID = 6034793247940424913L;
    
        // do not make this fields nonfinal. This object is in session as serialized stream and
        // setting the fields does not refresh the object in session. You must replace it
    
        protected final boolean isAnonymous;
        protected final String userName; 
    
        public SessionUsr(boolean isAnonymous, String userName) {
            super();
            this.isAnonymous = isAnonymous;
            this.userName = userName;
        }
        public boolean isAnonymous() {
            return this.isAnonymous;
        }
        public String getUserName() {
            return this.userName;
        }
    
        @Override
        public String toString() {
            return "SessionUsr [isAnonymous=" + this.isAnonymous + ", userName=" + this.userName + "]";
        }
        @Override
        public int hashCode() {
            int result = 17;
            result = 31 * result + (null == getUserName() ? 0 : getUserName().hashCode());
            result = 31 * result + (!isAnonymous() ? 0 : 1);
            return result;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof SessionUsr)) {
                return false;
            }
    
            final SessionUsr comparation= (SessionUsr) obj;
            if (comparation.getUserName().equals(this.userName))
                return true;
    
            return false;
        }
    
    }
    

    用于跟踪事件的会话监听器

    import java.util.logging.Logger;
    import javax.servlet.http.HttpSessionEvent;
    import javax.servlet.http.HttpSessionListener;
    
    public class SessionListener implements HttpSessionListener {
    
        public static final Logger log = Logger.getLogger(SessionListener.class.getCanonicalName());
    
        /**
         * Method is called after new client connects   
         * @see javax.servlet.http.HttpSessionListener#sessionCreated(javax.servlet.http.HttpSessionEvent)
         */
        @Override
        public void sessionCreated(HttpSessionEvent arg0) {
            log.info("SESSION CREATED with id " + arg0.getSession().getId());
            final SessionUsr authenticatedUser  = new SessionUsr(false, "anonymous");
            arg0.getSession().setAttribute("_USR", authenticatedUser);
        }
    
        /** 
         * Invalidation or timeout definined in web.xml (session-timeout).
         * @see javax.servlet.http.HttpSessionListener#sessionDestroyed(javax.servlet.http.HttpSessionEvent)
         */
        @Override
        public void sessionDestroyed(HttpSessionEvent arg0) {
            log.info("SESSION DESTROYED, INVALIDATED " + arg0.getSession().getId());
        }
    
    }
    

    在你的授权和认证代码中,用户成功验证后,你将会话属性替换为代码

    final SessionUsr authorizated = new SessionUsr("John Kennedy", true);
    getSession().setAttribute("_USR", authorizated);
    

    你可以在任何地方检索这个用户:

    final SessionUsr authenticatedUser  = (SessionUsr) getSession().getAttribute("_USR");
    

    【讨论】:

      【解决方案2】:

      更新:我在我的一个 Spring Security 登录页面上测试了我原来的 session="false" 答案,但它不起作用。请参阅下文以获得更好的解决方案。


      原答案

      将 添加到 JSP 文件的顶部应该会阻止会话启动,但这与 Spring Security 无关。

      下面的 URL 中有一些非常简单的 Oracle 文档:

      JSP 默认会话请求

      一般来说,servlet 不会通过以下方式请求 HTTP 会话 默认。但是,JSP 页面实现类确实请求 HTTP 默认会话。您可以通过设置会话来覆盖它 JSP页面指令中的参数为false,如下:

      https://docs.oracle.com/cd/A87860_01/doc/java.817/a83726/basics3.htm#1007356


      更新答案:问题可能与启用 CSRF 的 Spring Security 有关。它在 4.0 版中默认启用,这是一件好事,而不是您想要禁用的东西。在早期版本中,可能需要手动操作。

      CRSF 令牌需要一个会话,因此您需要一个解决方案,以便仅在登录过程中排除 CSRF 测试。

      Spring Security Reference 中有关于"Relaxing CSRF" 的讨论。他们特别提到了 SockJS,但原理是通用的。他们为 JavaConfig 建议如下行:

      http.csrf().ignoringAntMatchers("/login")
      

      被接受的answer by P.Peter for the SO question "CSRF token expires during login" 有一个类似的解决方案,需要更多的努力。

      您需要添加一个将实现 Spring 的 RequestMatcher 类并覆盖它的 matches() 方法的类:

      class CsrfSecurityRequestMatcher implements RequestMatcher {
          @Override
          public boolean matches(HttpServletRequest request) {
              return !request.getServletPath().equals("/login");
          }
      }
      

      如果您需要更复杂的匹配逻辑,您可以在您的自定义 RequestMatcher 类中添加 Pattern 和 RegexRequestMatcher 字段,并在您的 matches() 方法中使用它们。

      然后,您需要在 Spring Security 配置中添加对新类的引用。在 XML 中会是这样的:

      <http>
          <csrf request-matcher-ref="csrfSecurityRequestMatcher"/>
      </http>
      

      在 JavaConfig 中会是这样的:

      http.csrf().requireCsrfProtectionMatcher(new CsrfSecurityRequestMatcher());
      

      我不确定在登录页面上禁用 CSRF 测试可能会产生什么安全后果,因此您可能需要对此进行调查。

      如果您认为为您的登录页面禁用 CSRF 是不可接受的,那么您可以使用 AJAX keepalive 解决方案来防止您的登录页面过期,但这对我来说有点骇人听闻。

      【讨论】:

        猜你喜欢
        • 2011-06-06
        • 2022-10-19
        • 2014-01-19
        • 2011-03-24
        • 1970-01-01
        • 2016-08-16
        • 2016-07-17
        • 2014-09-22
        相关资源
        最近更新 更多