How to log out automatically with Spring Security

2020-02-01 01:53发布

问题:

I have a spring web application and I did user authentication using Spring security.

Everything works well. Log in and Log out works perfect!

Now, I want to implement in order to log out automatically. For example, if user has a window opened for about 30 minutes and do nothing (Sessions expired for instance) system should log out automatically. How can I implement this?

It might be implemented by client side (I send requests every 1 minutes and check if session is ended). But can't I do this automatically from Spring?

I have this config:

<http auto-config="true" use-expressions="true">


        <intercept-url pattern="/admin**" />
        <access-denied-handler error-page="/403" />

        <form-login login-page="/login" 
            default-target-url="/admin"
            authentication-failure-url="/login?error" 
            username-parameter="NAME"
            password-parameter="PASSWORD"  />

        <logout invalidate-session="true" 
             logout-success-url="/login?logout"/>

    </http>

and in web.xml

<session-config>
  <session-timeout>1</session-timeout>
</session-config>

after 1 minute, I see that session was destroyed. kill the session after 1 minute. but page was not redirected to /login?logout

回答1:

How about to use security configuration.?? I hope below config: will work.

applicationContext.xml

 --namespace-> xmlns:security="http://www.springframework.org/schema/security"

        <security:logout invalidate-session="true"
                        success-handler-ref="Logout"
                        logout-url="/logout.html" />
        </security:http>

web.xml

 <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>

And them, you need to write your own because success-handler-ref="Logout" is custom handler for logout:
Logout @Component

public class Logout extends SimpleUrlLogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {

        if (authentication != null) {
            // do something 
        }

        setDefaultTargetUrl("/login");
        super.onLogoutSuccess(request, response, authentication);       
    }
}


回答2:

You can use a global timeout value by putting this in your web.xml:

<session-config>
  <session-timeout>30</session-timeout>
</session-config>


回答3:

Here is the tutorial I used. There is java script and server side code. The server will calculate when the session will expire and send it back as a cookie. Then the java script will check every 10 seconds if it is expired, if so it will window.close(). http://www.javaworld.com/article/2073234/tracking-session-expiration-in-browser.html

Here is how I implemented it

SessionTimeout.js

/**
 * Monitor the session timeout cookie from Apache and log the user out when expired
 */
"use strict";

var jQuery = require("jquery").noConflict();
var jsCookie = require("js-cookie");

module.exports.registerListener = function() {
    calcOffset();
    checkSession();
};

/**
 * We can't assume the server time and client time are the same
 * so lets calcuate the difference
 */
function calcOffset() {
    var serverTime = jsCookie.get('serverTime');
    serverTime = serverTime==null ? null : Math.abs(serverTime);
    var clientTimeOffset = (new Date()).getTime() - serverTime;
    jsCookie.set('clientTimeOffset', clientTimeOffset);
}

/**
 * Check the sessionExpiry cookie and see if we should send the user to /
 */
function checkSession() {
    var sessionExpiry = Math.abs(jsCookie.get('sessionExpiry'));
    var timeOffset = Math.abs(jsCookie.get('clientTimeOffset'));
    var localTime = (new Date()).getTime();
    if(!sessionExpiry){
        window.console.log("Unknown session sessionExpiry");
        return;
    }
    if (localTime - timeOffset > (sessionExpiry+15000)) { // 15 extra seconds to make sure
        window.location = "/login";
        jsCookie.remove('sessionExpiry');
    } else {
        setTimeout('checkSession()', 10000);
    }
    window.console.log("Session expires in " + ((sessionExpiry+15000) - localTime - timeOffset) + "ms");
}

window.checkSession = checkSession; //Used for recalling via setTimeout

SessionTimeoutCookieFilter.java

public class SessionTimeoutCookieFilter implements Filter {

    private static final Logger LOG = LoggerFactory.getLogger(SessionTimeoutCookieFilter.class);

    @Override
    public void init(FilterConfig config) throws ServletException {
        LOG.info("Initialization SessionTimeoutCookieFilter");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse httpResp = (HttpServletResponse) resp;
        HttpServletRequest httpReq = (HttpServletRequest) req;

        long currTime = System.currentTimeMillis();
        String expiryTime = Long.toString(currTime + httpReq.getSession().getMaxInactiveInterval() * 1000);
        Cookie cookie = new Cookie("serverTime", Long.toString(currTime));
        cookie.setPath("/");
        httpResp.addCookie(cookie);
        if (httpReq.getRemoteUser() != null) {
            cookie = new Cookie("sessionExpiry", expiryTime);
        }
        cookie.setPath("/");
        httpResp.addCookie(cookie);

        filterChain.doFilter(req, resp);
    }

Add the filter

public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
...
    @Override
    protected Filter[] getServletFilters() {
        return new Filter[]{new SessionTimeoutCookieFilter()};
    }
}


回答4:

To redirect to login page after a session expiration add the "invalid-session-url" tag to your "session-management" bean in security context:

<session-management invalid-session-url="/error-login">
    ....
</session-management>

In my case, I'm redirecting to error-login page where an error message is displayed and I can re-login. Mind you, on session expiration, you're not going to be automatically redirected. You need to click on any part of your page and that will trigger the redirection.



回答5:

Either it may be spring-security, spring-mvc or servlet, auto logout is not possible without perfect client side logic.
Considering application will have both type of request

  • AJAX and
  • form submission/page reload

Auto logout needs very calculated logic. Presenting my autologout functionality implementation with following

Advantages.


1. No extra call/request is used to achieve this. considering performance impact if more than 10k active users and extra calls to achieve auto logout.
2. One line configuration using tag.
3. Works flawlessly even if user opens multiple tab or multiple window.
4. It intimates you before 30 seconds of session invalidation, so if you have filled form and not submitted, you can keep session alive(extend session by one click). So user less likely to loose unsaved data.

Usage


1. Include auto logout script in required JSP pages as given below.

    ....
    </body>
    <jsp:include page="../template/autologout-script.jsp"></jsp:include>
</html>

2. Create a JSP page, autologout-script.jsp and add below code. Note: No editing/configuring is required

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<script>
$(document).ready(function()
{
    var timeOutTimeInSeconds = ${ timeOutTimeInSeconds }; 
    var showTimerTimeInSeconds= ${ showTimerTimeInSeconds };

    var sessionCheckIntervalId = setInterval(redirectToLoginPage, timeOutTimeInSeconds * 1000);
    var timerDisplayIntervalId = setInterval(showTimer, (timeOutTimeInSeconds - showTimerTimeInSeconds) * 1000);
    var badgeTimerId;
    window.localStorage.setItem("AjaxRequestFired", new Date());

    function redirectToLoginPage(){
        //location.href =  '<c:url value="/" />'+'${loginPageUrl}';
        window.location.reload();
    }

    $(document).ajaxComplete(function () {
        resetTimer();
    });

    $(window).bind('storage', function (e) {
         if(e.originalEvent.key == "AjaxRequestFired"){
             console.log("Request sent from another tab, hence resetting timer")
             resetTimer();
         }
    });

    function resetTimer()
    {
        showTimerTimeInSeconds= ${ showTimerTimeInSeconds };

        console.log("timeOutTimeInSeconds : "+timeOutTimeInSeconds)
        window.localStorage.setItem("AjaxRequestFired", new Date());

        window.clearInterval(sessionCheckIntervalId);
        sessionCheckIntervalId = setInterval(redirectToLoginPage, timeOutTimeInSeconds * 1000);

        window.clearInterval(timerDisplayIntervalId);
        timerDisplayIntervalId = setInterval(showTimer, (timeOutTimeInSeconds - showTimerTimeInSeconds) * 1000);

        hideTimer();
    }

    function showTimer()
    {
        $('#sessionTimeRemaining').show();
        $('#sessionTimeRemainingBadge').html(showTimerTimeInSeconds--);
        window.clearInterval(timerDisplayIntervalId);
        badgeTimerId = setInterval(function(){
            $('#sessionTimeRemainingBadge').html(showTimerTimeInSeconds--);
        }, 1000);
    }

    function hideTimer()
    {
        window.clearInterval(badgeTimerId);
        $('#sessionTimeRemaining').hide();
    }
});
</script>

3. Configure session attributes to configuring timeout setting Note: Configure this after session creation. You can implement HttpSessionListener sessionCreated method and set the following configuration as per your requirement.

session.setMaxInactiveInterval(300);

session.setAttribute("timeOutTimeInSeconds", 300);
session.setAttribute("showTimerTimeInSeconds", 30);

4. Add below html for displaying timer.
Note: it can be moved to autologout-script template page if you are good at CSS. Hence you can avoid to add this in each and every page.
Include bootstrap or add your custom css.

<span class="badge badge-primary" title="click to keep session alive" id="sessionTimeRemaining" 
    onclick="ajaxSessionRefresh()" style="display:none;">
    <i class="badge badge-danger" id="sessionTimeRemainingBadge" style="float:left">30</i>
     &nbsp; 
     <small>Refresh</small>
     <i class="glyphicon glyphicon-refresh"></i>
</span>

That is all about a simple auto logout implementation. You can download working example from my github repository
Autologout using simple servlet example
Autologout using spring-security java configuration example
Autologout using spring-security xml configuration example

Logic Explained


Case 1: On Page load
Here logic is simple, on page load set timer of interval equlas to maxInactiveInterval. after timeout redirect to login page.
Case 2: Keep track AJAX calls
Now considering AJAX requests, you can use .ajaxStart() or .ajaxComplete() callbacks of jquery so that if any ajax request is fired you can reset the interval.
Case 3: Tracking multi tab/window activity
Intertab communication is done to synchronize state of each tab. Used localStorage on change event.

Limitations/Improvements required
1. If maximum allowed session is one, if session is taken from another system, AJAX request will fail. It needs to be handled to redirect to login page.
2. Use ajaxStart() instead of ajaxComplete() to have exact sync of idleTime values between server and browser.

Requirements
1. Jquery

Alternatives to current implementation compared


1. Setting Refresh header in http response. (Not works for AJAX requests)

response.setHeader("Refresh", "60; URL=login.jsp");
  1. Setting meta refresh tag in HTML (Not works for AJAX requests)
<meta http-equiv="refresh" content="60; url=login.jsp">
  1. Configuring Activity checker Keeps session alive by repeated AJAX request. Tracks idle time and makes logout request after timeout.
    No doubt it is a good one with simple logic. But i want to just ink my observations.
    • Performance impact if 2 requests are made per minute to keep session alive and 50k active users. 100k requests per minute.
    • Intertab communication If two tabs are open, one tab is receiving activity but other tab is not receiving activity, that tab fires logout request and invalidate session even though activity is present in other tab. (But can be handled)
    • Force logout approach It is a client is dominated over server to invalidate session.