如何使用Spring Security自动注销

时间:2015-01-05 08:18:41

标签: java spring spring-mvc spring-security

我有一个spring web应用程序,我使用Spring安全性进行了用户身份验证。

一切都运作良好。登录并退出工作完美!

现在,我想实现以便自动注销。例如,如果用户打开窗口大约30分钟并且什么也不做(例如,会话已过期)系统应该自动注销。我该如何实现呢?

它可能由客户端实现(我每1分钟发送一次请求并检查会话是否结束)。但我不能自动从Spring那里做到这一点吗?

我有这个配置:

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

并在web.xml中

<session-config>
  <session-timeout>1</session-timeout>
</session-config>
1分钟后,我看到会话被破坏了。 1分钟后杀死会话。但页面未重定向到/ login?logout

6 个答案:

答案 0 :(得分:4)

如何使用安全配置。我希望下面的配置:会工作。

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

他们,你需要编写自己的,因为success-handler-ref =“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);       
    }
}

答案 1 :(得分:1)

您可以通过将其放在web.xml中来使用全局超时值:

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

答案 2 :(得分:0)

这是我使用的教程。有java脚本和服务器端代码。服务器将计算会话何时到期并将其作为cookie发回。然后java脚本将每10秒检查一次是否过期,如果是的话,它将是window.close()。 http://www.javaworld.com/article/2073234/tracking-session-expiration-in-browser.html

以下是我实施它的方式

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

添加过滤器

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

答案 3 :(得分:0)

要在会话到期后重定向到登录页面,请在安全上下文中将“invalid-session-url”标记添加到“session-management”bean:

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

在我的情况下,我正在重定向到错误登录页面,其中显示错误消息,我可以重新登录。请注意,在会话到期时,您不会被自动重定向。您需要单击页面的任何部分,这将触发重定向。

答案 4 :(得分:0)

可能是spring-security,spring-mvc或servlet,没有完善的客户端逻辑就无法自动注销。
考虑到应用程序将同时具有两种类型的请求

  • AJAX和
  • 提交表单/重新加载页面

自动注销需要经过精心计算的逻辑。通过以下

展示我的自动注销功能实现

优势。


1.无需额外的呼叫/请求即可实现此目的。如果活动用户超过1万,并且要实现自动注销的额外呼叫,则要考虑对性能的影响。
2.使用标签进行一线配置。
3.即使用户打开多个选项卡或多个窗口也可以正常工作。
4.它会在会话无效30秒之前提示您,因此,如果您填写了表单但未提交,则可以使会话保持活动状态(一键扩展会话)。因此,用户不太可能丢失未保存的数据。

用法


1。如下所示,在所需的JSP页面中包括自动注销脚本。

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

2。创建一个JSP页面autologout-script.jsp并添加以下代码。 注意:无需编辑/配置

<%@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。配置会话属性以配置超时设置 注意:在会话创建后配置此。您可以实现HttpSessionListener sessionCreated方法,并根据需要设置以下配置。

session.setMaxInactiveInterval(300);

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

4。在html下方添加用于显示计时器。
注意:如果您擅长CSS,可以将其移至autologout-script模板页面。因此,您可以避免在每个页面中添加它。
包括引导程序或添加自定义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>

enter image description here

这就是简单的自动注销实现。 您可以从我的github存储库下载工作示例
Autologout using simple servlet example
Autologout using spring-security java configuration example
Autologout using spring-security xml configuration example

逻辑解释


案例1:页面加载
这里的逻辑很简单,页面加载设置计时器的间隔等于maxInactiveInterval。超时后重定向到登录页面。
案例2:跟踪AJAX调用
现在考虑AJAX请求,您可以使用jquery的.ajaxStart()或.ajaxComplete()回调,这样,如果触发了任何ajax请求,您可以重置时间间隔。
案例3:跟踪多个标签页/窗口活动
进行表间通信以同步每个选项卡的状态。在发生更改事件时使用了localStorage。

所需的限制/改进
1.如果最大允许会话数为1,则如果会话是从另一个系统进行的,则AJAX请求将失败。需要处理它以重定向到登录页面。
2.使用ajaxStart()而不是ajaxComplete()在服务器和浏览器之间实现idleTime值的精确同步。

要求
1. jQuery

当前实施方案的替代方案


1.在http响应中设置刷新标题(不适用于AJAX请求)

response.setHeader("Refresh", "60; URL=login.jsp");
  1. 在HTML 中设置元刷新标记(不适用于AJAX请求)
<meta http-equiv="refresh" content="60; url=login.jsp">
  1. 配置活动检查器 通过重复的AJAX请求使会话保持活动状态。跟踪空闲时间并在超时后发出注销请求。
    毫无疑问,这是一个简单逻辑的好人。但我只想提出自己的看法。
    • 对性能的影响,如果每分钟发出2个请求以保持会话活动和5万活跃用户。每分钟10万个请求。
    • 选项卡间通信如果两个选项卡都打开,则一个选项卡正在接收活动,而另一个选项卡未接收活动,即使其他选项卡中存在活动,该选项卡也会触发注销请求并使会话无效。 (但可以处理)
    • 强制注销方法。这是客户端主导着服务器,使会话无效。

答案 5 :(得分:0)

我试图使用 JavaScript 和 Spring Boot 为同一问题找出一些合适的解决方案,这对我有用,因为仅使用方法 expiredUrl("/login?expired") 不会将我重定向到带有会话的登录页面过期消息。

  1. 创建一个 JavaScript 函数,该函数将检查用户是否处于活动状态,并在 onload 主体中调用它,如下所示:

HTML 文件:

<body onload="idleLogout();">
</body>

JavaScript

var t;
function idleLogout() {
    window.onload = resetTimer;
    window.onmousemove = resetTimer;
    window.onmousedown = resetTimer; // catches touchscreen presses
    window.onclick = resetTimer;     // catches touchpad clicks
    window.onscroll = resetTimer;    // catches scrolling with arrow keys
    window.onkeypress = resetTimer;

    function logout() {
        window.location = '/login?expired';
    }

    function resetTimer() {
        clearTimeout(t);
        t = setTimeout(logout, 1800000);  // time is in milliseconds
    }
}
  1. 30 分钟后,您将被重定向到 /login?expired 并使用以下 HTML 代码:

    <div th:style="${param.expired} ? 'width: 100%' : 'width: 0'" 
        th:text="#{page.login.form.timeout}"
    </div>