登录成功后,全自动踢出去前一个登陆客户,松哥第一次看到这一作用,便是在QQ里面看到的,那时候感觉太好玩了的。
自己做开发设计后,也遇到过一模一样的要求,恰好近期的 Spring Security 系列产品已经更新连载,就融合 Spring Security 来和大伙儿聊一聊这一作用怎样完成。
在同一个系统软件中,大家很有可能只容许一个客户在一个终端设备上登陆,一般来说这可能是出自于安全性层面的考虑到,可是也是有一些状况是出自于业务流程上的考虑到,松哥以前碰到的要求便是业务流程缘故规定一个客户只有在一个机器设备上登陆。
要完成一个客户不能另外在两部机器设备上登陆,大家有二种构思:
之后的登陆全自动踢出去前边的登陆,如同大伙儿在QQ中见到的实际效果。
假如客户早已登陆,则不允许幸不辱命登陆。
这类构思都能完成这一作用,实际应用哪一个,也要看大家实际的要求。
在 Spring Security 中,这二种都很好完成,一个配备就可以拿下。
2.1 踢出去早已登陆客户
要想用新的登陆踢出去旧的登陆,大家只必须将较大 对话数设定为 1 就可以,配备以下:
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.authorizeRequests()
- .anyRequest().authenticated()
- .and()
- .formLogin()
- .loginPage("/login.html")
- .permitAll()
- .and()
- .csrf().disable()
- .sessionManagement()
- .maximumSessions(1);
- }
maximumSessions 表明配备较大 对话数为 1,那样后边的登陆便会全自动踢出去前边的登陆。这儿别的的配备全是大家前边文章内容讲过的,我也不会再反复详细介绍,文尾可以下载实例详细编码。
配备进行后,各自用 Chrome 和 Firefox 2个电脑浏览器开展检测(或是应用 Chrome 中的多客户作用)。
- 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,而 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<SessionInformation> 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<SessionInformation> 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;
- List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
- for (SessionInformation session: sessionsToBeExpired) {
- session.expireNow();
- }
- }
- }
这一段关键编码我给大伙儿略微表述下:
这般,二行简易的配备就完成了 Spring Security 中 session 的高并发管理方法。是否非常简单?但是这儿还有一个小小坑,松哥将在下一篇文章中再次和大伙儿剖析。
文中实例大伙儿能够从 GitHub 上免费下载:https://github.com/lenve/spring-security-samples