当前位置: 首页 > 工具软件 > session > 使用案例 >

使用spring-session 根据sessionId 删除session

常子濯
2023-12-01

前言

需求:如何保证同一个账号保证只有一个在线。(即:我在设备A上先登录账号guest,同时另外一个人在设备B上也登陆账号guest,此时,设备A上的账号将会被挤下线)

思路

  1. 账号登录成功后,在数据库或redis中查询当前用户绑定的sessionId
  2. 如果有值,则调用SessionRepository 删除当前session
  3. 在数据库或redis 记录当前登录账号对应的新的sessionId

步骤

  1. 在pom.xml引入依赖
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-core</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
  1. 开启spring-session
    这里使用内存,存储session,当然也可以使用 redis 或者 数据库
    这里就不讲了,redis和数据库存储spring都有实现,请找度娘。
@EnableSpringHttpSession
public class MySessionConfig {
    @Bean
    public MapSessionRepository sessionRepository() {
      return new MapSessionRepository(new HashMap<>());
   }
}
  1. 伪代码,模拟登录成功后,删除旧session
 ...此处省略登录代码
 ...登录成功
 HttpSession httpSession = request.getSession();
 //从数据库查询旧sessionId
 String sessionId = userService.getUserSessionId(loginUserId); 
 //删除 old sessionId
 remove(request,sessionId);
 
 //保存user session 数据
 request.getSession().setAttribute("user",userInfo);

//保存新的sessionId关系
userService.saveUserSessionId(userId,httpSession.getId());

  1. 根据sessionId删除session函数
	// 删除sessionId 方法
    public static boolean remove(HttpServletRequest request , String sessionId) {
        HttpSession httpSession = request.getSession();
        if (httpSession.getId().equals(sessionId)) {
            // 当前session
            httpSession.invalidate();
            return true;
        } else {
            // 非当前Session
            SessionRepository sessionRepository =  (SessionRepository) request.getAttribute(SessionRepositoryFilter.SESSION_REPOSITORY_ATTR);
            if (sessionRepository != null) {
                Session session = sessionRepository.findById(sessionId);
                if (session != null) {
                    log.debug("删除会话,ID: {}", sessionId);
                }
                sessionRepository.deleteById(sessionId);
                return true;
            }
        }
        return false; // session ID not found
    }

原理

  1. 进入EnableSpringHttpSession
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({ java.lang.annotation.ElementType.TYPE })
@Documented
@Import(SpringHttpSessionConfiguration.class)
@Configuration(proxyBeanMethods = false)
public @interface EnableSpringHttpSession {

}

  1. 引入了SpringHttpSessionConfiguration 进去看一下
    关键看注册了 SessionRepositoryFilter
	@Bean
	public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(
			SessionRepository<S> sessionRepository) {
		SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<>(sessionRepository);
		sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
		return sessionRepositoryFilter;
	}
  1. 瞅一瞅 SessionRepositoryFilter
    主要看SessionRepositoryFilter 的 doFilterInternal 函数 因为父类 doFilter 中有调用它。
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
			//这里设置了 所以上面能取到
		request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);

		SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
		SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,
				response);

		try {
			filterChain.doFilter(wrappedRequest, wrappedResponse);
		}
		finally {
			wrappedRequest.commitSession();
		}
	}
  1. 看一下包装类
	private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {


		... 省略不关键代码
		@Override
		public HttpSessionWrapper getSession(boolean create) {
			HttpSessionWrapper currentSession = getCurrentSession();
			if (currentSession != null) {
				return currentSession;
			}
			S requestedSession = getRequestedSession();
			if (requestedSession != null) {
				if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
					requestedSession.setLastAccessedTime(Instant.now());
					this.requestedSessionIdValid = true;
					currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
					currentSession.markNotNew();
					setCurrentSession(currentSession);
					return currentSession;
				}
			}
			else {
				// This is an invalid session id. No need to ask again if
				// request.getSession is invoked for the duration of this request
				if (SESSION_LOGGER.isDebugEnabled()) {
					SESSION_LOGGER.debug(
							"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
				}
				setAttribute(INVALID_SESSION_ID_ATTR, "true");
			}
			if (!create) {
				return null;
			}
			if (SESSION_LOGGER.isDebugEnabled()) {
				SESSION_LOGGER.debug(
						"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
								+ SESSION_LOGGER_NAME,
						new RuntimeException("For debugging purposes only (not an error)"));
			}
			//这里调用sessionRepository创建session
			S session = SessionRepositoryFilter.this.sessionRepository.createSession();
			session.setLastAccessedTime(Instant.now());
			currentSession = new HttpSessionWrapper(session, getServletContext());
			setCurrentSession(currentSession);
			return currentSession;
		}
		...省略不关键代码
	}

总结: spring-session 使用过滤器将request对象进行了包装,所以我们拿到的是包装后的request对象,从而我们获取session也是调用的包装对象的getSession函数,所以根据sessionId删除session时可以调用:sessionRepository.deleteById(sessionId);


问题

这里我有点不理解:
在springmvc 集成spring-session时需要在web.xml中配置代理filter
DelegatingFilterProxy并且指定名称为springSessionRepositoryFilter

为什么SessionRepositoryFilter过滤器在spring boot中通过注解@Bean注入到spring容器中,就可以直接在filter的过虑链中执行呢,有知道的博友帮忙解答下。谢谢。


记录一小步,成长一大步。
有问题欢迎大家在评论区讨论。

 类似资料: