Control the Session with Spring Security

Spring Security
Remote
1
06:20 PM · Nov 29 ,2025

1. 概述

在本教程中,我們將演示 Spring Security 如何允許我們控制我們的 HTTP 會話。

這種控制範圍從會話超時到啓用併發會話和其他高級安全配置。

2. 會話何時創建?

我們可以精確控制我們的會話何時創建以及 Spring Security 將如何與之交互:

  • always – 如果不存在已存在的會話,則會話將始終被創建。
  • ifRequired – 僅在需要時才創建會話(默認)。
  • 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();
}

務必理解,此配置僅控制 Spring Security 的行為,而不是整個應用程序。 Spring Security 不會在我們指示它不這樣做時創建會話,但我們的應用程序可能會!

默認情況下,Spring Security 會在需要時創建會話 — 這是“ifRequired”。

對於 一個無狀態應用程序, “never” 選項將確保 Spring Security 本身不會創建任何會話。 但是,如果應用程序創建了會話,Spring Security 將會使用它。

最嚴格的會話創建選項是“stateless”,它是一個 保證 Spring Security 不會創建任何會話

此選項在 Spring 3.1 中 引入,並且有效地會跳過 Spring Security 過濾器鏈中的部分內容——主要是一些與會話相關的部分,例如 HttpSessionSecurityContextRepositorySessionManagementFilterRequestCacheFilter

這些更嚴格的控制機制意味着 cookie 不會被使用,因此 每個請求都需要重新進行身份驗證

這種無狀態架構與 REST API 和它們的無狀態約束非常契合。 它們也與 Basic 和 Digest 身份驗證等身份驗證機制非常有效。

3. 內部機制

在運行身份驗證過程之前,Spring Security 將運行一個過濾器,負責在請求之間存儲 Security Context。這個過濾器是 SecurityContextPersistenceFilter

上下文將根據策略 HttpSessionSecurityContextRepository 默認存儲,該策略使用 HTTP Session 作為存儲。

對於嚴格的 create-session=”stateless” 屬性,該策略將被另一個策略替換——NullSecurityContextRepository,並且 不會創建或使用任何會話以保持上下文。

4. 併發會話控制

當一個已認證的用户嘗試再次 認證,應用程序可以以幾種方式處理該事件。它可以無效當前用户的活動會話,並使用新的會話重新認證用户,也可以允許兩個會話併發存在。

啓用併發 會話控制 支持的第一步是在 web.xml 中添加以下監聽器:


<listener>
    <listener-class>
      org.springframework.security.web.session.HttpSessionEventPublisher
    </listener-class>
</listener>

或者,我們可以將其定義為 Bean:

@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
    return new HttpSessionEventPublisher();
}

這對於確保 Spring Security 會話註冊器在會話被銷燬時被 通知至關重要。

為了允許同一用户同時擁有多個併發會話,應在 XML 配置中使用<session-management>元素:


<http ...>
    <session-management>
        <concurrency-control max-sessions="2" />
    </session-management>
</http>

或者,我們可以通過 Java 配置完成此操作:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.sessionManagement().maximumSessions(2)
}

5. 會話超時時間

5.1. 處理會話超時

在會話超時後,如果用户發送帶有 過期會話 ID

的請求,他們將被重定向到通過命名空間可配置的 URL:

<session-management>
    <concurrency-control expired-url="/sessionExpired.html" ... />
</session-management>

同樣,如果用户發送帶有未過期的但完全無效的會話 ID 的請求,他們也將被重定向到可配置的 URL:

<session-management invalid-session-url="/invalidSession.html">
    ...
</session-management>

以下是相應的 Java 配置:

http.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer
  .expiredUrl("/sessionExpired")
  .invalidSessionUrl("/invalidSession"));

5.2. 使用 Spring Boot 配置會話超時時間

我們可以使用屬性輕鬆配置嵌入服務器的會話超時時間:

server.servlet.session.timeout=15m

如果未指定時間單位,Spring 將假定為秒。

簡而言之,通過此配置,在無活動狀態 15 分鐘後會話將過期。在這一段時間後,會話將被視為無效。

如果我們的項目配置為使用 Tomcat,則需要記住它僅支持秒級精度,且最小值為 1 分鐘。這意味着如果指定超時值為 170s,例如,則會產生 2 分鐘的超時。

最後,值得注意的是,儘管 Spring Session 支持類似屬性用於此目的 (spring.session.timeout),但如果未指定該屬性,則會自動配置將回退到前面提到的屬性值。

6. 阻止使用 URL 參數進行會話跟蹤

將會話信息暴露在 URL 中是一個日益增長的 安全風險 (從 2007 年第七名到 2013 年第二名在 OWASP Top 10 列表中)。

從 Spring 3.0 開始,將 jsessionid 追加到 URL 的 URL 重寫邏輯現在可以通過在 <http> 命名空間中設置 disable-url-rewriting=”true” 來禁用。

或者,從 Servlet 3.0 開始,會話跟蹤機制也可以在 web.xml 中配置:

<session-config>
     <tracking-mode>COOKIE</tracking-mode>
</session-config>

以及程序化:

servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.COOKIE));

這選擇在 cookie 中還是在 URL 參數中存儲 JSESSIONID

7. 使用 Spring Security 防禦會話固定攻擊

該框架通過配置用户嘗試再次認證時,對現有會話的處理,來提供對典型會話固定攻擊的保護:

<session-management session-fixation-protection="migrateSession"> ...

以下是對應的 Java 配置:

http.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionFixation().migrateSession());

默認情況下,Spring Security 啓用了此保護 (“migrateSession“)。在認證時,會創建一個新的 HTTP 會話,舊的會話失效,並且舊會話的屬性被複制到新會話中。

如果這不是我們想要的結果,還有兩個其他的選項可用:

  • 當“none” 設置時,原始會話不會失效。
  • 當“newSession” 設置時,會創建一個沒有從舊會話中複製任何屬性的乾淨會話。

8. 安全會話 Cookie

接下來,我們將討論如何安全我們的會話 Cookie。

我們可以使用 httpOnly 標誌和 secure 標誌來安全我們的會話 Cookie:

  • httpOnly: 如果為 true,則瀏覽器腳本將無法訪問 Cookie
  • secure: 如果為 true,則 Cookie 只會通過 HTTPS 連接發送

我們可以將這些標誌設置為會話 Cookie 在 web.xml 中:

<session-config>
    <session-timeout>1</session-timeout>
    <cookie-config>
        <http-only>true</http-only>
        <secure>true</secure>
    </cookie-config>
</session-config>

此配置選項適用於 Java Servlet 3. 默認情況下,http-only 為 true,secure 為 false。

讓我們也看看相應的 Java 配置:

public class MainWebAppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext sc) throws ServletException {
        // ...
        sc.getSessionCookieConfig().setHttpOnly(true);        
        sc.getSessionCookieConfig().setSecure(true);        
    }
}

如果我們在 Spring Boot 中使用,我們可以將這些標誌設置為在 application.properties:

server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true

最後,我們還可以手動通過使用 Filter 來實現:

public 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. 工作中與會話

9.1. 會話範圍的 Bean

可以使用 會話 作用域定義 Bean,只需在 Web 上下文中聲明的 Bean 上使用 @Scope 註解:

@Component
@Scope("session")
public class Foo { .. }

或者使用 XML:

<bean id="foo" scope="session"/>

然後可以將該 Bean 注入到另一個 Bean 中:

@Autowired
private Foo theFoo;

Spring 將該新 Bean 綁定到 HTTP 會話的生命週期。

9.2. 將原始會話注入到控制器中

原始 HTTP 會話也可以直接注入到 控制器 方法中:

@RequestMapping(..)
public void fooMethod(HttpSession session) {
    session.setAttribute(Constants.FOO, new Foo());
    //...
    Foo foo = (Foo) session.getAttribute(Constants.FOO);
}

9.3. 獲取原始會話

當前的 HTTP 會話也可以通過 原始 Servlet API 編程方式獲取:

ServletRequestAttributes attr = (ServletRequestAttributes) 
    RequestContextHolder.currentRequestAttributes();
HttpSession session= attr.getRequest().getSession(true); // true == allow create

10. 結論

在本文中,我們討論了使用 Spring Security 管理會話。

此外,Spring Reference 包含一份關於會話管理的非常好的 常見問題解答

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.