1. 概述
在本教程中,我們將演示 Spring Security 如何允許我們控制我們的 HTTP 會話。
這種控制範圍從會話超時到啓用併發會話和其他高級安全配置。
2. 會話何時創建?
我們可以精確控制我們的會話何時創建以及 Spring Security 將如何與之交互:
- always – 如果不存在會話,則會話始終會被創建。
- ifRequired – 只有在需要時 (default )才會創建會話。
- never – 框架不會自行創建會話,但如果已存在會話,則會使用它。
- stateless – Spring Security 不會創建或使用會話。
<http create-session="ifRequired">...</http>以下是 Java 配置:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.sessionManagement(httpSecuritySessionManagementConfigurer ->
httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
return http.build();
}務必理解,
對於更具無狀態性的應用程序, “stateless” ), 是一種 首次引入,並有效地跳過了 Spring Security 過濾器鏈中的部分內容——主要是一些與會話相關的部分,例如 SessionManagementFilter 和 cookies 不會被使用,因此 <listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener> 我們也可以將其定義為 Bean: 這對於確保 Spring Security 會話註冊表在會話銷燬時被通知至關重要。 為了允許同一用户同時擁有多個會話,應在 XML 配置中<session-management> 元素。 我們也可以通過 Java 配置來完成此操作: 會話超時是指用户在不活動一段時間後,系統會自動將其會話終止,並將其重新定向到登錄頁面。這是一種安全措施,旨在保護用户帳户免受未經授權的訪問。 如果會話已超時,並且用户發送包含 過期會話 ID 的請求,則用户將被重定向到通過命名空間配置的 URL: 同樣,如果用户發送一個會話 ID 無效但未過期,也會被重定向到可配置的 URL: 以下是相應的 Java 配置: 我們可以通過屬性輕鬆配置嵌入式服務器的會話超時時間: 如果未指定時間單位,Spring 默認假設為秒。 簡而言之,通過這種配置,當會話無操作 15 分鐘後,會話將過期。在此期間,會話將被視為無效。 如果我們的項目配置使用 Tomcat,則需要注意的是,Tomcat 僅支持會話超時一分鐘精度,且最小值為一分鐘。這意味着如果指定超時值為 170s,例如,將會產生兩分鐘的超時。 最後,值得一提的是,雖然 Spring Session 支持類似屬性用於此目的 (spring.session.timeout),但如果未指定該屬性,則會自動回退到我們之前提到的值。 將會話信息暴露在 URL 中是一個日益增長的 安全風險 (自 2007 年第七名,到 2013 年第二名,在 OWASP Top 10 列表中)。 從 Spring 3.0 開始,用於將 jsessionid 追加到 URL 的 URL 重寫邏輯現在可以通過在 <http> 命名空間中設置 disable-url-rewriting=”true” 來禁用。 或者,從 Servlet 3.0 開始,會話跟蹤機制也可以在 web.xml 中配置: 以及通過編程方式: 它選擇將 JSESSIONID 存儲在 Cookie 中還是 URL 參數中。 該框架通過配置當用户嘗試再次認證時,對現有會話的處理方式,來提供對典型的 Session 劫持攻擊的保護。 以下是相應的 Java 配置: 默認情況下,Spring Security 啓用了該保護機制 (“migrateSession”)。 在身份驗證過程中,會創建一個新的 HTTP Session,舊的 Session 被無效化,並且舊 Session 的屬性會被複制到新的 Session 中。 如果這不是我們想要的結果,還有兩個其他的選項: 接下來,我們將討論如何安全地配置我們的會話Cookie。
我們可以將這些標誌設置為我們的會話Cookie,在<em>web.xml</em>中進行配置。 此配置選項自 Java Servlet 3 版本開始可用。默認情況下,http-only 為 true,secure 為 false。 下面我們來看對應的 Java 配置: 如果使用 Spring Boot,我們可以通過在 application.properties 文件中設置這些標誌: 最後,我們還可以手動實現這一點,通過使用過濾器: 一個 Bean 可以通過在 Web 上下文中聲明 Bean 時使用 @Scope 註解來定義為具有會話範圍。 或者使用XML: 然後,這個 Bean 可以注入到另一個 Bean 中: Spring 將新的 Bean 綁定到 HTTP Session 的生命週期。 可以使用原始 HTTP 會話直接注入到 Controller 方法中: 當前 HTTP 會話也可以通過 原始 Servlet API 編程方式獲取: 此外,Spring Reference 包含一份關於會話管理的優秀 常見問題解答。@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}<http ...>
<session-management>
<concurrency-control max-sessions="2" />
</session-management>
</http>@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().maximumSessions(2)
}5. 會話超時
5.1. 處理會話超時
<session-management>
<concurrency-control expired-url="/sessionExpired.html" ... />
</session-management><session-management invalid-session-url="/invalidSession.html">
...
</session-management>http.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer
.expiredUrl("/sessionExpired")
.invalidSessionUrl("/invalidSession"));5.2. 使用 Spring Boot 配置會話超時時間
server.servlet.session.timeout=15m6. 禁用使用 URL 參數進行會話跟蹤
<session-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.COOKIE));7. 使用 Spring Security 防禦 Session 劫持攻擊
<session-management session-fixation-protection="migrateSession"> ...http.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionFixation().migrateSession());
8. <strong>安全會話Cookie</strong>
<session-config>
<session-timeout>1</session-timeout>
<cookie-config>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>
</session-config>public class MainWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext sc) throws ServletException {
// ...
sc.getSessionCookieConfig().setHttpOnly(true);
sc.getSessionCookieConfig().setSecure(true);
}
}server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=truepublic class SessionFilter implements Filter {
@Override
public void doFilter(
ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
Cookie[] allCookies = req.getCookies();
if (allCookies != null) {
Cookie session =
Arrays.stream(allCookies).filter(x -> x.getName().equals("JSESSIONID"))
.findFirst().orElse(null);
if (session != null) {
session.setHttpOnly(true);
session.setSecure(true);
res.addCookie(session);
}
}
chain.doFilter(req, res);
}
}9. Working With the Session
9.1. 基於會話的 Bean
@Component
@Scope("session")
public class Foo { .. }<bean id="foo" scope="session"/>@Autowired
private Foo theFoo;9.2. 將原始會話注入到控制器中
@RequestMapping(..)
public void fooMethod(HttpSession session) {
session.setAttribute(Constants.FOO, new Foo());
//...
Foo foo = (Foo) session.getAttribute(Constants.FOO);
}9.3. 獲取原始會話
ServletRequestAttributes attr = (ServletRequestAttributes)
RequestContextHolder.currentRequestAttributes();
HttpSession session= attr.getRequest().getSession(true); // true == allow create10. 結論
在本文中,我們討論了使用 Spring Security 管理會話。