# Spring Session-按用户名查找

本指南描述了如何使用 Spring Session 通过用户名查找会话。

|   |你可以在[FindByUserName 应用程序](#findbyusername-sample)中找到完整的指南。|
|---|---------------------------------------------------------------------------------------------|

[Index](../index.html)

## 假设

该指南假定你已经通过使用内置的 Redis 配置支持向应用程序添加了 Spring Session 。该指南还假定你已经对应用程序应用了 Spring 安全性。然而，我们的指南是有点通用的目的，可以应用于任何技术，只需最小的变化，这一点我们将在后面的指南中进行讨论。

|   |如果需要学习如何将 Spring Session 添加到项目中，请参见[样本和指南](../#samples)列表|
|---|--------------------------------------------------------------------------------------------------------------------|

## 关于样本

我们的样例使用此功能使可能受到危害的用户会话无效。考虑以下场景：

* 用户到库并对应用程序进行身份验证。

* 用户回家后发现自己忘了注销。

* 用户可以使用位置、创建时间、最后访问时间等线索从库中登录并结束会话。

如果我们可以让用户在库中的会话从他们用来进行身份验证的任何设备上失效，这不是很好吗？这个示例演示了这是如何实现的。

## 使用`FindByIndexNameSessionRepository`

要通过用户名查找用户，你必须首先选择一个实现[`FindByIndexNameSessionRepository`]的`SessionRepository`。我们的示例应用程序假定已经设置了 Redis 支持，因此我们已经准备好了。

## 映射用户名

`FindByIndexNameSessionRepository`如果开发人员指示 Spring Session 什么用户与`Session`相关联，则只能通过用户名找到会话。你可以通过确保将名称`FindByUsernameSessionRepository.PRINCIPAL_NAME_INDEX_NAME`的会话属性填充为用户名来做到这一点。

一般来说，你可以在用户进行身份验证后立即使用以下代码来完成此操作：

```
String username = "username";
this.session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
```

## 映射具有 Spring 安全性的用户名

由于我们使用 Spring 安全性，用户名将自动为我们建立索引。这意味着我们不需要执行任何步骤来确保对用户名进行索引。

## 向会话添加附加数据

将附加信息（如 IP 地址、浏览器、位置和其他详细信息）与会话关联起来可能会很好。这样做可以使用户更容易地知道他们正在查看的会话。

要做到这一点，请确定你想要使用的会话属性以及你想要提供的信息。然后创建一个作为会话属性添加的 Java Bean。例如，我们的示例应用程序包括会话的位置和访问类型，如下面的清单所示：

```
public class SessionDetails implements Serializable {

	private String location;

	private String accessType;

	public String getLocation() {
		return this.location;
	}

	public void setLocation(String location) {
		this.location = location;
	}

	public String getAccessType() {
		return this.accessType;
	}

	public void setAccessType(String accessType) {
		this.accessType = accessType;
	}

	private static final long serialVersionUID = 8850489178248613501L;

}
```

然后，我们使用`SessionDetailsFilter`将该信息注入到每个 HTTP 请求的会话中，如下例所示：

```
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	chain.doFilter(request, response);

	HttpSession session = request.getSession(false);
	if (session != null) {
		String remoteAddr = getRemoteAddress(request);
		String geoLocation = getGeoLocation(remoteAddr);

		SessionDetails details = new SessionDetails();
		details.setAccessType(request.getHeader("User-Agent"));
		details.setLocation(remoteAddr + " " + geoLocation);

		session.setAttribute("SESSION_DETAILS", details);
	}
}
```

我们获得我们想要的信息，然后将`SessionDetails`设置为`Session`中的一个属性。当我们通过用户名检索`Session`时，我们可以使用会话来访问我们的`SessionDetails`，就像我们将访问任何其他会话属性一样。

|   |你可能想知道为什么 Spring Session 没有提供`SessionDetails`开箱即用的功能。<br/>我们有两个原因。<br/>第一个原因是应用程序自己实现这一点是非常琐碎的。<br/>第二个原因是信息会话中填充的内容（以及信息更新的频率）高度依赖于应用程序。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

## 查找特定用户的会话

现在，我们可以为特定用户找到所有会话。下面的示例展示了如何做到这一点：

```
@Autowired
FindByIndexNameSessionRepository<? extends Session> sessions;

@RequestMapping("/")
public String index(Principal principal, Model model) {
	Collection<? extends Session> usersSessions = this.sessions.findByPrincipalName(principal.getName()).values();
	model.addAttribute("sessions", usersSessions);
	return "index";
}
```

在我们的例子中，我们找到了当前登录用户的所有会话。但是，你可以对此进行修改，以便管理员使用表单来指定要查找的用户。

## `findbyusername`示例应用程序

本节介绍如何使用`findbyusername`示例应用程序。

### 运行`findbyusername`示例应用程序

你可以通过获取[源代码](https://github.com/spring-projects/spring-session/archive/main.zip)并调用以下命令来运行示例：

```
$ ./gradlew :spring-session-sample-boot-findbyusername:bootRun
```

|   |要使示例工作，你必须在 localhost 上[安装 Redis2.8+](https://redis.io/download)并使用默认端口（6379）运行它。<br/>或者，你可以更新`RedisConnectionFactory`以指向 Redis 服务器。<br/>另一个选项是使用[Docker](https://www.docker.com/)在 localhost 上运行 Redis。<br/>有关详细说明，请参见[Docker Redis 存储库](https://hub.docker.com/_/redis/)。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

现在你应该可以在[http://localhost:8080/](http://localhost:8080/)上访问应用程序了。

### 探索安全示例应用程序

你现在可以尝试使用该应用程序了。输入以下内容即可登录：

* **用户 Name ** *User *

* **密码** *密码*

现在点击**登录**按钮。你现在应该会看到一条消息，表明你是用先前输入的用户登录的。你还应该看到当前登录用户的活动会话列表。

你可以通过执行以下操作来模拟我们在[关于样本](#_about_the_sample)部分中讨论的流程：

* 打开一个新的隐身窗口并导航到[http://localhost:8080/](http://localhost:8080/)

* 输入以下内容即可登录：

  * **用户 Name ** *User *

  * **密码** *密码*

* 结束你的原始会话。

* 刷新原始窗口并查看你已注销。