网上很多文章的实现方法写得比较复杂
这里介绍一个简单的方法。
实现
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/**").access("hasRole('ADMIN') or hasRole('USER')")
.and().formLogin().permitAll();
//以下这句就可以控制单个用户只能创建一个session,也就只能在服务器登录一次
http.sessionManagement().maximumSessions(1).expiredUrl("/login");
}
原理
下面介绍下Spring Security的session数量控制的工作原理。
在org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy包下的onAuthentication方法(每次用户登录都会触发),会依据用户登录的authentication取出改用户在服务器的所有session,并计算该用户在服务器创建了多少个session,如果session多于设置的数量,会使用排序算法,得到最早的session,并将其设置为过期(删除)。
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
int sessionCount = sessions.size();
int allowedSessions = this.getMaximumSessionsForThisUser(authentication);
if (sessionCount >= allowedSessions) {
if (allowedSessions != -1) {
if (sessionCount == allowedSessions) {
HttpSession session = request.getSession(false);
if (session != null) {
Iterator var8 = sessions.iterator();
while(var8.hasNext()) {
SessionInformation si = (SessionInformation)var8.next();
if (si.getSessionId().equals(session.getId())) {
return;
}
}
}
}
this.allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry);
}
}
}
protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions, SessionRegistry registry) throws SessionAuthenticationException {
if (!this.exceptionIfMaximumExceeded && sessions != null) {
SessionInformation leastRecentlyUsed = null;
Iterator var5 = sessions.iterator();
while(true) {
SessionInformation session;
do {
if (!var5.hasNext()) {
leastRecentlyUsed.expireNow();
return;
}
session = (SessionInformation)var5.next();
} while(leastRecentlyUsed != null && !session.getLastRequest().before(leastRecentlyUsed.getLastRequest()));
leastRecentlyUsed = session;
}
} else {
throw new SessionAuthenticationException(this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed", new Object[]{allowableSessions}, "Maximum sessions of {0} for this principal exceeded"));
}
}
Spring Security 是使用org.springframework.security.core.userdetails.User类作为用户登录凭据( Principal )的。该类中重写了equals()和hashCode(),使用username属性作为唯一凭据。
public boolean equals(Object rhs) {
return rhs instanceof User ? this.username.equals(((User)rhs).username) : false;
}
public int hashCode() {
return this.username.hashCode();
}