0


Spring Security 自动踢掉前一个登录用户,一个配置搞定!,网易前端社招面经

.csrf().disable()

.sessionManagement()

.maximumSessions(1);

}

maximumSessions 表示配置最大会话数为 1,这样后面的登录就会自动踢掉前面的登录。这里其他的配置都是我们前面文章讲过的,我就不再重复介绍,文末可以下载案例完整代码。

配置完成后,分别用 Chrome 和 Firefox 两个浏览器进行测试(或者使用 Chrome 中的多用户功能)。

  1. Chrome 上登录成功后,访问 /hello 接口。
  2. Firefox 上登录成功后,访问 /hello 接口。
  3. 在 Chrome 上再次访问 /hello 接口,此时会看到如下提示:

This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).

可以看到,这里说这个 session 已经过期,原因则是由于使用同一个用户进行并发登录。

2.2 禁止新的登录

如果相同的用户已经登录了,你不想踢掉他,而是想禁止新的登录操作,那也好办,配置方式如下:

@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

.anyRequest().authenticated()

.and()

.formLogin()

.loginPage(“/login.html”)

.permitAll()

.and()

.csrf().disable()

.sessionManagement()

.maximumSessions(1)

.maxSessionsPreventsLogin(true);

}

添加 maxSessionsPreventsLogin 配置即可。此时一个浏览器登录成功后,另外一个浏览器就登录不了了。

是不是很简单?

不过还没完,我们还需要再提供一个 Bean:

@Bean

HttpSessionEventPublisher httpSessionEventPublisher() {

return new HttpSessionEventPublisher();

}

为什么要加这个 Bean 呢?因为在 Spring Security 中,它是通过监听 session 的销毁事件,来及时的清理 session 的记录。用户从不同的浏览器登录后,都会有对应的 session,当用户注销登录之后,session 就会失效,但是默认的失效是通过调用 StandardSession#invalidate 方法来实现的,这一个失效事件无法被 Spring 容器感知到,进而导致当用户注销登录之后,Spring Security 没有及时清理会话信息表,以为用户还在线,进而导致用户无法重新登录进来(小伙伴们可以自行尝试不添加上面的 Bean,然后让用户注销登录之后再重新登录)。

为了解决这一问题,我们提供一个 HttpSessionEventPublisher ,这个类实现了 HttpSessionListener 接口,在该 Bean 中,可以将 session 创建以及销毁的事件及时感知到,并且调用 Spring 中的事件机制将相关的创建和销毁事件发布出去,进而被 Spring Security 感知到,该类部分源码如下:

public void sessionCreated(HttpSessionEvent event) {

HttpSessionCreatedEvent e = new HttpSessionCreatedEvent(event.getSession());

getContext(event.getSession().getServletContext()).publishEvent(e);

}

public void sessionDestroyed(HttpSessionEvent event) {

HttpSessionDestroyedEvent e = new HttpSessionDestroyedEvent(event.getSession());

getContext(event.getSession().getServletContext()).publishEvent(e);

}

OK,虽然多了一个配置,但是依然很简单!

3.实现原理


上面这个功能,在 Spring Security 中是怎么实现的呢?我们来稍微分析一下源码。

首先我们知道,在用户登录的过程中,会经过 UsernamePasswordAuthenticationFilter(参考:松哥手把手带你捋一遍 Spring Security 登录流程),而 UsernamePasswordAuthenticationFilter 中过滤方法的调用是在 AbstractAuthenticationProcessingFilter 中触发的,我们来看下 AbstractAuthenticationProcessingFilter#doFilter 方法的调用:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

if (!requiresAuthentication(request, response)) {

chain.doFilter(request, response);

return;

}

Authentication authResult;

try {

authResult = attemptAuthentication(request, response);

if (authResult == null) {

return;

}

sessionStrategy.onAuthentication(authResult, request, response);

}

catch (InternalAuthenticationServiceException failed) {

unsuccessfulAuthentication(request, response, failed);

return;

}

catch (AuthenticationException failed) {

unsuccessfulAuthentication(request, response, failed);

return;

}

// Authentication success

if (continueChainBeforeSuccessfulAuthentication) {

chain.doFilter(request, response);

}

successfulAuthentication(request, response, chain, authResult);

在这段代码中,我们可以看到,调用 attemptAuthentication 方法走完认证流程之后,回来之后,接下来就是调用 sessionStrategy.onAuthentication 方法,这个方法就是用来处理 session 的并发问题的。具体在:

public class ConcurrentSessionControlAuthenticationStrategy implements

MessageSourceAware, SessionAuthenticationStrategy {

public void onAuthentication(Authentication authentication,

HttpServletRequest request, HttpServletResponse response) {

final List sessions = sessionRegistry.getAllSessions(

authentication.getPrincipal(), false);

int sessionCount = sessions.size();

int allowedSessions = getMaximumSessionsForThisUser(authentication);

if (sessionCount < allowedSessions) {

// They haven’t got too many login sessions running at present

return;

}

if (allowedSessions == -1) {

// We permit unlimited logins

return;

}

if (sessionCount == allowedSessions) {

HttpSession session = request.getSession(false);

if (session != null) {

// Only permit it though if this request is associated with one of the

// already registered sessions

for (SessionInformation si : sessions) {

if (si.getSessionId().equals(session.getId())) {

return;

}

}

}

// If the session is null, a new one will be created by the parent class,

// exceeding the allowed number

}

allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);

}

protected void allowableSessionsExceeded(List sessions,

int allowableSessions, SessionRegistry registry)

throws SessionAuthenticationException {

if (exceptionIfMaximumExceeded || (sessions == null)) {

throw new SessionAuthenticationException(messages.getMessage(

“ConcurrentSessionControlAuthenticationStrategy.exceededAllowed”,

new Object[] {allowableSessions},

“Maximum sessions of {0} for this principal exceeded”));

}

// Determine least recently used sessions, and mark them for invalidation

sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));

int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;

标签: spring 前端 java

本文转载自: https://blog.csdn.net/2401_87715696/article/details/143902975
版权归原作者 2401_87715696 所有, 如有侵权,请联系我们删除。

“Spring Security 自动踢掉前一个登录用户,一个配置搞定!,网易前端社招面经”的评论:

还没有评论