SpringBoot從入門到精通系列(專欄導(dǎo)航)
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
在登錄過濾器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
getSessionAuthenticationStrategy方法中把ConcurrentSessionControlAuthenticationStrategy ChangeSessionIdAuthenticationStrategy RegisterSessionAuthenticationStrategy添加到集合中,并返回代理類CompositeSessionAuthenticationStrategy
而sessionStrategy
ConcurrentSessionControlAuthenticationStrategy
主要用來處理Session并發(fā)問題,并發(fā)控制實(shí)際是由這個(gè)類來完成的
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { final List
從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
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)容。