使用Spring Security控制會話

程式猿Knight發表於2019-05-21

1.概述

在本文中,我們將說明Spring Security如何允許我們控制HTTP會話。此控制元件的範圍從會話超時到啟用併發會話和其他高階安全配置。

2.會話何時建立?

我們可以準確控制會話何時建立以及Spring Security如何與之交互:

  • always - 如果一個會話尚不存在,將始終建立一個會話
  • ifRequired - 僅在需要時建立會話(預設)
  • never - 框架永遠不會建立會話本身,但如果它已經存在,它將使用一個
  • stateless - Spring Security不會建立或使用任何會話
<http create-session="ifRequired">...</http>
Java配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
}

解此配置僅控制Spring Security的功能非常重要 - 而不是整個應用程式。如果我們不指示Spring Security,可能無法建立會話,但我們的應用程式可能會!

預設情況下,Spring Security會在需要時建立會話 - 這是“ifRequired”

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

最後,最嚴格的會話建立選項 - “stateless” - 保證應用程式根本不會建立任何會話

這是在Spring 3.1中引入的,它將有效地跳過部分Spring Security過濾器鏈。主要是會話相關的部分,如HttpSessionSecurityContextRepository,SessionManagementFilter,RequestCacheFilter

這些更嚴格的控制機制直接暗示不使用cookie,所以每個請求都需要重新進行身份驗證。這種無狀態架構適用於REST API及其無狀態約束。它們也適用於基本和摘要式身份驗證等身份驗證機制。

3. Under The Hood

在執行身份驗證過程之前,Spring Security將執行一個負責在請求之間儲存安全上下文的過濾器-SecurityContextPersistenceFilter。上下文將根據策略儲存 - 預設情況下為HttpSessionSecurityContextRepository - 它使用HTTP會話作為儲存。對於strict 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配置中使用元素:

<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.會話超時

會話超時後,如果使用者傳送的會話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()
  .expiredUrl("/sessionExpired.html")
  .invalidSessionUrl("/invalidSession.html");

6.防止使用URL引數進行會話跟蹤

在URL中公開會話資訊的安全風險越來越大(從2007年的第7位到2013年在OWASP排行榜前10位的第2位)。

從Spring 3.0開始,現在可以通過在名稱空間中設定disable-url-rewriting =“true”來禁用將jsessionid附加到URL的URL重寫邏輯。

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

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

程式設計方式

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

這將選擇儲存JSESSIONID的位置 - 在cookie或URL引數中

7. Spring Security的會話固定保護

該框架通過配置在使用者已有會話的情況但嘗試再次進行身份驗證時,提供了針對典型會話固定攻擊的保護:

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

相應的Java配置:

http.sessionManagement()
  .sessionFixation().migrateSession()

預設情況下,Spring Security啟用了此保護(“migrateSession”) - 在身份驗證時,會建立一個新的HTTP會話,舊的會話將失效,舊會話的屬性將被複制

如果這不是所需的行為,則可以使用其他兩個選項:

  • 設定“none”時,原始會話不會失效
  • 設定“newSession”時,將建立一個乾淨的會話,而不會複製舊會話中的任何屬性

8.安全會話Cookie

接下來,我們將討論如何保護會話cookie。
我們可以使用httpOnly和secure標籤來保護我們的會話cookie

  • httpOnly:如果為true,那麼瀏覽器指令碼將無法訪問cookie
  • secure:如果為true,則cookie將僅通過HTTPS連線傳送

我們可以在web.xml中為會話cookie設定這些標誌:

<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.Session使用

9.1。 Session Scoped Beans

只需在web-Context中,使用@Scope註釋宣告的bean:

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

或者使用XML:

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

然後,bean可以簡單地注入另一個bean:

@Autowired
private Foo theFoo;

Spring會將新bean繫結到HTTP Session的生命週期。

9.2。將會話注入控制器

原始HTTP會話也可以直接注入Controller方法:

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

9.3。獲取會話

當前的HTTP Session也可以通過原始Servlet API以程式設計方式獲得:

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

10.總結

在本文中,我們討論了使用Spring Security管理Sessions。此外,Spring Reference包含一個非常好的會話管理常見問題解答

與往常一樣,本文中提供的程式碼可以在Github上獲得。這是一個基於Maven的專案,因此它應該很容易匯入和執行。

相關文章