servlet-authentication-rememberme.md 7.1 KB
Newer Older
dallascao's avatar
dallascao 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
# rememe-me 认证

## 概述

Remember-Me 或 Persistent-Login 身份验证是指网站能够在会话之间记住主体的身份。这通常是通过向浏览器发送一个 cookie 来实现的,在将来的会话中会检测到该 cookie,并导致自动登录。 Spring 安全性为这些操作的发生提供了必要的挂钩,并且具有两个具体的 Rememe-me 实现。一种是使用散列来保护基于 Cookie 的令牌的安全性,另一种是使用数据库或其他持久存储机制来存储生成的令牌。

注意,这两个实现都需要`UserDetailsService`。如果你使用的身份验证提供程序不使用`UserDetailsService`(例如,LDAP 提供程序),那么它将无法工作,除非你的应用程序上下文中也有`UserDetailsService` Bean。

## 一种简单的基于散列的令牌方法

这种方法使用散列来实现一个有用的 rememe-me 策略。本质上,在成功进行交互身份验证后,cookie 会被发送到浏览器,cookie 的组成如下:

```
base64(username + ":" + expirationTime + ":" +
md5Hex(username + ":" + expirationTime + ":" password + ":" + key))

username:          As identifiable to the UserDetailsService
password:          That matches the one in the retrieved UserDetails
expirationTime:    The date and time when the remember-me token expires, expressed in milliseconds
key:               A private key to prevent modification of the remember-me token
```

因此,Rememe-Me 令牌仅在指定的时间段内有效,并且前提是用户名、密码和密钥不会更改。值得注意的是,这有一个潜在的安全问题,因为捕获的 Rememe-Me 令牌可以从任何用户代理使用,直到令牌过期为止。这是与摘要身份验证相同的问题。如果委托人知道某个令牌已被捕获,他们可以很容易地更改其密码,并立即使所有发行的 Rememe-Me 令牌无效。如果需要更重要的安全性,则应该使用下一节中描述的方法。或者,根本不应该使用 Rememe-me 服务。

如果你熟悉[名称空间配置](../configuration/xml-namespace.html#ns-config)一章中讨论的主题,那么只需添加`<remember-me>`元素,就可以启用 rememe 身份验证:

```
<http>
...
<remember-me key="myAppKey"/>
</http>
```

`UserDetailsService`通常会被自动选择。如果你的应用程序上下文中有多个应用程序,那么你需要指定应该使用`user-service-ref`属性中的哪个属性,其中的值是你的`UserDetailsService` Bean 的名称。

## 持久令牌方法

这种方法是以[http://jaspan.com/improved\_persistent\_login\_cookie\_best\_practice](https://web.archive.org/web/20180819014446/http://jaspan.com/improved_persistent_login_cookie_best_practice)为基础的,对<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>作了一些小的修改。要在名称空间配置中使用这种方法,你需要提供一个数据源引用:

```
<http>
...
<remember-me data-source-ref="someDataSource"/>
</http>
```

数据库应该包含一个`persistent_logins`表,该表使用以下 SQL(或等效的 SQL)创建:

```
create table persistent_logins (username varchar(64) not null,
								series varchar(64) primary key,
								token varchar(64) not null,
								last_used timestamp not null)
```

## Remember-Me 接口和实现

remember-me 用于`UsernamePasswordAuthenticationFilter`,并通过`AbstractAuthenticationProcessingFilter`超类中的钩子实现。它也在`BasicAuthenticationFilter`中使用。钩子将在适当的时候调用一个具体的`RememberMeServices`。界面如下所示:

```
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);

void loginFail(HttpServletRequest request, HttpServletResponse response);

void loginSuccess(HttpServletRequest request, HttpServletResponse response,
	Authentication successfulAuthentication);
```

请参考 Javadoc 以更全面地讨论这些方法的作用,尽管在此阶段注意`AbstractAuthenticationProcessingFilter`只调用`loginFail()``loginSuccess()`方法。当`SecurityContextHolder`不包含`Authentication`时,`autoLogin()`方法被`RememberMeAuthenticationFilter`调用。因此,该接口为底层的 Rememe-Me 实现提供了与身份验证相关的事件的充分通知,并在候选 Web 请求可能包含 Cookie 并希望被记住时将其委托给实现。这种设计允许任意数量的 rememe-me 实现策略。我们在上文中已经看到, Spring Security 提供了两种实现方式。我们将依次讨论这些问题。

### TokenBaseDreMemberMeservices

该实现支持[一种简单的基于散列的令牌方法](#remember-me-hash-token)中描述的更简单的方法。`TokenBasedRememberMeServices`生成一个`RememberMeAuthenticationToken`,它由`RememberMeAuthenticationProvider`处理。在此身份验证提供程序和`TokenBasedRememberMeServices`之间共享`key`。此外,`TokenBasedRememberMeServices`需要一个 UserDetailsService,它可以从中检索用户名和密码以进行签名比较,并生成`RememberMeAuthenticationToken`以包含正确的`GrantedAuthority`s。应用程序应该提供某种类型的注销命令,如果用户请求,该命令会使 cookie 无效。`TokenBasedRememberMeServices`还实现了 Spring Security 的`LogoutHandler`接口,因此可以与`LogoutFilter`一起使用,以自动清除 cookie。

在应用程序上下文中启用 Remember-Me 服务所需的 bean 如下:

```
<bean id="rememberMeFilter" class=
"org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<property name="rememberMeServices" ref="rememberMeServices"/>
<property name="authenticationManager" ref="theAuthenticationManager" />
</bean>

<bean id="rememberMeServices" class=
"org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="myUserDetailsService"/>
<property name="key" value="springRocks"/>
</bean>

<bean id="rememberMeAuthenticationProvider" class=
"org.springframework.security.authentication.RememberMeAuthenticationProvider">
<property name="key" value="springRocks"/>
</bean>
```

不要忘记将`RememberMeServices`实现添加到`UsernamePasswordAuthenticationFilter.setRememberMeServices()`属性中,在`AuthenticationManager.setProviders()`列表中包含`RememberMeAuthenticationProvider`,并将`RememberMeAuthenticationFilter`添加到`FilterChainProxy`(通常在`UsernamePasswordAuthenticationFilter`之后)。

### 坚持以 DREMEMBERMESERVICE 为基础

这个类可以以与`TokenBasedRememberMeServices`相同的方式使用,但是它还需要配置一个`PersistentTokenRepository`来存储令牌。有两种标准的实现方式。

* `InMemoryTokenRepositoryImpl`仅用于测试。

* `JdbcTokenRepositoryImpl`,它将令牌存储在数据库中。

上面在[持久令牌方法](#remember-me-persistent-token)中描述了数据库模式。

---

[1](#_footnoteref_1)。本质上,用户名不包含在 cookie 中,以防止暴露有效的登录名。在本文的评论部分对此进行了讨论。

[Session Management](session-management.html)[OpenID](openid.html)