Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to exclude some page from session management (timeout/concurrency check) in Spring Security?

I added this code in spring-security.xml to enable session timeout check and concurrency check.

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

but the question is,

  1. I need to exclude some page like login change_password from session check (timeout and concurrency).

  2. If I have a page that accessible for both logged user or un-logged user. But I need to do session timeout and concurrency check only when the user logged in.

How should I implement this?

Thanks very much.

like image 819
user930836 Avatar asked May 25 '16 12:05

user930836


People also ask

How do I set session timeout in Spring Security?

Spring Security Session Timeout In the case of Tomcat we can set the session timeout by configuring the maxInactiveInterval attribute on the manager element in server. xml or using the session-timeout element in web. xml. Note that the first option will affect every app that's deployed to the Tomcat instance.

How does Spring Security concurrent session control work?

Spring security provides a mechanism to control and limit the maximum number of single-user open sessions. This mechanism prevents users from exceeding the number of allowed simultaneous connections. For example, Netflix limits the number of screens you can watch at the same time according to your subscription plan.

Does Spring Security provide protection against session fixation?

Spring security will disable the session fixation protection. When the user successfully authenticated, a new session will be created, and it copies no attributes from the old session. This is done using the new Servlet containers (Servlet 3.1 and newer).


2 Answers

Update: I tested my original session="false" answer on one of my Spring Security login pages and it didn't work. See further below for a better solution.


Original answer:

Adding <%@page session="false"%> to the top of the JSP file should prevent a session from being started but this is unrelated to Spring Security.

There is some very minimal Oracle documentation at the URL below that says:

JSP Default Session Requests

Generally speaking, servlets do not request an HTTP session by default. However, JSP page implementation classes do request an HTTP session by default. You can override this by setting the session parameter to false in a JSP page directive, as follows:

<%@ page ... session="false" %

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


Updated answer: The problem may be related to Spring Security having CSRF enabled. It is enabled by default in version 4.0 which is a good thing and not something you want to disable. In earlier versions it may have needed to be manually.

CRSF tokens require a session so you need a solution for excluding the CSRF testing on just the login process.

There is discussion of "Relaxing CSRF" in the Spring Security Reference. They specifically mention SockJS but the principle is universal. They suggest something like the line below for JavaConfig:

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

The accepted answer by P.Peter for the SO question "CSRF token expires during login" has a similar solution that requires a little more effort.

You need to add a class that will implement Spring's RequestMatcher class and override it's matches() method:

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

If you need more complex matching logic you can add Pattern and RegexRequestMatcher fields in your custom RequestMatcher class and use them in your matches() method.

Then you will need to add a reference to the new class in your Spring Security configuration. In XML it would be something like this:

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

In JavaConfig it would be something like this:

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

I'm unsure what security ramifications there may be to disabling CSRF testing on the login page so you may want to look into that.

If you decide that disabling CSRF for your login page is unacceptable then you can use an AJAX keepalive solution that prevents your login page from expiring but that is a bit hackish for my taste.

like image 145
Night Owl Avatar answered Oct 16 '22 21:10

Night Owl


Session is created regardless of spring, security, etc. Mechanism behind session is fully automatic, you do not need to change or do anything. Instead of digging into Spring details, you can use plain javax.servlet API and accomplish what you need.

And what you need to do is simple: differentiate between anonymous session and authenticated user sessions with the help of session-bound custom object.

Session can hold objects, and server maintains objects bound to specific session (normally via session cookie jsessionid). Example of such a object is following code:

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

}

Session listener to track events

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());
    }

}

In your authorization and authentication code, after user is successfully validated, you replace session attribute with the code

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

And you can retrieve this user anywhere with :

final SessionUsr authenticatedUser  = (SessionUsr) getSession().getAttribute("_USR");
like image 33
Mitja Gustin Avatar answered Oct 16 '22 20:10

Mitja Gustin