Spring Security的會(huì)話管理

      網(wǎng)友投稿 940 2025-04-05

      Spring Security的會(huì)話管理

      當(dāng)瀏覽器調(diào)用登錄接口登錄成功后,服務(wù)端會(huì)和瀏覽器之間建立一個(gè)會(huì)話Session,瀏覽器在每次發(fā)送請(qǐng)求時(shí)都會(huì)攜帶一個(gè)SessionId,服務(wù)器會(huì)根據(jù)這個(gè)SessionId來判斷用戶身份。當(dāng)瀏覽器關(guān)閉后,服務(wù)端的Session并不會(huì)自動(dòng)銷毀,需要開發(fā)者手動(dòng)在服務(wù)端調(diào)用Session銷毀方法,或者等Session過期時(shí)間到了自動(dòng)銷毀。

      會(huì)話并發(fā)管理就是指在當(dāng)前系統(tǒng)中,同一個(gè)用戶可以同時(shí)創(chuàng)建多少個(gè)會(huì)話,如果一臺(tái)設(shè)備對(duì)應(yīng)一個(gè)會(huì)話,那么可以簡(jiǎn)單理解為同一個(gè)用戶可以同時(shí)在多少臺(tái)設(shè)備上登錄,默認(rèn)同一個(gè)用戶在設(shè)備上登錄并沒有限制,可以在Security中配置。

      protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .csrf() .disable() .sessionManagement() .sessionFixation() .none() .maximumSessions(1) .expiredSessionStrategy(event -> { HttpServletResponse response = event.getResponse(); response.setContentType("application/json;charset=utf-8"); Map result = new HashMap<>(); result.put("status", 500); result.put("msg", "當(dāng)前會(huì)話已經(jīng)失效,請(qǐng)重新登錄"); String s = new ObjectMapper().writeValueAsString(result); response.getWriter().print(s); response.flushBuffer(); }); }

      在登錄過濾器AbstractAuthenticationProcessingFilter的doFilter方法中,調(diào)用attemptAuthentication方法進(jìn)行登錄認(rèn)證后,調(diào)用sessionStrategy.onAuthentication方法進(jìn)行Session并發(fā)的管理,默認(rèn)是CompositeSessionAuthenticationStrategy

      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; } if (logger.isDebugEnabled()) { logger.debug("Request is to process authentication"); } Authentication authResult; try { authResult = attemptAuthentication(request, response); if (authResult == null) { // return immediately as subclass has indicated that it hasn't completed // authentication return; } sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException failed) { logger.error( "An internal error occurred while trying to authenticate the user.", failed); unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { // Authentication failed unsuccessfulAuthentication(request, response, failed); return; } // Authentication success if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } successfulAuthentication(request, response, chain, authResult); }

      CompositeSessionAuthenticationStrategy的onAuthentication方法中遍歷集合,依次調(diào)用集合元素的onAuthentication方法

      public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException { for (SessionAuthenticationStrategy delegate : this.delegateStrategies) { if (this.logger.isDebugEnabled()) { this.logger.debug("Delegating to " + delegate); } delegate.onAuthentication(authentication, request, response); } }

      sessionStrategy是AbstractAuthenticationFilterConfigurer類的configure方法中進(jìn)行配置的,可以看到,這里從HttpSecurity的共享對(duì)象中獲取到SessionAuthenticationStrategy的實(shí)例,并設(shè)置到authFilter過濾器中

      public void configure(B http) throws Exception { PortMapper portMapper = http.getSharedObject(PortMapper.class); if (portMapper != null) { authenticationEntryPoint.setPortMapper(portMapper); } RequestCache requestCache = http.getSharedObject(RequestCache.class); if (requestCache != null) { this.defaultSuccessHandler.setRequestCache(requestCache); } authFilter.setAuthenticationManager(http .getSharedObject(AuthenticationManager.class)); authFilter.setAuthenticationSuccessHandler(successHandler); authFilter.setAuthenticationFailureHandler(failureHandler); if (authenticationDetailsSource != null) { authFilter.setAuthenticationDetailsSource(authenticationDetailsSource); } SessionAuthenticationStrategy sessionAuthenticationStrategy = http .getSharedObject(SessionAuthenticationStrategy.class); if (sessionAuthenticationStrategy != null) { authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy); } RememberMeServices rememberMeServices = http .getSharedObject(RememberMeServices.class); if (rememberMeServices != null) { authFilter.setRememberMeServices(rememberMeServices); } F filter = postProcess(authFilter); http.addFilter(filter); }

      SessionAuthenticationStrategy的實(shí)例是在SessionManagementConfigurer的init方法中存入的

      public void init(H http) { SecurityContextRepository securityContextRepository = http .getSharedObject(SecurityContextRepository.class); boolean stateless = isStateless(); if (securityContextRepository == null) { if (stateless) { http.setSharedObject(SecurityContextRepository.class, new NullSecurityContextRepository()); } else { HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository(); httpSecurityRepository .setDisableUrlRewriting(!this.enableSessionUrlRewriting); httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation()); AuthenticationTrustResolver trustResolver = http .getSharedObject(AuthenticationTrustResolver.class); if (trustResolver != null) { httpSecurityRepository.setTrustResolver(trustResolver); } http.setSharedObject(SecurityContextRepository.class, httpSecurityRepository); } } RequestCache requestCache = http.getSharedObject(RequestCache.class); if (requestCache == null) { if (stateless) { http.setSharedObject(RequestCache.class, new NullRequestCache()); } } http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy(http)); http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy()); }

      方法中 首先從HttpSecurity中獲取SecurityContextRepository實(shí)例,沒有則進(jìn)行創(chuàng)建,創(chuàng)建的時(shí)候如果是Session的創(chuàng)建策略是STATELESS,則使用NullSecurityContextRepository來保存SecurityContext,如果不是則構(gòu)建HttpSessionSecurityContextRepository,并存入HTTPSecurity共享對(duì)象中。

      如果Session的創(chuàng)建策略是STATELESS,還要把請(qǐng)求緩存對(duì)象替換為NullRequestCache

      最后構(gòu)建SessionAuthenticationStrategy的實(shí)例和InvalidSessionStrategy的實(shí)例,SessionAuthenticationStrategy的實(shí)例從getSessionAuthenticationStrategy中獲得

      private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) { if (this.sessionAuthenticationStrategy != null) { return this.sessionAuthenticationStrategy; } List delegateStrategies = this.sessionAuthenticationStrategies; SessionAuthenticationStrategy defaultSessionAuthenticationStrategy; if (this.providedSessionAuthenticationStrategy == null) { // If the user did not provide a SessionAuthenticationStrategy // then default to sessionFixationAuthenticationStrategy defaultSessionAuthenticationStrategy = postProcess( this.sessionFixationAuthenticationStrategy); } else { defaultSessionAuthenticationStrategy = this.providedSessionAuthenticationStrategy; } if (isConcurrentSessionControlEnabled()) { SessionRegistry sessionRegistry = getSessionRegistry(http); ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy( sessionRegistry); concurrentSessionControlStrategy.setMaximumSessions(this.maximumSessions); concurrentSessionControlStrategy .setExceptionIfMaximumExceeded(this.maxSessionsPreventsLogin); concurrentSessionControlStrategy = postProcess( concurrentSessionControlStrategy); RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy( sessionRegistry); registerSessionStrategy = postProcess(registerSessionStrategy); delegateStrategies.addAll(Arrays.asList(concurrentSessionControlStrategy, defaultSessionAuthenticationStrategy, registerSessionStrategy)); } else { delegateStrategies.add(defaultSessionAuthenticationStrategy); } this.sessionAuthenticationStrategy = postProcess( new CompositeSessionAuthenticationStrategy(delegateStrategies)); return this.sessionAuthenticationStrategy; }

      getSessionAuthenticationStrategy方法中把ConcurrentSessionControlAuthenticationStrategy ChangeSessionIdAuthenticationStrategy RegisterSessionAuthenticationStrategy添加到集合中,并返回代理類CompositeSessionAuthenticationStrategy

      而sessionStrategy

      Spring Security的會(huì)話管理

      ConcurrentSessionControlAuthenticationStrategy

      主要用來處理Session并發(fā)問題,并發(fā)控制實(shí)際是由這個(gè)類來完成的

      public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { final List 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); }

      從sessionRegistry中獲取當(dāng)前用戶所有未失效的SessionInformation,然后獲取當(dāng)前項(xiàng)目允許的最大session數(shù)。如果獲取到的SessionInformation實(shí)例小于當(dāng)前項(xiàng)目允許的最大session數(shù),說明當(dāng)前登錄沒有問題,直接return

      如果允許的最大session數(shù)為-1,表示應(yīng)用并不限制登錄并發(fā)數(shù),當(dāng)前登錄沒有問題,直接return

      如果兩者相等,判斷當(dāng)前sessionId是否在SessionInformation中,如果存在,直接return

      超出最大并發(fā)數(shù),進(jìn)入allowableSessionsExceeded方法

      protected void allowableSessionsExceeded(List 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 sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy); for (SessionInformation session: sessionsToBeExpired) { session.expireNow(); } }

      allowableSessionsExceeded方法中判斷exceptionIfMaximumExceeded屬性為true,則直接拋出異常,exceptionIfMaximumExceeded的屬性是在SecurityConfig中

      通過maxSessionPreventsLogin方法的值來改變,即禁止后來者的登錄,拋出異常后,本次登錄失敗。否則對(duì)查詢當(dāng)前用戶所有登錄的session按照最后一次請(qǐng)求時(shí)間進(jìn)行排序,計(jì)算出需要過期的session數(shù)量,從session集合中取出來進(jìn)行遍歷,依次調(diào)用expireNow方法讓session過期。

      ChangeSessionIdAuthenticationStrategy

      通過修改sessionId來防止會(huì)話固定攻擊。

      所謂會(huì)話固定攻擊是一種潛在的風(fēng)險(xiǎn),惡意攻擊者可能通過訪問當(dāng)前應(yīng)用程序來創(chuàng)建會(huì)話,然后誘導(dǎo)用戶以相同的會(huì)話Id登錄,進(jìn)而獲取用戶登錄身份。

      RegisterSessionAuthenticationStrategy

      在認(rèn)證成功后把HttpSession信息記錄到SessionRegistry中。

      public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal()); }

      用戶使用RememberMe的方式進(jìn)行身份認(rèn)證,則會(huì)通過SessionManagementFilter的doFilter方法觸發(fā)Session并發(fā)管理。

      SessionManagementConfigurer的configure方法中構(gòu)建了這兩個(gè)過濾器SessionManagementFilter和ConcurrentSessionFilter

      public void configure(H http) { SecurityContextRepository securityContextRepository = http .getSharedObject(SecurityContextRepository.class); SessionManagementFilter sessionManagementFilter = new SessionManagementFilter( securityContextRepository, getSessionAuthenticationStrategy(http)); if (this.sessionAuthenticationErrorUrl != null) { sessionManagementFilter.setAuthenticationFailureHandler( new SimpleUrlAuthenticationFailureHandler( this.sessionAuthenticationErrorUrl)); } InvalidSessionStrategy strategy = getInvalidSessionStrategy(); if (strategy != null) { sessionManagementFilter.setInvalidSessionStrategy(strategy); } AuthenticationFailureHandler failureHandler = getSessionAuthenticationFailureHandler(); if (failureHandler != null) { sessionManagementFilter.setAuthenticationFailureHandler(failureHandler); } AuthenticationTrustResolver trustResolver = http .getSharedObject(AuthenticationTrustResolver.class); if (trustResolver != null) { sessionManagementFilter.setTrustResolver(trustResolver); } sessionManagementFilter = postProcess(sessionManagementFilter); http.addFilter(sessionManagementFilter); if (isConcurrentSessionControlEnabled()) { ConcurrentSessionFilter concurrentSessionFilter = createConcurrencyFilter(http); concurrentSessionFilter = postProcess(concurrentSessionFilter); http.addFilter(concurrentSessionFilter); } }

      SessionManagementFilter創(chuàng)建過程中調(diào)用getSessionAuthenticationStrategy方法獲取SessionAuthenticationStrategy的實(shí)例放入過濾器中,然后配置各種回調(diào)函數(shù),最終創(chuàng)建的SessionManagementFilter過濾器放入HttpSecurity中。

      如果開啟會(huì)話并發(fā)控制(只要maximumSessions不會(huì)空就算開啟會(huì)話并發(fā)控制),則創(chuàng)建ConcurrentSessionFilter過濾器 加入到HttpSecurity中。

      總結(jié)

      用戶通過用戶名密碼發(fā)起認(rèn)證請(qǐng)求,當(dāng)認(rèn)證成功后,在AbstractAuthenticationProcessingFilter的doFilter方法中觸發(fā)Session并發(fā)管理。默認(rèn)的sessionStrategy是CompositeSessionAuthenticationStrategy,它代理了三個(gè)類ConcurrentSessionControlAuthenticationStrategy ChangeSessionIdAuthenticationStrategy RegisterSessionAuthenticationStrategy。當(dāng)前請(qǐng)求在這三個(gè)SessionAuthenticationStrategy中分別走一圈,第一個(gè)用來判斷當(dāng)前用戶的Session數(shù)是否超過限制,第二個(gè)用來修改sessionId(防止會(huì)話固定攻擊),第三個(gè)用來將當(dāng)前Session注冊(cè)到SessionRegistry中。

      如果用戶使用RememberMe的方式進(jìn)行身份認(rèn)證,則會(huì)通過SessionManagementFilter的doFilter方法觸發(fā)Session并發(fā)管理。當(dāng)用戶認(rèn)證成功后,以后的每一次請(qǐng)求都會(huì)經(jīng)過ConcurrentSessionFilter,在該過濾器中,判斷當(dāng)前會(huì)話是否過期,如果過期執(zhí)行注銷流程,如果沒有過期,更新最近一次請(qǐng)求時(shí)間。

      統(tǒng)一身份認(rèn)證服務(wù) IAM

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:如何調(diào)整WPS表格中的行距
      下一篇:如何讓幻燈片持續(xù)停留一段時(shí)間(幻燈片停留時(shí)間過短)
      相關(guān)文章
      亚洲乱码在线视频| 亚洲av日韩综合一区在线观看| 99亚洲精品高清一二区| 国产精品亚洲а∨无码播放| 国产a v无码专区亚洲av| 亚洲va中文字幕无码| 色欲aⅴ亚洲情无码AV蜜桃| 亚洲精品无码久久久久APP| 亚洲精品色播一区二区| 亚洲欧美精品午睡沙发| 亚洲乱理伦片在线观看中字| 亚洲狠狠婷婷综合久久| 亚洲av永久中文无码精品综合| 亚洲国产欧美日韩精品一区二区三区| 亚洲一线产区二线产区区| 亚洲人成人网站18禁| 亚洲欧美日韩综合俺去了| 亚洲av永久无码精品秋霞电影秋| 亚洲国产美女精品久久久| 婷婷亚洲天堂影院| 亚洲人成电影网站国产精品| 伊人久久亚洲综合| 久久91亚洲人成电影网站| 久久精品国产亚洲夜色AV网站| 久久精品亚洲综合一品| 久久精品九九亚洲精品| 亚洲成AV人综合在线观看| 国产成人精品亚洲2020| 久久亚洲精品11p| 亚洲国产黄在线观看| 亚洲情XO亚洲色XO无码| 无码久久精品国产亚洲Av影片| 亚洲精品人成在线观看| 亚洲国产精品久久丫| 亚洲一区二区观看播放| 国产亚洲视频在线观看网址| 国产亚洲美日韩AV中文字幕无码成人| 亚洲女初尝黑人巨高清| 久久亚洲日韩精品一区二区三区| 亚洲嫩草影院在线观看| 亚洲国产成人精品无码区二本|