spring security同一用户只允许登录一次

@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Resource  
    private SessionRegistry sessionRegistry;

    @Bean  
    public SessionRegistry sessionRegistry() {  
        return new SessionRegistryImpl();  
    } 

    /**
     * 强制下线过滤器.<br/>
     *
     * @return
     *
     */
    @Bean
    public ConcurrentSessionFilter concurrencyFilter() {
        // 定义session失效后,重定向的url
        ConcurrentSessionFilter concurrentSessionFilter = new ConcurrentSessionFilter(sessionRegistry(), "/kickout");
        concurrentSessionFilter.setRedirectStrategy(autoRedirectStrategy());
        return concurrentSessionFilter;
    }
...

    @Override
    protected void configure(HttpSecurity http) throws Exception {

...
        // 设定强制下线自定义过滤器
        http.addFilterAt(concurrencyFilter(), ConcurrentSessionFilter.class);
        //session管理
        //session失效后跳转  
        http.sessionManagement().invalidSessionUrl("/login"); 
        //只允许一个用户登录,如果同一个账户两次登录,那么第一个账户将被踢下线,跳转到登录页面
        http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry).expiredUrl("/login");
    }
}

 

UserInfo.java

 

public class UserInfo implements UserDetails {
...// 用户信息


    @Override
    public int hashCode() {
...// 自定义userInfo hashCode方法,session管理时会用做key
        return new HashCodeBuilder(17, 37).append(getCompanyId()).append(getUserId()).append(getUsername())
                .toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
...// 自定义equals方法
        boolean isEqual = false;
        if (obj != null && UserInfo.class.isAssignableFrom(obj.getClass())) {
            UserInfo userInfo = (UserInfo) obj;
            isEqual = new EqualsBuilder().append(getCompanyId(), userInfo.getCompanyId())
                    .append(getUserId(), userInfo.getUserId()).append(getUsername(), userInfo.getUsername()).isEquals();
        }
        return isEqual;
    }

}

 

 

原理分析:

 

SessionRegistryImpl#registerNewSession(String sessionId, Object principal) 

把用户的sessionId添加到principals Map中,key为登录用户principal(userInfo)的hashCode();

 

public void registerNewSession(String sessionId, Object principal) {
		Assert.hasText(sessionId, "SessionId required as per interface contract");
		Assert.notNull(principal, "Principal required as per interface contract");

		if (logger.isDebugEnabled()) {
			logger.debug("Registering session " + sessionId + ", for principal "
					+ principal);
		}

		if (getSessionInformation(sessionId) != null) {
			removeSessionInformation(sessionId);
		}

		sessionIds.put(sessionId,
				new SessionInformation(principal, sessionId, new Date()));
                // 根据principal(UserInfo)取得用户所有session
		Set<String> sessionsUsedByPrincipal = principals.get(principal);
                
                // 如果为空,新建,key为userInfo的hashCode,就是上面提到的自定义hashCode方法。
		if (sessionsUsedByPrincipal == null) {
			sessionsUsedByPrincipal = new CopyOnWriteArraySet<String>();
			Set<String> prevSessionsUsedByPrincipal = principals.putIfAbsent(principal,
					sessionsUsedByPrincipal);
			if (prevSessionsUsedByPrincipal != null) {
				sessionsUsedByPrincipal = prevSessionsUsedByPrincipal;
			}
		}

		sessionsUsedByPrincipal.add(sessionId);

		if (logger.isTraceEnabled()) {
			logger.trace("Sessions used by '" + principal + "' : "
					+ sessionsUsedByPrincipal);
		}
	}

 

 ConcurrentSessionControlAuthenticationStrategy#onAuthentication 判断某个用户的session是否有超过设定连接数

 

public void onAuthentication(Authentication authentication,
			HttpServletRequest request, HttpServletResponse response) {
                // 取得用户所有session
		final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
				authentication.getPrincipal(), false);

		int sessionCount = sessions.size();
                // 在WebSecurityConfigurer中设定的最大session数
		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
		}
                // 失效时间最长的session
		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[] { Integer.valueOf(allowableSessions) },
					"Maximum sessions of {0} for this principal exceeded"));
		}

		// Determine least recently used session, and mark it for invalidation
		SessionInformation leastRecentlyUsed = null;
                // 判断session中哪个最后的访问时间最长,设为失效
		for (SessionInformation session : sessions) {
			if ((leastRecentlyUsed == null)
					|| session.getLastRequest()
							.before(leastRecentlyUsed.getLastRequest())) {
				leastRecentlyUsed = session;
			}
		}

		leastRecentlyUsed.expireNow();
	}

 

在ConcurrentSessionFilter过滤器中

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		HttpSession session = request.getSession(false);

		if (session != null) {
			SessionInformation info = sessionRegistry.getSessionInformation(session
					.getId());

			if (info != null) {
                                // 如果session已经失效,重定向到设定好的expiredUrl中
				if (info.isExpired()) {
					// Expired - abort processing
					if (logger.isDebugEnabled()) {
						logger.debug("Requested session ID "
								+ request.getRequestedSessionId() + " has expired.");
					}
					doLogout(request, response);

					this.sessionInformationExpiredStrategy.onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response));
					return;
				}
				else {
					// Non-expired - update last request date/time
					sessionRegistry.refreshLastRequest(info.getSessionId());
				}
			}
		}

		chain.doFilter(request, response);
	}

 

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值