You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不使用Spring Security实现会话超时管理及用户移除方案问询

问题描述

我正在开发一款简易聊天应用,未使用Spring Security。前端用户输入用户名后,由以下Controller处理:

@PostMapping("/addUser")
public User addUser(@RequestBody String name, HttpServletRequest request) {         
    String session = (String) request.getSession().getAttribute("sessionId");
    System.out.println("Session id is " + session);
    User newUser = new User(name, session);
    userService.addUser(newUser);
    System.out.println(newUser);
                    
    return newUser;
}

我使用拦截器的preHandle方法为用户生成sessionId,代码如下:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("Its working");
    // TODO Auto-generated method stub
    if(request instanceof HttpServletRequest) {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpSession session = servletRequest.getSession();
        session.setAttribute("sessionId", session.getId());
                        
        System.out.println("Connected with session id : " + session.getAttribute("sessionId"));
    }
    return true;
}

我希望实现当用户长时间未活跃时,结束其会话并将该用户从存储所有注册用户的ArrayList中移除。请问不使用Spring Security是否可以实现该功能?我曾尝试使用任务调度,但发现无法在其中调用HttpSession。

解决方案

完全可以不依赖Spring Security实现该功能,核心思路是自己维护用户活跃状态和会话映射,结合定时任务完成清理。具体步骤如下:

1. 扩展User类,增加活跃时间字段

给User类添加lastActiveTime字段,用来记录用户最后一次活跃的时间戳:

public class User {
    private String name;
    private String sessionId;
    private long lastActiveTime; // 记录最后活跃时间戳

    // 构造方法、getter/setter
    public User(String name, String sessionId) {
        this.name = name;
        this.sessionId = sessionId;
        this.lastActiveTime = System.currentTimeMillis(); // 初始化时设为当前时间
    }

    // 更新活跃时间的方法
    public void updateActiveTime() {
        this.lastActiveTime = System.currentTimeMillis();
    }

    // getter方法
    public long getLastActiveTime() {
        return lastActiveTime;
    }

    public String getSessionId() {
        return sessionId;
    }
}

2. 维护全局会话映射

创建一个全局的线程安全映射,用来存储sessionId和对应的HttpSession,这样定时任务就能通过sessionId找到会话:

@Component
public class SessionManager {
    // 线程安全的HashMap,存储sessionId -> HttpSession
    private final ConcurrentHashMap<String, HttpSession> sessionMap = new ConcurrentHashMap<>();

    // 添加会话
    public void addSession(String sessionId, HttpSession session) {
        sessionMap.put(sessionId, session);
    }

    // 根据sessionId获取会话
    public HttpSession getSession(String sessionId) {
        return sessionMap.get(sessionId);
    }

    // 删除会话
    public void removeSession(String sessionId) {
        sessionMap.remove(sessionId);
    }
}

3. 更新拦截器,维护会话映射和用户活跃时间

修改拦截器的preHandle方法,每次用户请求时更新用户的活跃时间,并将会话存入全局映射:

@Component
public class SessionInterceptor implements HandlerInterceptor {
    @Autowired
    private SessionManager sessionManager;
    @Autowired
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Its working");
        HttpSession session = request.getSession();
        String sessionId = session.getId();
        session.setAttribute("sessionId", sessionId);
        
        // 将会话存入全局映射
        sessionManager.addSession(sessionId, session);
        
        // 更新当前用户的活跃时间(如果用户已存在)
        User currentUser = userService.getUserBySessionId(sessionId);
        if (currentUser != null) {
            currentUser.updateActiveTime();
        }
        
        System.out.println("Connected with session id : " + sessionId);
        return true;
    }
}

注意:需要在UserService中实现getUserBySessionId方法,根据sessionId从用户列表中查找对应的用户。

4. 编写定时清理任务

使用Spring的@Scheduled注解实现定时任务,定期检查用户活跃时间,清理超时用户:

@Component
public class UserCleanupTask {
    @Autowired
    private UserService userService;
    @Autowired
    private SessionManager sessionManager;

    // 超时时间,比如30分钟(单位:毫秒)
    private static final long TIMEOUT = 30 * 60 * 1000;

    // 每隔1分钟执行一次清理
    @Scheduled(fixedRate = 60 * 1000)
    public void cleanupInactiveUsers() {
        long currentTime = System.currentTimeMillis();
        // 获取用户列表(注意线程安全,建议用Collections.synchronizedList包装原ArrayList)
        List<User> users = userService.getAllUsers();
        
        // 遍历用户,找出超时的用户
        Iterator<User> iterator = users.iterator();
        while (iterator.hasNext()) {
            User user = iterator.next();
            if (currentTime - user.getLastActiveTime() > TIMEOUT) {
                // 销毁会话
                HttpSession session = sessionManager.getSession(user.getSessionId());
                if (session != null) {
                    session.invalidate();
                    sessionManager.removeSession(user.getSessionId());
                }
                // 从用户列表中移除
                iterator.remove();
                System.out.println("清理超时用户:" + user.getName());
            }
        }
    }
}

注意:要确保UserService中的用户列表是线程安全的,比如在初始化时用Collections.synchronizedList(new ArrayList<>())创建,避免并发修改问题。

5. 开启Spring定时任务支持

在Spring Boot启动类上添加@EnableScheduling注解,开启定时任务功能:

@SpringBootApplication
@EnableScheduling
public class ChatApplication {
    public static void main(String[] args) {
        SpringApplication.run(ChatApplication.class, args);
    }
}

这样就能实现用户长时间未活跃时自动销毁会话并移除用户的功能,全程不需要依赖Spring Security。

内容的提问来源于stack exchange,提问作者LazyCoder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 16:55:22