【Session】关于在用户退出登录的时候清除相关session信息

本文探讨了在用户退出系统时,如何确保session信息的安全清除,防止信息泄露。介绍了两种清除session的方法,以及利用Redis和ThreadLocal来进一步增强安全性,确保每次请求都能验证用户登录状态。

在默认情况下,session对象在关闭浏览器后并不是立刻被销毁,因此,为了考虑系统的安全性,在用户退出时,需要即刻清除session对象,防止他人盗用session对象中的信息。清除session信息主要有两种方式,一种是遍历的清除session中的所有属性,另一种是直接设置session失效,两种方式都要在退出的接口中实现,附代码:

		   HttpSession session = request.getSession();
           /* 清除session的方案一 */
           Enumeration attributeNames = request.getSession().getAttributeNames();
           while (attributeNames.hasMoreElements()) {
               session.removeAttribute(attributeNames.nextElement().toString());
           }
           /* 清除session的方案二 */
           session.invalidate();

到这里好像结束了,因为session已经被正确的清除。但是设想一种场景,当用户点击退出的时候,这里记录请求为request1,调用退出接口并清除了相关的session,但是如果用户此刻复制了目标路径,新建了一个窗口,回车进入,此时其实是另一个请求request2了,就是说上一步清除的session信息其实是request1,而request2请求将能正常登入系统。那么应该怎么做呢?这里有两种方案,一是利用Redis,二是采用ThreadLocal

Redis解决方案

思路:在Redis为每个用户登录的用户名维护一条记录,一定要设置过期时间,一般一个小时。用户每次登录的时候,通过通过拦截器从缓存中查询用户名,如果没有命中,则认为用户未登录过或者登录信息已经失效,引导用户重新登录,用户正确登录之后通过setnx()方法将用户名插入缓存。用户每次点击退出时,清除本次请求的session信息,并同时将缓存中记录用户名的key同步的删除。

Tips:
setnx()也被经常用作分布式锁,具体可参看博客:
【分布式锁】并发场景下的数据插入

接下来附上利用Redis维护、删除用户登录信息的部分代码:

	/* 缓存里记录用户名的key */
	private static final String USERNAME = "username_";
    public ModelAndView login(HttpServletRequest request,
                              @RequestParam(value = "username", required = true) String username,
                              @RequestParam(value = "password", required = true) String password) {
        ……
        String key = USERNAME + username;
        /* 把用户信息设置进缓存并设置过期时间 */
        redisCacheService.setnx(key, username);
        redisCacheService.expire(key, 60 * 60);
        ……
    }

    public ModelAndView logout(HttpServletRequest request, @ApiParam("用户名") @RequestParam(value = "username", required = true) String username) {
        ……
        HttpSession session = request.getSession();
        session.invalidate();
        /* 清除本地缓存 */
        String key = USERNAME + username;
        redisCacheService.remove(key);
        ……
    }
    

然后在登录拦截器中设置每次从request中获取到用户信息之后,再和Redis中的去比较,如果缓存中没有命中,则视本次请求中的用户登录信息已经过期,需要跳转到登录页面。

import org.apache.commons.lang3.StringUtils;
public class LoginInterceptor implements HandlerInterceptor {
	/* 缓存里记录用户名的key */
    private static final String USERNAME = "username_";

	 @Override
    public boolean preHandle(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Object o) throws Exception {
        servletRequest.setCharacterEncoding("UTF-8");
        servletResponse.setContentType("application/json;charset=utf-8");
        ServletOutputStream outputStream = servletResponse.getOutputStream();

        HttpSession session = servletRequest.getSession();
        Object userNameObject = session.getAttribute("userName");
        String userName = userNameObject != null ? userNameObject.toString() : null;
        if (userName == null) {
            servletResponse.setStatus(HttpServletResponse.SC_OK);
            String res="登录失败,未获取到用户登录信息~";
            outputStream.write(JsonUtils.toJson(res).getBytes("UTF-8"));
            return false;
        }
        /* 和本地缓存中的比较 */
        String key = USERNAME + userName;
        String value = (String) redisCacheService.get(key, String.class, new CacheCallable() {
            @Override
            public Object getCacheValue() throws Exception {
                return null;
            }
        });
        /* 若未命中缓存,说明用户登录信息已失效,引导用户重新登录 */
        if (StringUtils.isBlank(value)) {
            return false;
        }
        return true;
    }
}

ThreadLocal解决方案

待补充。

参考阅读
如何解决每次向后台发起请求时判断用户是否处于登录状态?
<think>我们正在设计一个退出用户登录页面,并需要销毁session。根据引用材料,销毁session主要有两种方式:一种是遍历清除session中的所有属性,另一种是直接设置session失效。同时,为了确保安全,我们还需要清除浏览器的Cookie(特别是JSESSIONID)。此外,退出后应该重定向到登录页面,并防止用户通过浏览器后退按钮返回隐私页面。 设计步骤: 1. 前端设计:创建一个退出按钮,点击后触发退出操作。 2. 确认机制:为了防止误操作,可以在前端添加一个确认对话框。 3. 后端处理:当用户确认退出后,向后端发送退出请求,后端销毁session清除相关的Cookie。 4. 重定向:退出成功后,重定向到登录页面,并且使用替换历史记录的方式,防止后退。 参考引用[2]中的代码,我们可以这样实现后端退出逻辑(Java Servlet示例): - 获取当前session(如果存在),然后调用invalidate()方法销毁session。 - 创建一个同名的JSESSIONID的Cookie,设置其最大存活时间为0(立即失效),并添加到响应中,这样浏览器就会删除该Cookie。 - 重定向到登录页面。 另外,为了防止用户退出后通过浏览器后退按钮返回,我们可以在前端使用`window.location.replace`方法跳转,这样就不会在历史记录中留下当前页面。 前端退出页面设计示例(HTML+JavaScript): - 页面中有一个退出登录”按钮。 - 点击按钮时,弹出确认对话框。 - 确认后,发送请求到后端的退出接口,然后前端清除本地存储(如果有token等)并跳转到登录页。 下面是一个完整的设计方案: 一、前端页面(logout.html 或 在用户页面中的退出按钮): ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>退出登录</title> </head> <body> <h1>您确定要退出吗?</h1> <button id="logoutButton">退出登录</button> <script> document.getElementById('logoutButton').addEventListener('click', function() { if (confirm('确定要退出登录吗?')) { // 向后端发送退出请求 fetch('/logout', { method: 'POST', credentials: 'include' // 确保发送cookie }).then(response => { if (response.redirected) { // 清除本地存储(如果有) localStorage.removeItem('userToken'); // 使用replace方法跳转,避免后退 window.location.replace(response.url); } }).catch(error => { console.error('退出失败:', error); }); } }); </script> </body> </html> ``` 二、后端Servlet(LogoutServlet): ```java @WebServlet("/logout") public class LogoutServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取当前session,如果不存在则不创建 HttpSession session = request.getSession(false); if (session != null) { session.invalidate(); // 销毁session } // 清除客户端存放的JSESSIONID的Cookie Cookie cookie = new Cookie("JSESSIONID", ""); cookie.setMaxAge(0); // 立即失效 cookie.setPath("/"); // 设置与当前应用相同的路径 response.addCookie(cookie); // 重定向到登录页面 response.sendRedirect("login.html"); } } ``` 三、安全性增强: - 为了防止退出后通过后退按钮返回,我们在前端跳转时使用`window.location.replace`,这样当前页面不会保存在历史记录中,用户无法后退回来。 - 同时,在后端我们销毁了session清除cookie,这样即使有人通过其他方式访问需要登录的页面,也会因为没有有效的session而跳转到登录页。 四、注意事项: - 如果使用了其他Cookie(比如自动登录的Cookie),也需要清除。 - 如果前端存储了token(例如localStorage),也要清除。 根据引用[3],关闭浏览器并不会立即销毁session,所以在退出登录时主动销毁session是非常重要的安全措施。 五、移动端适配: - 在移动端,我们同样可以使用上述方案。另外,对于移动端物理返回键的拦截,可以在登录页添加代码,当用户登录页按返回键时,直接退出应用(或者跳转到首页,根据需求)。 六、多设备登录: - 如果系统支持多设备登录,需要实现全局退出功能,可以在销毁当前session的同时,将用户登录状态标记为已退出(例如在数据库中记录),这样其他设备的session在下次请求时也会被判定为无效。具体实现需要根据系统的会话管理机制来定。 总结:退出登录页面的设计需要前后端配合,确保session和客户端的凭证被清除,并采取防止用户返回的措施。 相关问题: §§相关问题§§ 1. 如何实现多设备登录状态下的全局退出功能? 2. 退出登录时,除了销毁session,还需要清除哪些信息? 3. 如何防止用户退出后通过浏览器后退按钮访问之前的页面? 4. 移动端退出登录需要特殊处理吗? 5. 退出登录时如何避免丢失用户未保存的数据? </think>### 设计退出用户登录页面并销毁Session的完整指南 #### 一、核心设计原则 1. **双重凭证销毁机制** - **前端**:清除Cookie和本地存储 - **后端**:立即销毁Session(推荐`session.invalidate()`方法)[^1][^2] 2. **浏览器行为控制** - 退出后禁止返回隐私页面 - 清除历史记录和缓存数据 3. **安全增强** - 跨设备会话终止 - 操作审计日志记录 #### 二、退出流程实现方案 ```mermaid sequenceDiagram participant 前端 as 浏览器 participant 后端 as 服务器 前端->>后端: 用户点击退出按钮 后端->>后端: 执行session.invalidate() 后端->>前端: 发送清除Cookie指令 后端->>前端: 重定向到登录页 前端->>前端: 清除本地存储 前端->>前端: 替换历史记录 ``` #### 三、关键代码实现 ##### 1. 后端Session销毁(Java示例) ```java @WebServlet("/logout") public class LogoutServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) { // 获取当前Session但不创建新Session HttpSession session = request.getSession(false); if (session != null) { // 方案1:遍历清除所有属性 Enumeration<String> attributes = session.getAttributeNames(); while (attributes.hasMoreElements()) { session.removeAttribute(attributes.nextElement()); } // 方案2:直接使Session失效(推荐) session.invalidate(); // 立即销毁Session[^1][^2] } // 清除浏览器Cookie Cookie jsessionCookie = new Cookie("JSESSIONID", ""); jsessionCookie.setMaxAge(0); // 立即过期 jsessionCookie.setPath("/"); response.addCookie(jsessionCookie); // 重定向到登录页 response.sendRedirect("login.jsp"); } } ``` ##### 2. 前端安全退出(JavaScript实现) ```javascript // 退出按钮事件绑定 document.getElementById('logout-btn').addEventListener('click', () => { if(confirm("确定要退出系统吗?")) { // 清除前端存储 localStorage.clear(); sessionStorage.clear(); // 发送退出请求 fetch('/logout', { method: 'POST' }) .then(() => { // 替换历史记录防止后退 window.location.replace('/login'); // 清除Cookie(补充后端清除) document.cookie = "JSESSIONID=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; }); } }); ``` #### 四、安全增强措施 1. **跨设备退出** ```java // 在session.invalidate()后追加 UserSessionManager.invalidateAllSessions(userId); // 自定义会话管理 ``` 2. **操作审计日志** ```json // 退出日志记录 { "event": "logout", "user": "user123", "ip": "192.168.1.100", "session_id": "A1B2C3D4", "timestamp": "2023-08-20T10:30:00Z" } ``` 3. **浏览器历史清除** ```javascript // 重定向时替换历史记录 window.location.replace("/login?logout=success"); ``` #### 五、移动端特殊处理 1. **物理返回键拦截(Android)** ```java @Override public void onBackPressed() { if(isLogoutPage) { // 退出页禁用返回 moveTaskToBack(true); } else { super.onBackPressed(); } } ``` 2. **iOS端缓存清理** ```javascript // 清除PWA缓存 caches.keys().then(names => names.forEach(name => caches.delete(name))); ``` #### 六、最佳实践验证 | 指标 | 要求 | 检测方法 | |------|------|----------| | Session销毁率 | 100% | 服务端日志审计 | | Cookie清除率 | 100% | 浏览器开发工具验证 | | 页面不可访问率 | >99.9% | 退出后尝试访问受限页面 | | 操作响应时间 | <1s | 性能监控工具 | > **关键提示**:浏览器关闭不会立即销毁Session[^3],必须通过主动注销实现即时安全防护。对于移动端应用,需额外清除本地存储的认证凭证[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值