8.1.4 Authentication in a Web Application
Now let’s explore the situation where you are using Spring Security in a web application (without web.xml
security enabled). How is a user authenticated and the security context established?
现在让我们研究一下在web应用程序中使用Spring安全性的情况(没有web.xml安全启用)。如何对用户进行身份验证并建立安全上下文?
Consider a typical web application’s authentication process:
考虑一个典型的web应用程序的身份验证过程:
- You visit the home page, and click on a link.
- A request goes to the server, and the server decides that you’ve asked for a protected resource.
- As you’re not presently authenticated, the server sends back a response indicating that you must authenticate. The response will either be an HTTP response code, or a redirect to a particular web page.
- Depending on the authentication mechanism, your browser will either redirect to the specific web page so that you can fill out the form, or the browser will somehow retrieve your identity (via a BASIC authentication dialogue box, a cookie, a X.509 certificate etc.).
- The browser will send back a response to the server. This will either be an HTTP POST containing the contents of the form that you filled out, or an HTTP header containing your authentication details.
- Next the server will decide whether or not the presented credentials are valid. If they’re valid, the next step will happen. If they’re invalid, usually your browser will be asked to try again (so you return to step two above).
- The original request that you made to cause the authentication process will be retried. Hopefully you’ve authenticated with sufficient granted authorities to access the protected resource. If you have sufficient access, the request will be successful. Otherwise, you’ll receive back an HTTP error code 403, which means "forbidden".
- 您可以访问主页,并单击链接。
- 一个请求到达服务器,服务器决定您请求的是受保护的资源。
- 由于您目前没有经过身份验证,服务器将返回一个响应,指示您必须进行身份验证。响应可以是HTTP响应代码,也可以重定向到特定的web页面。
- 根据身份验证机制,浏览器将重定向到特定的web页面,以便您可以填写表单,或者浏览器将以某种方式检索您的身份(通过基本身份验证对话框、cookie、X.509证书等)。
- 浏览器将向服务器发回响应。这将是一个HTTP POST,其中包含您填写的表单的内容,或者是一个HTTP header,其中包含您的身份验证详细信息。
- 接下来,服务器将决定所提供的凭证是否有效。如果它们是有效的,下一步就会发生。如果它们无效,通常会要求您的浏览器重试一次(因此返回到上面的步骤2)。
- 将重试导致身份验证过程的原始请求。希望您已经使用足够的授权权限进行了身份验证,以访问受保护的资源。如果您有足够的访问权限,请求将会成功。否则,您将收到一个HTTP错误代码403,这意味着“禁止”。
Spring Security has distinct classes responsible for most of the steps described above. The main participants (in the order that they are used) are the ExceptionTranslationFilter
, an AuthenticationEntryPoint
and an "authentication mechanism", which is responsible for calling the AuthenticationManager
which we saw in the previous section.
Spring Security有不同的类负责上面描述的大多数步骤。主要参与者(按使用顺序)是ExceptionTranslationFilter、AuthenticationEntryPoint和“身份验证机制”,该机制负责调用我们在上一节中看到的authenticationmanager。
ExceptionTranslationFilter
ExceptionTranslationFilter
is a Spring Security filter that has responsibility for detecting any Spring Security exceptions that are thrown. Such exceptions will generally be thrown by an AbstractSecurityInterceptor
, which is the main provider of authorization services. We will discuss AbstractSecurityInterceptor
in the next section, but for now we just need to know that it produces Java exceptions and knows nothing about HTTP or how to go about authenticating a principal. Instead the ExceptionTranslationFilter
offers this service, with specific responsibility for either returning error code 403 (if the principal has been authenticated and therefore simply lacks sufficient access - as per step seven above), or launching an AuthenticationEntryPoint
(if the principal has not been authenticated and therefore we need to go commence step three).
ExceptionTranslationFilter是一个Spring安全过滤器,它负责检测抛出的任何Spring安全异常。此类异常通常由AbstractSecurityInterceptor抛出,它是授权服务的主要提供者。我们将在下一节讨论AbstractSecurityInterceptor,但是现在我们只需要知道它会产生Java异常,并且不知道HTTP或如何对主体进行身份验证。相反ExceptionTranslationFilter提供这种服务,具体负责返回错误代码403(如果校长已经过身份验证的,因此只是缺乏足够的访问——按步骤7),或启动一个AuthenticationEntryPoint(如果委托人还没有经过身份验证的,因此我们要开始第三步)。
AuthenticationEntryPoint
The AuthenticationEntryPoint
is responsible for step three in the above list. As you can imagine, each web application will have a default authentication strategy (well, this can be configured like nearly everything else in Spring Security, but let’s keep it simple for now). Each major authentication system will have its own AuthenticationEntryPoint
implementation, which typically performs one of the actions described in step 3.
AuthenticationEntryPoint负责上述列表中的第三步。正如您可以想象的那样,每个web应用程序都有一个默认的身份验证策略(这个策略可以像Spring Security中的几乎所有其他策略一样配置,但是现在让我们保持简单)。每个主要的身份验证系统都有自己的AuthenticationEntryPoint实现,它通常执行步骤3中描述的操作之一。
Authentication Mechanism
Once your browser submits your authentication credentials (either as an HTTP form post or HTTP header) there needs to be something on the server that "collects" these authentication details. By now we’re at step six in the above list. In Spring Security we have a special name for the function of collecting authentication details from a user agent (usually a web browser), referring to it as the "authentication mechanism". Examples are form-base login and Basic authentication. Once the authentication
details have been collected from the user agent, an Authentication
"request" object is built and then presented to the AuthenticationManager
.
一旦浏览器提交了身份验证凭证(以HTTP表单post或HTTP header的形式),服务器上就需要“收集”这些身份验证细节。到目前为止,我们已经完成了上述列表中的第6步。在Spring Security中,我们为从用户代理(通常是web浏览器)收集身份验证细节的功能取了一个特殊的名称,将其称为“身份验证机制”。示例是基于表单的登录和基本身份验证。从用户代理收集身份验证详细信息之后,将构建身份验证“请求”对象,然后将其呈现给AuthenticationManager。
After the authentication mechanism receives back the fully-populated Authentication
object, it will deem the request valid, put the Authentication
into the SecurityContextHolder, and cause the original request to be retried (step seven above). If, on the other hand, the AuthenticationManager
rejected the request, the authentication mechanism will ask the user agent to retry (step two above).
身份验证机制接收到完整填充的身份验证对象后,将认为请求有效,将Authentication
放入SecurityContextHolder
中,并导致重新尝试原始请求(上面的步骤7)。另一方面,如果AuthenticationManager
拒绝请求,身份验证机制将要求用户代理重试(上面的步骤2)。
Storing the SecurityContext between requests
在请求之间存储SecurityContext
Depending on the type of application, there may need to be a strategy in place to store the security context between user operations. In a typical web application, a user logs in once and is subsequently identified by their session Id. The server caches the principal information for the duration session. In Spring Security, the responsibility for storing the SecurityContext
between requests falls to the SecurityContextPersistenceFilter
, which by default stores the context as an HttpSession attribute between HTTP requests. It restores the context to the SecurityContextHolder
for each request and, crucially, clears the SecurityContextHolder
when the request completes. You shouldn’t interact directly with the HttpSession
for security purposes. There is simply no justification for doing so - always use the SecurityContextHolder
instead.
根据应用程序的类型,可能需要适当的策略来存储用户操作之间的安全上下文。在典型的web应用程序中,用户登录一次,然后由其会话Id标识。在Spring Security中,在请求之间存储SecurityContext的责任落在SecurityContextPersistenceFilter
身上,它在默认情况下将上下文作为HTTP请求之间的HttpSession属性存储。它将每个请求的上下文恢复到SecurityContextHolder
,最重要的是,在请求完成时清除SecurityContextHolder
。出于安全目的,您不应该直接与HttpSession交互。这样做没有任何理由——总是使用SecurityContextHolder
。
Many other types of application (for example, a stateless RESTful web service) do not use HTTP sessions and will re-authenticate on every request. However, it is still important that the SecurityContextPersistenceFilter
is included in the chain to make sure that the SecurityContextHolder
is cleared after each request.
许多其他类型的应用程序(例如,无状态RESTful web服务)不使用HTTP会话,而是对每个请求重新进行身份验证。但是,仍然需要在链中包含SecurityContextPersistenceFilter
,以确保在每个请求之后清除SecurityContextHolder
。
In an application which receives concurrent requests in a single session, the sameSecurityContext
instance will be shared between threads. Even though aThreadLocal
is being used, it is the same instance that is retrieved from theHttpSession
for each thread. This has implications if you wish to temporarily change the context under which a thread is running. If you just useSecurityContextHolder.getContext()
, and callsetAuthentication(anAuthentication)
on the returned context object, then theAuthentication
object will change in all concurrent threads which share the sameSecurityContext
instance. You can customize the behaviour ofSecurityContextPersistenceFilter
to create a completely newSecurityContext
for each request, preventing changes in one thread from affecting another. Alternatively you can create a new instance just at the point where you temporarily change the context. The methodSecurityContextHolder.createEmptyContext()
always returns a new context instance.
在一个在单个会话中接收并发请求的应用程序中,相同的SecurityContext实例将在线程之间共享。尽管使用的是ThreadLocal,但是从HttpSession中为每个线程检索的实例是相同的。如果您希望临时更改线程所运行的上下文,那么这将产生影响。如果您只是使用SecurityContext.getcontext()
,并对返回的上下文对象调用setAuthentication(anAuthentication),那么身份验证对象将在共享相同SecurityContext实例的所有并发线程中发生变化。您可以自定义SecurityContextPersistenceFilter
的行为,为每个请求创建一个全新的SecurityContext,防止一个线程中的更改影响另一个线程。或者,您可以只在临时更改上下文的地方创建一个新实例。方法SecurityContextHolder.createemptycontext()
总是返回一个新的上下文实例。