SpringSecurity+Springboot实现踢掉前一个登录用户
1.需求分析
要实现一个用户不可以同时在两台设备上登录,有两种思路:
(1)后来的登录自动踢掉前面的登录,就像大家在扣扣中看到的效果
(2)如果用户已经登录,则不允许后来者登录。
这种思路都能实现这个功能,具体使用哪一个,还要看我们具体的需求。
在 Spring Security 中,这两种都很好实现。
下面只说第一种实现。
2.具体实现
(1)踢掉前面的登录
想要用新的登录踢掉旧的登录,我们只需要将最大会话数设置为 1 即可,配置如下:
// 关闭csrf验证
http.csrf().disable().cors()
// create no session
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// session 已经过期的提示解决
.sessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy())
// 对请求进行认证
.and()
.authorizeRequests()
.antMatchers(resources).permitAll()
.antMatchers(HttpMethod.POST, postMapping).permitAll()
.antMatchers(HttpMethod.GET, getMapping).permitAll()
// 所有请求需要身份认证
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new Http401AuthenticationEntryPoint())
.and()
.maximumSessions(1)
.sessionRegistry(sessionRegistry)
.maxSessionsPreventsLogin(false);
maximumSessions 表示配置最大会话数为 1,这样后面的登录就会自动踢掉前面的登录
会有以下提示:
This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).
可以看到,这里说这个 session 已经过期,原因则是由于使用同一个用户进行并发登录
(2)解决 session 已经过期的提示
.sessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy())
把该类加入到web配置里
public class ConcurrentSessionControlAuthenticationStrategy implements
MessageSourceAware, SessionAuthenticationStrategy {
@Autowired
private SessionRegistry sessionRegistry;
private MessageSource messageSource;
public void onAuthentication(Authentication authentication,
HttpServletRequest request, HttpServletResponse response) {
int sessionCount = 0;
List<SessionInformation> sessions = new ArrayList<>();
if (sessionRegistry!=null) {
sessions = sessionRegistry.getAllSessions(
authentication.getPrincipal(), false);
sessionCount = sessions.size();
}
int allowedSessions = 1;
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) {
for (SessionInformation si : sessions) {
if (si.getSessionId().equals(session.getId())) {
return;
}
}
}
}
allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);
}
protected void allowableSessionsExceeded(List<SessionInformation> sessions,
int allowableSessions, SessionRegistry registry)
throws SessionAuthenticationException {
if ((sessions == null)) {
throw new SessionAuthenticationException(messageSource.getMessage(
"ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",
new Object[] {allowableSessions},
Locale.forLanguageTag("Maximum sessions of {0} for this principal exceeded")));
}
sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
for (SessionInformation session: sessionsToBeExpired) {
session.expireNow();
}
}
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
}