如何从Spring Security中的会话管理(超时/并发检查)中排除某些页面?

时间:2016-05-25 12:14:00

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

我在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. 如果我有一个可供登录用户或未登录用户访问的页面。但是我只需要在用户登录时进行会话超时和并发检查。

  3. 我该如何实现?

    非常感谢。

2 个答案:

答案 0 :(得分:4)

更新:我在我的一个Spring Security登录页面上测试了我的原始session="false"答案,但它无效。请参阅下面的更多解决方案。

原始回答

添加&lt;%@ page session =“false”%&gt;在JSP文件的顶部应该阻止会话启动,但这与Spring Security无关。

以下网址上有一些非常简单的Oracle文档:

  

JSP默认会话请求

     

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

     

&lt;%@ page ... session =“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解决方案来阻止您的登录页面过期,但这有点让我感到沮丧。

答案 1 :(得分:3)

无论弹簧,安全性如何,都会创建会话。会话后面的机制是全自动的,您无需更改或执行任何操作。您可以使用普通的javax.servlet API来完成所需的工作,而不是深入了解Spring细节。

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

会话可以保存对象,服务器维护绑定到特定会话的对象(通常通过会话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");