如何使用 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>
<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");
- 在 HTML 中设置元刷新标签 (不适用于 AJAX 请求)
<meta http-equiv="refresh" content="60; url=login.jsp">
- 正在配置 Activity 检查器
通过重复 AJAX 请求使 session 保持活动状态。跟踪空闲时间并在超时后发出注销请求。
毫无疑问,这是一篇逻辑简单的好文章。但我只想写下我的观察结果。
- 性能影响 如果每分钟发出 2 个请求以保持 session 活动和 50k 活跃用户。每分钟 10 万个请求。
- 标签间通信 如果打开两个标签,一个标签正在接收 activity 但另一个标签未接收 activity,该标签会触发注销请求并且即使 activity 出现在其他选项卡中,也会使 session 无效。 (但可以处理)
- 强制注销方式是client支配server失效session.
我正在尝试使用 JavaScript 和 Spring Boot 为同一问题找出一些合适的解决方案,这对我有用,就像使用方法 expiredUrl("/login?expired" ) 不会将我重定向到带有会话过期消息的登录页面。
- 创建一个 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
}
}
30 分钟后,您将被重定向到 /login?expired 并且将使用以下 HTML 代码:
<div th:style="${param.expired} ? 'width: 100%' : 'width: 0'"
th:text="#{page.login.form.timeout}"
</div>
我有一个 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>
<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");
- 在 HTML 中设置元刷新标签 (不适用于 AJAX 请求)
<meta http-equiv="refresh" content="60; url=login.jsp">
- 正在配置 Activity 检查器
通过重复 AJAX 请求使 session 保持活动状态。跟踪空闲时间并在超时后发出注销请求。
毫无疑问,这是一篇逻辑简单的好文章。但我只想写下我的观察结果。- 性能影响 如果每分钟发出 2 个请求以保持 session 活动和 50k 活跃用户。每分钟 10 万个请求。
- 标签间通信 如果打开两个标签,一个标签正在接收 activity 但另一个标签未接收 activity,该标签会触发注销请求并且即使 activity 出现在其他选项卡中,也会使 session 无效。 (但可以处理)
- 强制注销方式是client支配server失效session.
我正在尝试使用 JavaScript 和 Spring Boot 为同一问题找出一些合适的解决方案,这对我有用,就像使用方法 expiredUrl("/login?expired" ) 不会将我重定向到带有会话过期消息的登录页面。
- 创建一个 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
}
}
30 分钟后,您将被重定向到 /login?expired 并且将使用以下 HTML 代码:
<div th:style="${param.expired} ? 'width: 100%' : 'width: 0'" th:text="#{page.login.form.timeout}" </div>