`

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);
	}

 

分享到:
评论

相关推荐

    利用spring security控制同一个用户只能一次登陆

    标题中的“利用Spring Security控制同一个用户只能一次登录”是指在基于Spring Security的Web应用程序中实现单点登录(Single Sign-On, SSO)的功能,确保同一时间只有一个设备或浏览器会话可以登录同一用户的账户。...

    Spring Security 3多用户登录实现之十一 退出

    Spring Security 是一个强大的安全框架,用于为Java应用提供安全控制,包括认证、授权以及会话管理等。在Spring Security 3中,实现多用户登录功能是构建安全Web应用的重要环节。退出功能则允许用户安全地结束他们的...

    spring security 官方文档

    4. **会话管理(Session Management)**:Spring Security提供了强大的会话管理功能,可以限制同一用户同时在线的数量,检测会话劫持和会话固定攻击,并能实现会话超时策略。 5. **CSRF保护(Cross-Site Request ...

    Spring Security-3中文官方文档(及教程)

    6. **记住我功能**:Spring Security支持“记住我”功能,允许用户在一段时间内无须重新登录。这通过在客户端存储一个长期有效的令牌来实现。 7. **OAuth2整合**:Spring Security可以与OAuth2框架集成,支持第三方...

    SpringSecurity3.1.2控制一个账户同时只能登录一次

    在Spring Security框架中,实现一个账户同时只能登录一次的功能,是为了增强系统的安全性,防止恶意用户通过多设备或多个浏览器窗口同时登录同一账号进行非法操作。这个功能通常被称为单点登录(Single Sign-On, SSO...

    springsecurity3.0.5应用

    5. **Session Management**:Spring Security提供了会话管理策略,如限制同一用户并发会话的数量,防止会话固定攻击。 6. **CSRF防护**:为防止跨站请求伪造(Cross-Site Request Forgery)攻击,Spring Security...

    Spring Boot Security 2.5.8 实现账号、手机号、邮件登录,记住密码等功能

    Spring Boot Security 2.5.8 是一个强大的框架,用于为Spring Boot应用程序提供安全控制,包括身份验证和授权。在2.5.8版本中,它增强了灵活性和易用性,使得开发者能够轻松地实现复杂的安全需求。在这个项目中,...

    filter过滤器实现权限访问控制以及同一账号只能登录一台设备

    在实际项目中,`Spring Security`或`Apache Shiro`等安全框架提供了更高级的权限管理和会话管理功能,它们可以简化上述过程,提供更丰富的功能,如记住我、CSRF防护等。 总的来说,通过`Filter`实现权限访问控制和...

    Spring Security 3全文下载

    Spring Security 是一个强大且高度可定制的Java安全框架,主要用于处理Web应用的安全需求。在Spring Security 3版本中,该框架进行了大量的改进和优化,提供了更全面的安全控制,包括认证、授权、会话管理以及CSRF...

    SpringSecurity

    默认情况下,Spring Security 开启 CSRF 保护,通过生成和验证一个唯一的CSRF令牌确保只处理来自同一浏览器的请求。 **Web安全增强** Spring Security 还提供了一些额外的 web 安全特性,如 HTTP 响应头设置(如 X...

    spring-security-4.2.0.RELEASE-dist

    4. **会话管理(Session Management)**:Spring Security提供了一套完整的会话管理机制,包括会话固定保护(Session Fixation Protection)、会话超时检测以及单会话约束(防止同一用户同时登录多个设备)。...

    springsecurity的帮助文档

    - **Spring Security**:Spring Security 是一个强大的、高度可定制的身份验证和访问控制框架。它为开发者提供了多种方式来保护应用程序,并且可以非常灵活地根据项目需求进行配置。 - **历史沿革**:自2005年起...

    spring-security3.1源码

    5. **Remember Me服务**:Spring Security 3.1 提供了Remember Me服务,允许用户在一段时间内无需重新登录。此功能通过`RememberMeServices`接口实现,通常与`PersistentTokenBasedRememberMeServices`或`...

    单点登录SSO解决方案之SpringSecurity+JWT实现.docx

    单点登录(Single Sign-On,简称SSO)是一种认证机制,允许用户仅通过一次登录就能访问同一域下的多个应用程序和服务。这种机制简化了用户的使用体验,并提升了系统的整体安全性。 #### 二、单点登录的基本运行机制...

    spring security 3.x session-management 会话管理失效

    Spring Security提供了一种基于票证的并发控制,如果发现用户有新的登录尝试,会强制旧会话注销。 5. **权限控制** - Spring Security通过访问决策管理器(Access Decision Manager)和权限访问决策策略(Voter)...

    Spring-Security_java_

    6. **Remember Me**: Spring Security 提供了“记住我”功能,允许用户在一段时间内无需重新登录。这个功能通过在用户的浏览器中设置长期 cookie 来实现。 7. **Session Management**: 该组件管理用户会话,防止...

    Spring_Security3中文指南.pdf

    - **自动“记住我”认证**:允许用户在一段时间内无需重复认证。 - **匿名认证**:对于不关心用户身份的场景,自动假设一个特定的安全主体。 - **Run-as 认证**:在同一会话中使用不同的安全身份。 - **Java ...

    Spring Security学习总结

    Spring Security的Remember Me服务通过在用户登录时创建一个长期的凭据,并将其存储在用户的Cookie中,以便在后续的无状态请求中自动重新认证用户。 7. **CSRF防护**:为了防止跨站请求伪造,Spring Security提供了...

    SpringSecurity3.0 教程

    SpringSecurity提供了会话管理功能,可以防止会话固定攻击,限制同一用户并发会话的数量,以及实现会话超时策略。 七、安全性API SpringSecurity提供了一系列API,如`SecurityContextHolder`用于获取当前安全上下文...

Global site tag (gtag.js) - Google Analytics