如何使用 Spring 安全性自动注销

How to log out automatically with Spring Security

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

一切正常。登录和注销完美无缺!

现在,我想实现自动注销。例如,如果用户打开 window 大约 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

您可以通过将其放入 web.xml:

来使用全局超时值
<session-config>
  <session-timeout>30</session-timeout>
</session-config>

使用安全配置怎么样??我希望下面的配置:会起作用。

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

这是我使用的教程。有 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()};
    }
}

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

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

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

可能是 spring-security、spring-mvc 或 servlet,如果没有完善的客户端逻辑,自动注销是不可能的。
考虑应用程序将有两种类型的请求

  • AJAX 和
  • 表格submission/page重新加载

自动注销需要非常计算的逻辑。通过以下

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

优点。


1.没有额外的call/request来实现这一点。考虑超过 10k 活跃用户和额外调用以实现自动注销的性能影响。
2.使用标签一行配置。
3. 即使用户打开多个选项卡或多个 window.
也能完美运行 4、在session失效前30秒提示,所以如果你已经填完表没有提交,可以保持session存活(一键延长session)。因此用户不太可能丢失未保存的数据。

用法


1.在要求的 JSP 页面中包含自动注销脚本,如下所示。

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

2。创建一个 JSP 页面,autologout-script.jsp 并添加以下代码。 注:不需要editing/configuring

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

session.setMaxInactiveInterval(300);

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

4.在下面添加 html 用于显示计时器。
注意:如果你擅长CSS,可以移动到autologout-script模板页面。因此,您可以避免在每个页面中添加它。
包括 bootstrap 或添加您的自定义 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>

这就是关于简单的自动注销实现的全部内容。 您可以从我的 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:跟踪多个 tab/window activity
进行标签间通信以同步每个标签的状态。在更改事件中使用 localStorage。

Limitations/Improvements 需要
1.如果maximum allowed session是一个,如果session取自另一个系统,AJAX请求将失败。需要处理重定向到登录页面。
2. 使用 ajaxStart() 而不是 ajaxComplete() 以在服务器和浏览器之间精确同步 idleTime 值。

要求
1. Jquery

当前实现的替代方案比较


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

response.setHeader("Refresh", "60; URL=login.jsp");
  1. 在 HTML 中设置元刷新标签 (不适用于 AJAX 请求)
<meta http-equiv="refresh" content="60; url=login.jsp">
  1. 正在配置 Activity 检查器 通过重复 AJAX 请求使 session 保持活动状态。跟踪空闲时间并在超时后发出注销请求。
    毫无疑问,这是一篇逻辑简单的好文章。但我只想写下我的观察结果。
    • 性能影响 如果每分钟发出 2 个请求以保持 session 活动和 50k 活跃用户。每分钟 10 万个请求。
    • 标签间通信 如果打开两个标签,一个标签正在接收 activity 但另一个标签未接收 activity,该标签会触发注销请求并且即使 activity 出现在其他选项卡中,也会使 session 无效。 (但可以处理)
    • 强制注销方式是client支配server失效session.

我正在尝试使用 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>