日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術(shù)文章
文章詳情頁

Spring Boot + Vue 前后端分離項目如何踢掉已登錄用戶

瀏覽:18日期:2023-01-22 14:26:37

上篇文章中,我們講了在 Spring Security 中如何踢掉前一個登錄用戶,或者禁止用戶二次登錄,通過一個簡單的案例,實現(xiàn)了我們想要的效果。

但是有一個不太完美的地方,就是我們的用戶是配置在內(nèi)存中的用戶,我們沒有將用戶放到數(shù)據(jù)庫中去。正常情況下,松哥在 Spring Security 系列中講的其他配置,大家只需要參考Spring Security+Spring Data Jpa 強強聯(lián)手,安全管理只有更簡單!一文,將數(shù)據(jù)切換為數(shù)據(jù)庫中的數(shù)據(jù)即可。

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

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

但是,在做 Spring Security 的 session 并發(fā)處理時,直接將內(nèi)存中的用戶切換為數(shù)據(jù)庫中的用戶會有問題,今天我們就來說說這個問題,順便把這個功能應(yīng)用到微人事中(https://github.com/lenve/vhr )。

本文的案例將基于Spring Security+Spring Data Jpa 強強聯(lián)手,安全管理只有更簡單!一文來構(gòu)建,所以重復(fù)的代碼我就不寫了,小伙伴們要是不熟悉可以參考該篇文章。

1.環(huán)境準(zhǔn)備

首先,我們打開Spring Security+Spring Data Jpa 強強聯(lián)手,安全管理只有更簡單!一文中的案例,這個案例結(jié)合 Spring Data Jpa 將用戶數(shù)據(jù)存儲到數(shù)據(jù)庫中去了。

然后我們將上篇文章中涉及到的登錄頁面拷貝到項目中(文末可以下載完整案例):

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7XB0viq6-1588898082940)(http://img.itboyhub.com/2020/...]

并在 SecurityConfig 中對登錄頁面稍作配置:

@Overridepublic void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers('/js/**', '/css/**', '/images/**');}@Overrideprotected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() ... .and() .formLogin() .loginPage('/login.html') .loginProcessingUrl('/doLogin') ... .and() .sessionManagement() .maximumSessions(1);}

這里都是常規(guī)配置,我就不再多說。注意最后面我們將 session 數(shù)量設(shè)置為 1。

好了,配置完成后,我們啟動項目,并行性多端登錄測試。

打開多個瀏覽器,分別進行多端登錄測試,我們驚訝的發(fā)現(xiàn),每個瀏覽器都能登錄成功,每次登錄成功也不會踢掉已經(jīng)登錄的用戶!

這是怎么回事?

2.問題分析

要搞清楚這個問題,我們就要先搞明白 Spring Security 是怎么保存用戶對象和 session 的。

Spring Security 中通過 SessionRegistryImpl 類來實現(xiàn)對會話信息的統(tǒng)一管理,我們來看下這個類的源碼(部分):

public class SessionRegistryImpl implements SessionRegistry, ApplicationListener<SessionDestroyedEvent> { /** <principal:Object,SessionIdSet> */ private final ConcurrentMap<Object, Set<String>> principals; /** <sessionId:Object,SessionInformation> */ private final Map<String, SessionInformation> sessionIds; public void registerNewSession(String sessionId, Object principal) { if (getSessionInformation(sessionId) != null) { removeSessionInformation(sessionId); } sessionIds.put(sessionId, new SessionInformation(principal, sessionId, new Date())); principals.compute(principal, (key, sessionsUsedByPrincipal) -> { if (sessionsUsedByPrincipal == null) { sessionsUsedByPrincipal = new CopyOnWriteArraySet<>(); } sessionsUsedByPrincipal.add(sessionId); return sessionsUsedByPrincipal; }); } public void removeSessionInformation(String sessionId) { SessionInformation info = getSessionInformation(sessionId); if (info == null) { return; } sessionIds.remove(sessionId); principals.computeIfPresent(info.getPrincipal(), (key, sessionsUsedByPrincipal) -> { sessionsUsedByPrincipal.remove(sessionId); if (sessionsUsedByPrincipal.isEmpty()) { sessionsUsedByPrincipal = null; } return sessionsUsedByPrincipal; }); }}

這個類的源碼還是比較長,我這里提取出來一些比較關(guān)鍵的部分:

首先大家看到,一上來聲明了一個 principals 對象,這是一個支持并發(fā)訪問的 map 集合,集合的 key 就是用戶的主體(principal),正常來說,用戶的 principal 其實就是用戶對象,松哥在之前的文章中也和大家講過 principal 是怎么樣存入到 Authentication 中的(參見: Spring Security 登錄流程),而集合的 value 則是一個 set 集合,這個 set 集合中保存了這個用戶對應(yīng)的 sessionid。 如有新的 session 需要添加,就在 registerNewSession 方法中進行添加,具體是調(diào)用 principals.compute 方法進行添加,key 就是 principal。 如果用戶注銷登錄,sessionid 需要移除,相關(guān)操作在 removeSessionInformation 方法中完成,具體也是調(diào)用 principals.computeIfPresent 方法,這些關(guān)于集合的基本操作我就不再贅述了。

看到這里,大家發(fā)現(xiàn)一個問題,ConcurrentMap 集合的 key 是 principal 對象,用對象做 key,一定要重寫 equals 方法和 hashCode 方法,否則第一次存完數(shù)據(jù),下次就找不到了,這是 JavaSE 方面的知識,我就不用多說了。

如果我們使用了基于內(nèi)存的用戶,我們來看下 Spring Security 中的定義:

public class User implements UserDetails, CredentialsContainer { private String password; private final String username; private final Set<GrantedAuthority> authorities; private final boolean accountNonExpired; private final boolean accountNonLocked; private final boolean credentialsNonExpired; private final boolean enabled; @Override public boolean equals(Object rhs) { if (rhs instanceof User) { return username.equals(((User) rhs).username); } return false; } @Override public int hashCode() { return username.hashCode(); }}

可以看到,他自己實際上是重寫了 equals 和 hashCode 方法了。

所以我們使用基于內(nèi)存的用戶時沒有問題,而我們使用自定義的用戶就有問題了。

找到了問題所在,那么解決問題就很容易了,重寫 User 類的 equals 方法和 hashCode 方法即可:

@Entity(name = 't_user')public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; private boolean accountNonExpired; private boolean accountNonLocked; private boolean credentialsNonExpired; private boolean enabled; @ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST) private List<Role> roles; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(username, user.username); } @Override public int hashCode() { return Objects.hash(username); } ... ...}

配置完成后,重啟項目,再去進行多端登錄測試,發(fā)現(xiàn)就可以成功踢掉已經(jīng)登錄的用戶了。

如果你使用了 MyBatis 而不是 Jpa,也是一樣的處理方案,只需要重寫登錄用戶的 equals 方法和 hashCode 方法即可。

3.微人事應(yīng)用

3.1 存在的問題

由于微人事目前是采用了 JSON 格式登錄,所以如果項目控制 session 并發(fā)數(shù),就會有一些額外的問題要處理。

最大的問題在于我們用自定義的過濾器代替了 UsernamePasswordAuthenticationFilter,進而導(dǎo)致前面所講的關(guān)于 session 的配置,統(tǒng)統(tǒng)失效。所有相關(guān)的配置我們都要在新的過濾器 LoginFilter 中進行配置 ,包括 SessionAuthenticationStrategy 也需要我們自己手動配置了。

這雖然帶來了一些工作量,但是做完之后,相信大家對于 Spring Security 的理解又會更上一層樓。

3.2 具體應(yīng)用

我們來看下具體怎么實現(xiàn),我這里主要列出來一些關(guān)鍵代碼,完整代碼大家可以從 GitHub 上下載:https://github.com/lenve/vhr 。

首先第一步,我們重寫 Hr 類的 equals 和 hashCode 方法,如下:

public class Hr implements UserDetails { ... ... @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Hr hr = (Hr) o; return Objects.equals(username, hr.username); } @Override public int hashCode() { return Objects.hash(username); } ... ...}

接下來在 SecurityConfig 中進行配置。

這里我們要自己提供 SessionAuthenticationStrategy,而前面處理 session 并發(fā)的是 ConcurrentSessionControlAuthenticationStrategy,也就是說,我們需要自己提供一個 ConcurrentSessionControlAuthenticationStrategy 的實例,然后配置給 LoginFilter,但是在創(chuàng)建 ConcurrentSessionControlAuthenticationStrategy 實例的過程中,還需要有一個 SessionRegistryImpl 對象。

前面我們說過,SessionRegistryImpl 對象是用來維護會話信息的,現(xiàn)在這個東西也要我們自己來提供,SessionRegistryImpl 實例很好創(chuàng)建,如下:

@BeanSessionRegistryImpl sessionRegistry() { return new SessionRegistryImpl();}

然后在 LoginFilter 中配置 SessionAuthenticationStrategy,如下:

@BeanLoginFilter loginFilter() throws Exception { LoginFilter loginFilter = new LoginFilter(); loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> { //省略 } ); loginFilter.setAuthenticationFailureHandler((request, response, exception) -> { //省略 } ); loginFilter.setAuthenticationManager(authenticationManagerBean()); loginFilter.setFilterProcessesUrl('/doLogin'); ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry()); sessionStrategy.setMaximumSessions(1); loginFilter.setSessionAuthenticationStrategy(sessionStrategy); return loginFilter;}

我們在這里自己手動構(gòu)建 ConcurrentSessionControlAuthenticationStrategy 實例,構(gòu)建時傳遞 SessionRegistryImpl 參數(shù),然后設(shè)置 session 的并發(fā)數(shù)為 1,最后再將 sessionStrategy 配置給 LoginFilter。

其實上篇文章中,我們的配置方案,最終也是像上面這樣,只不過現(xiàn)在我們自己把這個寫出來了而已。

這就配置完了嗎?沒有!session 處理還有一個關(guān)鍵的過濾器叫做 ConcurrentSessionFilter,本來這個過濾器是不需要我們管的,但是這個過濾器中也用到了 SessionRegistryImpl,而 SessionRegistryImpl 現(xiàn)在是由我們自己來定義的,所以,該過濾器我們也要重新配置一下,如下:

@Overrideprotected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() ... http.addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event -> { HttpServletResponse resp = event.getResponse(); resp.setContentType('application/json;charset=utf-8'); resp.setStatus(401); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(RespBean.error('您已在另一臺設(shè)備登錄,本次登錄已下線!'))); out.flush(); out.close(); }), ConcurrentSessionFilter.class); http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);}

在這里,我們重新創(chuàng)建一個 ConcurrentSessionFilter 的實例,代替系統(tǒng)默認(rèn)的即可。在創(chuàng)建新的 ConcurrentSessionFilter 實例時,需要兩個參數(shù):

sessionRegistry 就是我們前面提供的 SessionRegistryImpl 實例。 第二個參數(shù),是一個處理 session 過期后的回調(diào)函數(shù),也就是說,當(dāng)用戶被另外一個登錄踢下線之后,你要給什么樣的下線提示,就在這里來完成。

最后,我們還需要在處理完登錄數(shù)據(jù)之后,手動向 SessionRegistryImpl 中添加一條記錄:

public class LoginFilter extends UsernamePasswordAuthenticationFilter { @Autowired SessionRegistry sessionRegistry; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //省略 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); setDetails(request, authRequest); Hr principal = new Hr(); principal.setUsername(username); sessionRegistry.registerNewSession(request.getSession(true).getId(), principal); return this.getAuthenticationManager().authenticate(authRequest); } ... ... }}

在這里,我們手動調(diào)用 sessionRegistry.registerNewSession 方法,向 SessionRegistryImpl 中添加一條 session 記錄。

OK,如此之后,我們的項目就配置完成了。

接下來,重啟 vhr 項目,進行多端登錄測試,如果自己被人踢下線了,就會看到如下提示:

Spring Boot + Vue 前后端分離項目如何踢掉已登錄用戶

完整的代碼,我已經(jīng)更新到 vhr 上了,大家可以下載學(xué)習(xí)。

4.小結(jié)

好了,本文主要和小伙伴們介紹了一個在 Spring Security 中處理 session 并發(fā)問題時,可能遇到的一個坑,以及在前后端分離情況下,如何處理 session 并發(fā)問題。不知道小伙伴們有沒有 GET 到呢?

本文第二小節(jié)的案例大家可以從 GitHub 上下載:https://github.com/lenve/spring-security-samples

如果覺得有收獲,記得點個在看鼓勵下松哥哦~

標(biāo)簽: Spring
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
久久中文亚洲字幕| 天堂va在线高清一区| 欧美影院精品| 国产日韩免费| 成人午夜在线| 国产一区日韩欧美| 蜜臀av在线播放一区二区三区| 视频在线观看一区| 日本va欧美va欧美va精品| 久久国产视频网| 国产欧美日韩精品一区二区三区| 欧美1区2区3| 99tv成人| 久久午夜精品| 国产精品成人自拍| 国产精品免费99久久久| 美女福利一区二区三区| 国语精品一区| 国产欧美丝祙| 精品免费av在线| 日韩在线观看一区二区| 日韩国产欧美视频| 欧洲精品一区二区三区| 蜜臀va亚洲va欧美va天堂 | 中文视频一区| 国产精品嫩模av在线| 久久人人99| 国产情侣一区在线| 午夜电影亚洲| 精品久久99| 亚洲久久在线| 久久国产日韩| 欧美精品97| 亚洲深深色噜噜狠狠爱网站| 日韩一区二区在线免费| 久久精品97| 国产精品高清一区二区| 色老板在线视频一区二区| 91精品国产自产在线丝袜啪| 精品国产亚洲一区二区三区在线| 亚洲午夜免费| 亚洲免费网址| 久久精品主播| 精品72久久久久中文字幕| 日韩av中文字幕一区二区三区| 91精品国产91久久久久久黑人| 国产精品资源| 亚洲精品四区| 一区二区电影| 丝袜美腿一区二区三区| 1024精品一区二区三区| 色在线中文字幕| 麻豆免费精品视频| 国产精品99久久久久久董美香| 91精品精品| 欧美天堂视频| 国产成人精品亚洲日本在线观看| 久久香蕉网站| 粉嫩av一区二区三区四区五区| 国产日韩视频在线| 久久av免费| 久久精品欧洲| 午夜影院一区| 婷婷综合社区| 在线观看一区| 欧美视频二区| 久久中文精品| 久久在线免费| 中文字幕一区二区三区四区久久 | 日韩激情精品| 国产无遮挡裸体免费久久| 91伊人久久| 嫩呦国产一区二区三区av| 日韩欧美三级| 国产精品7m凸凹视频分类| 亚洲黄色影院| 日本a级不卡| 精品日产乱码久久久久久仙踪林| 国产剧情在线观看一区| 国产资源在线观看入口av| 五月精品视频| 欧美激情一区| 免费不卡中文字幕在线| 日韩高清电影免费| 精品72久久久久中文字幕| 91精品99| 精品亚洲免a| 日韩中文字幕麻豆| 国产成人精品亚洲线观看 | 亚洲国产欧美日本视频| 美女日韩在线中文字幕| 国产福利一区二区精品秒拍| 福利一区二区三区视频在线观看| 亚洲va在线| 日本91福利区| 欧美一区三区| 精品免费在线| 日韩国产高清在线| 九九在线精品| 成人日韩av| 欧美亚洲tv| 乱人伦精品视频在线观看| 国产一区二区三区亚洲| 亚洲综合欧美| 99久精品视频在线观看视频| 日本午夜精品| 亚洲在线网站| 国产成人免费| 国产激情精品一区二区三区| 鲁大师成人一区二区三区| 精品三级国产| 69堂免费精品视频在线播放| 九一国产精品| 亚洲天堂1区| 福利一区二区免费视频| 国产精品亚洲综合久久| 亚洲1区在线观看| 日韩一区精品视频| 久久理论电影| 天堂中文av在线资源库| 久久久久久自在自线| 老鸭窝一区二区久久精品| 蜜臀精品一区二区三区在线观看 | 999久久久91| 精品丝袜在线| 亚洲精品在线影院| 国产中文在线播放| 首页国产精品| 久久精品国产成人一区二区三区 | 男人天堂欧美日韩| 久久www成人_看片免费不卡| 91精品xxx在线观看| 精精国产xxxx视频在线野外| 日韩欧美一区二区三区在线视频 | 国产v综合v| 欧美日韩精品免费观看视完整 | 国产精品自拍区| 日韩av三区| 麻豆成人91精品二区三区| 欧美视频一区| 老色鬼精品视频在线观看播放| 欧美黄页在线免费观看| 久久av偷拍| 91精品一区二区三区综合| 五月婷婷六月综合| 中文字幕成人| 久久中文字幕一区二区| 亚洲www啪成人一区二区| 欧美网站在线| 91福利精品在线观看| 久久中文在线| 一区三区视频| 国产欧美亚洲精品a| 美女av在线免费看| 美国三级日本三级久久99| 欧美日韩a区| 激情婷婷亚洲| 国产精品一区二区精品视频观看 | 久久香蕉精品| 精品免费在线| 亚洲三级网站| 91精品蜜臀一区二区三区在线| 1024精品一区二区三区| 中文字幕在线免费观看视频| 免费在线观看日韩欧美| 麻豆精品久久久| 视频一区欧美精品| 国产精品蜜芽在线观看| 亚洲a成人v| 亚洲午夜视频| 蜜臀av在线播放一区二区三区| 国产精一区二区| 免费看的黄色欧美网站| 98精品久久久久久久| 日韩av不卡在线观看| 日韩午夜黄色| 久久精品av| 精品捆绑调教一区二区三区| 婷婷久久免费视频| 久久午夜视频| 亚洲欧洲一区| 日韩精品久久久久久久电影99爱| 久久99视频| 国产精品免费不| 欧美日韩午夜电影网| 日韩精品导航| 日韩视频1区| 亚洲97av| 日韩精品中文字幕一区二区| 亚洲欧美日韩国产| 午夜电影亚洲| 国产一级久久| 黄色国产精品| 另类中文字幕国产精品| 欧美亚洲激情| 美女国产精品| 日韩成人在线看| 国产精品videossex| 国产乱子精品一区二区在线观看 | 日韩精品一区二区三区免费观看|