最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Security 自動(dòng)踢掉前一個(gè)登錄用戶的實(shí)現(xiàn)代碼

 更新時(shí)間:2020年05月08日 10:29:12   作者:_江南一點(diǎn)雨  
這篇文章主要介紹了Spring Security 自動(dòng)踢掉前一個(gè)登錄用戶的實(shí)現(xiàn)代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

登錄成功后,自動(dòng)踢掉前一個(gè)登錄用戶,松哥第一次見到這個(gè)功能,就是在扣扣里邊見到的,當(dāng)時(shí)覺得挺好玩的。

自己做開發(fā)后,也遇到過一模一樣的需求,正好最近的 Spring Security 系列正在連載,就結(jié)合 Spring Security 來和大家聊一聊這個(gè)功能如何實(shí)現(xiàn)。

本文是本系列的第十三篇,閱讀前面文章有助于更好的理解本文:

  1. 挖一個(gè)大坑,Spring Security 開搞!
  2. 松哥手把手帶你入門 Spring Security,別再問密碼怎么解密了
  3. 手把手教你定制 Spring Security 中的表單登錄
  4. Spring Security 做前后端分離,咱就別做頁面跳轉(zhuǎn)了!統(tǒng)統(tǒng) JSON 交互
  5. Spring Security 中的授權(quán)操作原來這么簡單
  6. Spring Security 如何將用戶數(shù)據(jù)存入數(shù)據(jù)庫?
  7. Spring Security+Spring Data Jpa 強(qiáng)強(qiáng)聯(lián)手,安全管理只有更簡單!
  8. Spring Boot + Spring Security 實(shí)現(xiàn)自動(dòng)登錄功能
  9. Spring Boot 自動(dòng)登錄,安全風(fēng)險(xiǎn)要怎么控制?
  10. 在微服務(wù)項(xiàng)目中,Spring Security 比 Shiro 強(qiáng)在哪?
  11. SpringSecurity 自定義認(rèn)證邏輯的兩種方式(高級(jí)玩法)
  12. Spring Security 中如何快速查看登錄用戶 IP 地址等信息?

1.需求分析

在同一個(gè)系統(tǒng)中,我們可能只允許一個(gè)用戶在一個(gè)終端上登錄,一般來說這可能是出于安全方面的考慮,但是也有一些情況是出于業(yè)務(wù)上的考慮,松哥之前遇到的需求就是業(yè)務(wù)原因要求一個(gè)用戶只能在一個(gè)設(shè)備上登錄。

要實(shí)現(xiàn)一個(gè)用戶不可以同時(shí)在兩臺(tái)設(shè)備上登錄,我們有兩種思路:

  • 后來的登錄自動(dòng)踢掉前面的登錄,就像大家在扣扣中看到的效果。
  • 如果用戶已經(jīng)登錄,則不允許后來者登錄。

這種思路都能實(shí)現(xiàn)這個(gè)功能,具體使用哪一個(gè),還要看我們具體的需求。

在 Spring Security 中,這兩種都很好實(shí)現(xiàn),一個(gè)配置就可以搞定。

2.具體實(shí)現(xiàn)

2.1 踢掉已經(jīng)登錄用戶

想要用新的登錄踢掉舊的登錄,我們只需要將最大會(huì)話數(shù)設(shè)置為 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 表示配置最大會(huì)話數(shù)為 1,這樣后面的登錄就會(huì)自動(dòng)踢掉前面的登錄。這里其他的配置都是我們前面文章講過的,我就不再重復(fù)介紹,文末可以下載案例完整代碼。

配置完成后,分別用 Chrome 和 Firefox 兩個(gè)瀏覽器進(jìn)行測試(或者使用 Chrome 中的多用戶功能)。

  • Chrome 上登錄成功后,訪問 /hello 接口。
  • Firefox 上登錄成功后,訪問 /hello 接口。
  • 在 Chrome 上再次訪問 /hello 接口,此時(shí)會(huì)看到如下提示:

This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).

可以看到,這里說這個(gè) session 已經(jīng)過期,原因則是由于使用同一個(gè)用戶進(jìn)行并發(fā)登錄。

2.2 禁止新的登錄

如果相同的用戶已經(jīng)登錄了,你不想踢掉他,而是想禁止新的登錄操作,那也好辦,配置方式如下:

@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 配置即可。此時(shí)一個(gè)瀏覽器登錄成功后,另外一個(gè)瀏覽器就登錄不了了。

是不是很簡單?

不過還沒完,我們還需要再提供一個(gè) Bean:

@Bean
HttpSessionEventPublisher httpSessionEventPublisher() {
 return new HttpSessionEventPublisher();
}

為什么要加這個(gè) Bean 呢?因?yàn)樵?Spring Security 中,它是通過監(jiān)聽 session 的銷毀事件,來及時(shí)的清理 session 的記錄。用戶從不同的瀏覽器登錄后,都會(huì)有對(duì)應(yīng)的 session,當(dāng)用戶注銷登錄之后,session 就會(huì)失效,但是默認(rèn)的失效是通過調(diào)用 StandardSession#invalidate 方法來實(shí)現(xiàn)的,這一個(gè)失效事件無法被 Spring 容器感知到,進(jìn)而導(dǎo)致當(dāng)用戶注銷登錄之后,Spring Security 沒有及時(shí)清理會(huì)話信息表,以為用戶還在線,進(jìn)而導(dǎo)致用戶無法重新登錄進(jìn)來(小伙伴們可以自行嘗試不添加上面的 Bean,然后讓用戶注銷登錄之后再重新登錄)。

為了解決這一問題,我們提供一個(gè) HttpSessionEventPublisher ,這個(gè)類實(shí)現(xiàn)了 HttpSessionListener 接口,在該 Bean 中,可以將 session 創(chuàng)建以及銷毀的事件及時(shí)感知到,并且調(diào)用 Spring 中的事件機(jī)制將相關(guān)的創(chuàng)建和銷毀事件發(fā)布出去,進(jìn)而被 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,雖然多了一個(gè)配置,但是依然很簡單!

3.實(shí)現(xiàn)原理

上面這個(gè)功能,在 Spring Security 中是怎么實(shí)現(xiàn)的呢?我們來稍微分析一下源碼。

首先我們知道,在用戶登錄的過程中,會(huì)經(jīng)過 UsernamePasswordAuthenticationFilter(參考: Spring Security 登錄流程),而 UsernamePasswordAuthenticationFilter 中過濾方法的調(diào)用是在 AbstractAuthenticationProcessingFilter 中觸發(fā)的,我們來看下 AbstractAuthenticationProcessingFilter#doFilter 方法的調(diào)用:

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

在這段代碼中,我們可以看到,調(diào)用 attemptAuthentication 方法走完認(rèn)證流程之后,回來之后,接下來就是調(diào)用 sessionStrategy.onAuthentication 方法,這個(gè)方法就是用來處理 session 的并發(fā)問題的。具體在:

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

這段核心代碼我來給大家稍微解釋下:

  • 首先調(diào)用 sessionRegistry.getAllSessions 方法獲取當(dāng)前用戶的所有 session,該方法在調(diào)用時(shí),傳遞兩個(gè)參數(shù),一個(gè)是當(dāng)前用戶的 authentication,另一個(gè)參數(shù) false 表示不包含已經(jīng)過期的 session(在用戶登錄成功后,會(huì)將用戶的 sessionid 存起來,其中 key 是用戶的主體(principal),value 則是該主題對(duì)應(yīng)的 sessionid 組成的一個(gè)集合)。
  • 接下來計(jì)算出當(dāng)前用戶已經(jīng)有幾個(gè)有效 session 了,同時(shí)獲取允許的 session 并發(fā)數(shù)。
  • 如果當(dāng)前 session 數(shù)(sessionCount)小于 session 并發(fā)數(shù)(allowedSessions),則不做任何處理;如果 allowedSessions 的值為 -1,表示對(duì) session 數(shù)量不做任何限制。
  • 如果當(dāng)前 session 數(shù)(sessionCount)等于 session 并發(fā)數(shù)(allowedSessions),那就先看看當(dāng)前 session 是否不為 null,并且已經(jīng)存在于 sessions 中了,如果已經(jīng)存在了,那都是自家人,不做任何處理;如果當(dāng)前 session 為 null,那么意味著將有一個(gè)新的 session 被創(chuàng)建出來,屆時(shí)當(dāng)前 session 數(shù)(sessionCount)就會(huì)超過 session 并發(fā)數(shù)(allowedSessions)。
  • 如果前面的代碼中都沒能 return 掉,那么將進(jìn)入策略判斷方法 allowableSessionsExceeded 中。
  • allowableSessionsExceeded 方法中,首先會(huì)有 exceptionIfMaximumExceeded 屬性,這就是我們在 SecurityConfig 中配置的 maxSessionsPreventsLogin 的值,默認(rèn)為 false,如果為 true,就直接拋出異常,那么這次登錄就失敗了(對(duì)應(yīng) 2.2 小節(jié)的效果),如果為 false,則對(duì) sessions 按照請求時(shí)間進(jìn)行排序,然后再使多余的 session 過期即可(對(duì)應(yīng) 2.1 小節(jié)的效果)。

4.小結(jié)

如此,兩行簡單的配置就實(shí)現(xiàn)了 Spring Security 中 session 的并發(fā)管理。是不是很簡單?不過這里還有一個(gè)小小的坑,松哥將在下篇文章中繼續(xù)和大家分析。

本文案例大家可以從 GitHub 上下載:https://github.com/lenve/spring-security-samples

到此這篇關(guān)于Spring Security 自動(dòng)踢掉前一個(gè)登錄用戶的實(shí)現(xiàn)代碼的文章就介紹到這了,更多相關(guān)Spring Security 踢掉登錄用戶內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用Spring?Boot進(jìn)行單元測試詳情

    使用Spring?Boot進(jìn)行單元測試詳情

    這篇文章主要介紹了使用Spring?Boot進(jìn)行單元測試詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09
  • mybatis 如何判斷l(xiāng)ist集合是否包含指定數(shù)據(jù)

    mybatis 如何判斷l(xiāng)ist集合是否包含指定數(shù)據(jù)

    這篇文章主要介紹了mybatis 判斷l(xiāng)ist集合是否包含指定數(shù)據(jù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Java開發(fā)中最讓人頭疼的十個(gè)bug

    Java開發(fā)中最讓人頭疼的十個(gè)bug

    這篇文章主要給大家總結(jié)介紹了關(guān)于Java開發(fā)中最讓人頭疼的十個(gè)bug,同樣的bug信息,可能背后有千萬種原因,而我,永遠(yuǎn)都不知道到底是哪一個(gè),努力通過代碼積累盡可能多的bug,并將它們進(jìn)行分類,可以幫你debug節(jié)省了時(shí)間,需要的朋友可以參考下
    2021-10-10
  • Java的JDBC和橋接模式詳解

    Java的JDBC和橋接模式詳解

    下面小編就為大家?guī)硪黄狫ava的JDBC和橋接模式(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2021-09-09
  • Java中為什么不同的返回類型不算方法重載

    Java中為什么不同的返回類型不算方法重載

    這篇文章主要介紹了Java中為什么不同的返回類型不算方法重載,方法重載是指在同一個(gè)類中,定義了多個(gè)同名方法,但每個(gè)方法的參數(shù)類型或者是參數(shù)個(gè)數(shù)不同就是方法重載,下文詳細(xì)介紹,需要的小伙伴可以參考一下
    2022-05-05
  • JAVA socket.io注解原理及用法圖解

    JAVA socket.io注解原理及用法圖解

    這篇文章主要介紹了JAVA socket.io注解原理及用法圖解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • Java網(wǎng)絡(luò)編程基礎(chǔ)教程之Socket入門實(shí)例

    Java網(wǎng)絡(luò)編程基礎(chǔ)教程之Socket入門實(shí)例

    這篇文章主要介紹了Java網(wǎng)絡(luò)編程基礎(chǔ)教程之Socket入門實(shí)例,本文講解了創(chuàng)建Socket、Socket發(fā)送數(shù)據(jù)、Socket讀取數(shù)據(jù)、關(guān)閉Socket等內(nèi)容,都是最基礎(chǔ)的知識(shí)點(diǎn),需要的朋友可以參考下
    2014-09-09
  • 詳解java中this.getClass()和super.getClass()的實(shí)例

    詳解java中this.getClass()和super.getClass()的實(shí)例

    這篇文章主要介紹了詳解java中this.getClass()和super.getClass()的實(shí)例的相關(guān)資料,需要的朋友可以參考下
    2017-08-08
  • Java?Web實(shí)戰(zhàn)之使用三層架構(gòu)與Servlet構(gòu)建登錄注冊模塊

    Java?Web實(shí)戰(zhàn)之使用三層架構(gòu)與Servlet構(gòu)建登錄注冊模塊

    這篇文章介紹了如何使用三層架構(gòu)(View,?Service,?DAO)和JDBCTemplate技術(shù)在JavaWeb環(huán)境下實(shí)現(xiàn)登錄和注冊功能,詳細(xì)說明了構(gòu)建項(xiàng)目的步驟,包括創(chuàng)建數(shù)據(jù)庫表、實(shí)體類、DAO層、Service層、Servlet處理及頁面設(shè)計(jì),需要的朋友可以參考下
    2024-10-10
  • 詳解Java正則表達(dá)式中Pattern類和Matcher類

    詳解Java正則表達(dá)式中Pattern類和Matcher類

    java.util.regex是一個(gè)用正則表達(dá)式所訂制的模式來對(duì)字符串進(jìn)行匹配工作的類庫包。包括兩個(gè)類Pattern和Matcher Pattern,Pattern是一個(gè)正則表達(dá)式經(jīng)編譯后的表現(xiàn)模式。Matcher對(duì)象是一個(gè)狀態(tài)機(jī)器,它依據(jù)Pattern對(duì)象做為匹配模式對(duì)字符串展開匹配檢查。
    2016-12-12

最新評(píng)論

烟台市| 五原县| 中牟县| 铅山县| 桑日县| 肃南| 元谋县| 康平县| 兴安盟| 苗栗市| 轮台县| 托里县| 馆陶县| 韩城市| 汶川县| 金坛市| 青铜峡市| 酉阳| 昌宁县| 忻城县| 兴和县| 化州市| 闵行区| 鄯善县| 永平县| 菏泽市| 沙田区| 黄梅县| 乌苏市| 木兰县| 洪江市| 三原县| 福安市| 桃园县| 临邑县| 定日县| 张家口市| 瑞金市| 萨嘎县| 昌黎县| 松原市|