Control the Session with Spring Security – 用Spring Security控制会话

最后修改: 2013年 7月 1日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to illustrate how Spring Security allows us to control our HTTP Sessions.

在本教程中,我们将说明Spring Security如何允许我们控制我们的HTTP会话。

This control ranges from a session timeout to enabling concurrent sessions and other advanced security configs.

这种控制包括从会话超时到启用并发会话和其他高级安全配置。

2. When Is the Session Created?

2.会话何时创建?2.

We can control exactly when our session gets created and how Spring Security will interact with it:

我们可以准确地控制我们的会话何时被创建,以及Spring Security将如何与之互动。

  • always – A session will always be created if one doesn’t already exist.
  • ifRequired – A session will be created only if required (default).
  • never – The framework will never create a session itself, but it will use one if it already exists.
  • stateless – No session will be created or used by Spring Security.
<http create-session="ifRequired">...</http>

Here is the Java configuration:

这里是Java配置。

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

It’s very important to understand that this configuration only controls what Spring Security does, not the entire application. Spring Security won’t create the session if we instruct it not to, but our app might!

要知道,这个配置只控制Spring Security的工作,而不是整个应用程序。如果我们指示Spring Security不创建会话,它就不会创建会话,但我们的应用程序可能会这样做。

By default, Spring Security will create a session when it needs one — this is “ifRequired“.

默认情况下,Spring Security会在需要时创建一个会话 – 这是”ifRequired“。

For a more stateless application, the “never” option will ensure that Spring Security itself won’t create any session. But if the application creates one, Spring Security will make use of it.

对于无状态应用程序,”never“选项将确保Spring Security本身不会创建任何会话。但如果应用程序创建了会话,Spring Security将利用它。

Finally, the strictest session creation option, “stateless“, is a guarantee that the application won’t create any session at all.

最后,最严格的会话创建选项”stateless“是一个保证,应用程序根本不会创建任何会话。

This was introduced in Spring 3.1 and will effectively skip parts of the Spring Security filter chain — mainly the session-related parts such as HttpSessionSecurityContextRepository, SessionManagementFilter and RequestCacheFilter.

这是在 Spring 3.1 中引入的,它将有效地跳过 Spring Security 过滤器链的一部分 – 主要是与会话相关的部分,如 HttpSessionSecurityContextRepositorySessionManagementFilterRequestCacheFilter

These more strict control mechanisms have the direct implication that cookies are not used, and so each and every request needs to be re-authenticated.

这些更严格的控制机制直接意味着不使用cookies,因此每一个请求都需要重新认证

This stateless architecture plays well with REST APIs and their Statelessness constraint. They also work well with authentication mechanisms such as Basic and Digest Authentication.

这种无状态架构与REST APIs及其无状态约束发挥得很好。它们也能很好地与认证机制(如Basic和Digest认证)一起工作。

3. Under the Hood

3.引擎盖下的

Before running the Authentication process, Spring Security will run a filter responsible for storing the Security Context between requests. This is the SecurityContextPersistenceFilter.

在运行验证过程之前,Spring Security将运行一个过滤器,负责在请求之间存储安全上下文。这就是SecurityContextPersistenceFilter

The context will be stored according to the strategy HttpSessionSecurityContextRepository by default, which uses the HTTP Session as storage.

上下文将按照默认的策略HttpSessionSecurityContextRepository进行存储,它使用HTTP Session作为存储。

For the strict create-session=”stateless” attribute, this strategy will be replaced with another — NullSecurityContextRepository — and no session will be created or used to keep the context.

对于严格的create-session=”stateless”属性,这个策略将被另一个策略–NullSecurityContextRepository所取代,将不创建或使用会话来保持上下文。

4. Concurrent Session Control

4.并发会话控制

When a user that is already authenticated tries to authenticate again, the application can deal with that event in one of a few ways. It can either invalidate the active session of the user and authenticate the user again with a new session, or allow both sessions to exist concurrently.

当一个已经被认证的用户试图再次认证时,应用程序可以通过以下几种方式之一来处理该事件。它可以使该用户的活动会话无效,用一个新的会话再次认证该用户,或者允许两个会话同时存在。

The first step in enabling the concurrent session-control support is to add the following listener in the web.xml:

启用并发session-control支持的第一步是在web.xml中添加以下监听器。

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

Or we can define it as a Bean:

或者我们可以把它定义为一个Bean。

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

This is essential to make sure that the Spring Security session registry is notified when the session is destroyed.

这对于确保Spring Security会话注册表在会话被销毁时得到通知至关重要。

In order to allow multiple concurrent sessions for the same user, the <session-management> element should be used in the XML configuration:

为了允许同一用户的多个并发会话,应在XML配置中使用<会话管理>元素。

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

Or we can do this via Java configuration:

或者我们可以通过Java配置来实现。

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

5. Session Timeout

5.会话超时

5.1. Handling the Session Timeout

5.1.处理会话超时

After the session has timed out, if the user sends a request with an expired session id, they will be redirected to a URL configurable via the namespace:

在会话超时后,如果用户以过期的会话ID发送请求,他们将被重定向到一个可通过命名空间配置的URL。

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

Similarly, if the user sends a request with a session id that is not expired but entirely invalid, they will also be redirected to a configurable URL:

同样地,如果用户用一个没有过期但完全无效的会话ID发送请求,他们也将被重定向到一个可配置的URL。

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

And here’s the corresponding Java configuration:

而这里是相应的Java配置。

http.sessionManagement()
  .expiredUrl("/sessionExpired.html")
  .invalidSessionUrl("/invalidSession.html");

5.2. Configure the Session Timeout With Spring Boot

5.2.用Spring Boot配置会话超时

We can easily configure the Session timeout value of the embedded server using properties:

我们可以使用属性轻松地配置嵌入式服务器的会话超时值。

server.servlet.session.timeout=15m

If we don’t specify the duration unit, Spring will assume it’s seconds.

如果我们不指定持续时间的单位,Spring会假定它是秒。

In a nutshell, with this configuration, the session will expire after 15 minutes of inactivity. The session is considered invalid after this period of time.

简而言之,在这种配置下,会话将在15分钟的不活动后过期。在这段时间后,会话被认为是无效的。

If we configured our project to use Tomcat, we have to keep in mind that it only supports minute precision for session timeout, with a minimum of one minute. This means that if we specify a timeout value of 170s, for example, it will result in a two-minute timeout.

如果我们将项目配置为使用Tomcat,我们必须记住,它只支持会话超时的分钟精度,最小为一分钟。这意味着,如果我们指定一个170s的超时值,例如,它将导致两分钟的超时。

Finally, it’s important to mention that even though Spring Session supports a similar property for this purpose (spring.session.timeout), if that’s not specified, the autoconfiguration will fallback to the value of the property we first mentioned.

最后,值得一提的是,即使Spring Session支持类似的属性用于此目的(spring.session.timeout),如果不指定该属性,自动配置将退回到我们第一次提到的属性值。

6. Prevent Using URL Parameters for Session Tracking

6.防止使用URL参数进行会话跟踪

Exposing session information in the URL is a growing security risk (from seventh place in 2007 to second place in 2013 on the OWASP Top 10 List).

在URL中暴露会话信息是一个不断增长的安全风险(从2007年的第七位上升到2013年OWASP Top 10名单的第二位)。

Starting with Spring 3.0, the URL rewriting logic that would append the jsessionid to the URL can now be disabled by setting the disable-url-rewriting=”true” in the <http> namespace.

从 Spring 3.0 开始,现在可以通过在 <http> 名称空间中设置 disable-url-rewriting=”true” 来禁用将附加 jsessionid 到 URL 的 URL 重写逻辑。

Alternatively, starting with Servlet 3.0, the session tracking mechanism can also be configured in the web.xml:

另外,从Servlet 3.0开始,会话跟踪机制也可以在web.xml中进行配置。

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

and programmatically:

和程序化。

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

This chooses where to store the JSESSIONID — in the cookie or in a URL parameter.

这就选择了存储JSESSIONID的位置–在cookie中或在URL参数中。

7. Session Fixation Protection With Spring Security

7.用Spring Security进行会话固定保护

The framework offers protection against typical Session Fixation attacks by configuring what happens to an existing session when the user tries to authenticate again:

该框架通过配置当用户试图再次验证时对现有会话发生的情况,提供对典型会话修复攻击的保护。

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

And here’s the corresponding Java configuration:

而这里是相应的Java配置。

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

By default, Spring Security has this protection enabled (“migrateSession“). On authentication, a new HTTP Session is created, the old one is invalidated and the attributes from the old session are copied over.

默认情况下,Spring Security启用了这种保护(”migrateSession“)。在认证时,会创建一个新的HTTP会话,旧的会话无效,旧会话的属性被复制过来。

If this is not what we want, two other options are available:

如果这不是我们想要的,还有另外两个选择。

  • When “none” is set, the original session will not be invalidated.
  • When “newSession” is set, a clean session will be created without any of the attributes from the old session being copied over.

8. Secure Session Cookie

8.安全会话cookie

Next, we’ll discuss how to secure our session cookie.

接下来,我们将讨论如何保护我们的会话cookie。

We can use the httpOnly and secure flags to secure our session cookie:

我们可以使用httpOnlysecure标志来保护我们的会话cookie

  • httpOnly: if true then browser script won’t be able to access the cookie
  • secure: if true then the cookie will be sent only over HTTPS connection

We can set those flags for our session cookie in the web.xml:

我们可以在web.xml中为我们的会话cookie设置这些标志。

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

This configuration option is available since Java servlet 3. By default, http-only is true and secure is false.

这个配置选项从Java servlet 3开始可用。默认情况下,http-only为真,secure为假。

Let’s also have a look at the corresponding Java configuration:

我们也来看看相应的Java配置。

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

If we’re using Spring Boot, we can set these flags in our application.properties:

如果我们使用Spring Boot,我们可以在我们的application.properties中设置这些标志。

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

Finally, we can also achieve this manually by using a Filter:

最后,我们也可以通过使用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. Working With the Session

9.与会议一起工作

9.1. Session Scoped Beans

9.1.会话范围内的Bean

A bean can be defined with session scope simply by using the @Scope annotation on beans declared in the web context:

只要在Web上下文中声明的Bean上使用@Scope注解,就可以用session范围来定义Bean。

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

or with XML:

或用XML。

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

Then the bean can be injected into another bean:

然后,该Bean可以被注入另一个Bean中。

@Autowired
private Foo theFoo;

And Spring will bind the new bean to the lifecycle of the HTTP Session.

而Spring将把新的Bean绑定到HTTP Session的生命周期中。

9.2. Injecting the Raw Session Into a Controller

9.2.将原始会话注入到控制器中

The raw HTTP Session can also be injected directly into a Controller method:

原始的HTTP Session也可以直接注入到Controller方法中。

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

9.3. Obtaining the Raw Session

9.3.获取原始会话

The current HTTP Session can also be obtained programmatically via the raw Servlet API:

当前的HTTP会话也可以通过raw Servlet API以编程方式获得。

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

10. Conclusion

10.结论

In this article, we discussed managing Sessions with Spring Security.

在这篇文章中,我们讨论了用Spring Security管理Sessions。

Also, the Spring Reference contains a very good FAQ on Session Management.

此外,Spring Reference中包含了一个非常好的会话管理的FAQ

As always, the code presented in this article is available over on GitHub. This is a Maven-based project, so it should be easy to import and run as it is.

像往常一样,本文介绍的代码可以在GitHub上找到over。这是一个基于Maven的项目,所以应该很容易导入并按原样运行。