天天看點

Spring Security系列(8)-會話管理及登入退出詳解

會話管理

使用者認證通過後,為了避免使用者的每次操作都進行認證可将使用者的資訊保證在會話中。會話就是系統為了保持目前 使用者的登入狀态所提供的機制,常見的有基于session方式、基于token方式等。

基于session的認證方式由Servlet規範定制,服務端要存儲session資訊需要占用記憶體資源,用戶端需要支援 cookie;基于token的方式則一般不需要服務端存儲token,并且不限制用戶端的存儲方式。如今移動網際網路時代 更多類型的用戶端需要接入系統,系統多是采用前後端分離的架構進行實作,是以基于token的方式更适合。

session

使用者認證成功後,在服務端生成使用者相關的資料儲存在session(目前會話)中,發給用戶端的 sesssion_id 存放到 cookie 中,這樣使用者用戶端請求時帶上 session_id 就可以驗證伺服器端是否存在 session 數 據,以此完成使用者的合法校驗,當使用者退出系統或session過期銷毀時,用戶端的session_id也就無效了。基于session的認證方式目前不常用了,而且不适用于現在分布式微服務場景。

Spring Security系列(8)-會話管理及登入退出詳解

token

,使用者認證成功後,服務端生成一個token發給用戶端,用戶端可以放到 cookie 或 localStorage 等存儲中,每次請求時帶上 token,服務端收到token通過驗證後即可确認使用者身份。

Spring Security系列(8)-會話管理及登入退出詳解

Spring Security會話管理

使用者認證通過後,為了避免使用者的每次操作都進行認證可将使用者的資訊儲存在會話中。spring security提供會話管理,認證通過後将身份資訊放入SecurityContextHolder上下文,SecurityContext與目前線程進行綁定,友善擷取使用者身份。

SecurityContext context = SecurityContextHolder.getContext();
        Authentication authentication = context.getAuthentication();
        MyUser principal = (MyUser) authentication.getPrincipal();
           

可以查到到登入後,在浏覽器中儲存了session_id。

Spring Security系列(8)-會話管理及登入退出詳解

session建立政策

Spring Security下的枚舉SessionCreationPolicy類,管理session的建立政策。

/**
 * Specifies the various session creation policies for Spring Security.
 * 指定 Spring Security 的各種會話建立政策
 */
public enum SessionCreationPolicy {
	ALWAYS,
	NEVER,
	IF_REQUIRED,
	STATELESS
}
           

可以通過以下選項準确控制會話何時建立以及Spring Security如何與之互動:

機制 描述
ALWAYS 如果沒有session存在就建立一個
NEVER SpringSecurity 将不會建立Session,但是如果應用中其他地方建立了Session,那麼Spring Security将會使用它。
IF_REQUIRED 如果需要就建立一個Session(預設)登入時
STATELESS SpringSecurity将絕對不會建立Session,也不使用Session

通過以下配置方式對該選項進行配置:

@Override protected void configure(HttpSecurity http) throws Exception { 
	http.sessionManagement() 
	.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) 
}
           

預設情況下,Spring Security會為每個登入成功的使用者會建立一個Session,就是ifRequired 。

若選用never,則訓示Spring Security對登入成功的使用者不建立Session了,但若你的應用程式在某地方建立了 session,那麼Spring Security會用它的。

若使用stateless,則說明Spring Security對登入成功的使用者不會建立Session了,你的應用程式也不會允許建立 session。并且它會暗示不使用cookie,是以每個請求都需要重新進行身份驗證。這種無狀态架構适用于REST API 及其無狀态認證機制。

配置項

SessionManagementConfigurer是會話管理配置類,它提供了很多配置項及方法處理session。

public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractHttpConfigurer<SessionManagementConfigurer<H>, H> {

	private final SessionAuthenticationStrategy DEFAULT_SESSION_FIXATION_STRATEGY = createDefaultSessionFixationProtectionStrategy();

	private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = this.DEFAULT_SESSION_FIXATION_STRATEGY;

	private SessionAuthenticationStrategy sessionAuthenticationStrategy;

	private SessionAuthenticationStrategy providedSessionAuthenticationStrategy;

	// 設定session id無效時要應用的政策InvalidSessionStrategy。如果設定了該屬性,浏覽器端提供了無效的session id時,伺服器端會調用該政策對象。
	// 通常情況下,這裡也會是一個跳轉政策對象SimpleRedirectInvalidSessionStrategy。
	private InvalidSessionStrategy invalidSessionStrategy;

	private SessionInformationExpiredStrategy expiredSessionStrategy;

	private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<>();

	private SessionRegistry sessionRegistry;
	// 設定每個使用者的最大并發會話數量。此方法傳回一個ConcurrencyControlConfigurer,這也是一個安全配置器,設定每個使用者會話數量超出單使用者最大會話并發數時如何處理。
	private Integer maximumSessions;

	private String expiredUrl;

	private boolean maxSessionsPreventsLogin;
	// 設定會話建立政策SessionCreationPolicy。如果不設定,則會嘗試使用公共對象中設定的SessionCreationPolicy。
	// 如果公共對象中也沒有設定會話建立政策,則使用預設的會話建立政策SessionCreationPolicy.IF_REQUIRED。
	private SessionCreationPolicy sessionPolicy;
	// 調用該方法設定屬性enableSessionUrlRewriting.如果enableSessionUrlRewriting屬性被設定為true,
	// 使用HttpServletResponse#encodeRedirectURL(String)/HttpServletResponse#encodeURL(String)時,
	// 允許将HTTP session資訊重寫到URL中。該方法對應的屬性enableSessionUrlRewriting預設為false,不允許Http session重寫到URL。
	private boolean enableSessionUrlRewriting;

	// 設定session id無效時的跳轉URL。如果設定了該屬性,浏覽器端提供了無效的session id時,伺服器端會将其跳轉到所設定的URL。
	private String invalidSessionUrl;
	// 定義SessionAuthenticationStrategy抛出異常時要跳轉的URL。如果未設定該屬性,SessionAuthenticationStrategy抛出異常時,會傳回402給用戶端。
	private String sessionAuthenticationErrorUrl;
	// 定義SessionAuthenticationStrategy抛出異常時要應用的認證失敗處理器AuthenticationFailureHandler。
	// 如果未設定該屬性,SessionAuthenticationStrategy抛出異常時,會傳回402給用戶端。
	private AuthenticationFailureHandler sessionAuthenticationFailureHandler;
	// -----省略大量源碼
}
           

會話逾時時間

可以再sevlet容器中設定Session的逾時時間,如下設定Session有效期為3600s

server:
  servlet:
    session:
      timeout: 3600s
           

跳轉路徑

session逾時之後,可以通過Spring Security 設定跳轉的路徑,expired指session過期,invalidSession指傳入的sessionid無效。

http.sessionManagement() 
.expiredUrl("/login‐view?error=EXPIRED_SESSION") 
.invalidSessionUrl("/login‐view?error=INVALID_SESSION");
           

安全會話

我們可以使用httpOnly和secure标簽來保護我們的會話cookie: httpOnly:如果為true,那麼浏覽器腳本将無法通路cookie secure:如果為true,則cookie将僅通過HTTPS連接配接發送 spring boot 配置檔案:

server:
  servlet:
    session:
      timeout: 3600s
      cookie:
        http-only: true
        secure: true
           

登入退出

之前我們了解到,spring security提供了登入頁面,那麼是否也提供了登入退出頁面呢?答案是肯定的,我們隻要輸入http://localhost:9090/logout,就出現了登入退出頁面。

Spring Security系列(8)-會話管理及登入退出詳解

當點選了退出後,跳轉到了登入頁。

Spring Security系列(8)-會話管理及登入退出詳解

登入退出的配置類為LogoutConfigurer,在HttpSecurity 配置加載時,會建立該配置項。

// HttpSecurity 代碼片段
	public LogoutConfigurer<HttpSecurity> logout() throws Exception {
		return getOrApply(new LogoutConfigurer<>());
	}

           

LogoutConfigurer核心配置如下:

public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractHttpConfigurer<LogoutConfigurer<H>, H> {
	// 退出時要執行的處理器,可以有多個, contextLogoutHandler 總是最後一個
	private List<LogoutHandler> logoutHandlers = new ArrayList<>();

	private SecurityContextLogoutHandler contextLogoutHandler = new SecurityContextLogoutHandler();

	// 成功登出跳轉路徑
	private String logoutSuccessUrl = "/login?logout";
	// 登出成處理器
	private LogoutSuccessHandler logoutSuccessHandler;
	// 登出頁面路徑
	private String logoutUrl = "/logout";

	private RequestMatcher logoutRequestMatcher;
	// 是否允許所有
	private boolean permitAll;
	// 是否自定義成功登出
	private boolean customLogoutSuccess;
}
           

可以自定義退出功能:

@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() //... .
	and() .logout() 
 	.logoutUrl("/logout") 
  	.logoutSuccessUrl("/login‐view?logout") 
   	.logoutSuccessHandler(logoutSuccessHandler) 
    .addLogoutHandler(logoutHandler)
    .invalidateHttpSession(true); 
}
           

當退出操作出發時,将發生:

  • 使HTTP Session 無效
  • 清除 SecurityContextHolder
  • 跳轉到 /login-view?logout

LogoutHandler接口定義了退出的相關操作。

public interface LogoutHandler {

	/**
	 * Causes a logout to be completed. The method must complete successfully.
	 *
	 * @param request        the HTTP request
	 * @param response       the HTTP response
	 * @param authentication the current principal details
	 *                       導緻登出完成。該方法必須成功完成
	 */
	void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication);
}
           

LogoutHandler的實作類如下圖:

Spring Security系列(8)-會話管理及登入退出詳解
  • PersistentTokenBasedRememberMeServices 基于持久化token的RememberMe功能的相關清理
  • TokenBasedRememberMeService 基于token的RememberMe功能的相關清理
  • CookieClearingLogoutHandler 退出時Cookie的相關清理
  • CsrfLogoutHandler 負責在退出時移除csrfToken
  • SecurityContextLogoutHandler 退出時SecurityContext的相關清理