Spring Session - 按用户名查找
本指南描述如何使用 Spring Session 按用户名查找会话。
你可以在 findbyusername 应用程序 中找到完整的指南。
假设
本指南假设你已经使用内置的 Redis 配置支持将 Spring Session 添加到应用程序中。本指南还假设你已经将 Spring Security 应用到应用程序中。不过,本指南具有一定的通用性,只需稍作修改即可应用于任何技术,我们将在本指南后面讨论这些修改。
如果你需要了解如何将 Spring Session 添加到项目中,请参阅示例和指南的列表。
关于示例
我们的示例使用此功能来使可能已被窃取的用户会话失效。考虑以下场景:
- 用户去图书馆并向应用程序认证。
- 用户回到家后意识到忘记登出。
- 用户可以利用位置、创建时间、最后访问时间等线索登录并结束在图书馆的会话。
如果我们能让用户从其认证过的任何设备上使图书馆的会话失效,那不是很好吗?本示例演示了如何实现这一点。
使用 FindByIndexNameSessionRepository
要按用户名查找用户,你必须首先选择一个实现了 FindByIndexNameSessionRepository 的 SessionRepository。我们的示例应用程序假设 Redis 支持已经设置好,因此我们可以开始了。
映射用户名
只有当开发者告知 Spring Session 哪个用户与 Session 关联时,FindByIndexNameSessionRepository 才能仅通过用户名查找会话。你可以通过确保名称为 FindByUsernameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 的会话属性填充了用户名来做到这一点。
一般来说,你可以在用户认证后立即使用以下代码来做到这一点:
String username = "username";
this.session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
使用 Spring Security 映射用户名
由于我们使用 Spring Security,用户名会自动为我们建立索引。这意味着我们无需执行任何步骤来确保用户名被索引。
向会话添加额外数据
将会话关联额外信息(如 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 Security 不直接提供 SessionDetails 功能。我们有两个原因。第一个原因是应用程序自己实现这个功能非常简单。第二个原因是,会话中填充的信息(以及该信息更新的频率)高度依赖于具体应用程序。
查找特定用户的会话
现在我们可以查找特定用户的所有会话。以下示例展示了如何这样做:
@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 示例应用程序
你可以通过获取源代码并运行以下命令来启动示例:
$ ./gradlew :spring-session-sample-boot-findbyusername:bootRun
要使示例正常工作,你必须在 localhost 上安装 Redis 2.8+ 并以默认端口(6379)运行。或者,你可以更新 RedisConnectionFactory 以指向 Redis 服务器。另一个选择是使用 Docker 在 localhost 上运行 Redis。详细说明请参阅 Docker Redis 仓库。
现在你应该可以通过 localhost:8080/ 访问该应用程序了。
探索 security 示例应用程序
现在你可以尝试使用该应用程序。输入以下内容进行登录:
- 用户名 user
- 密码 password
现在点击 Login 按钮。你现在应该会看到一条消息,指示你已使用先前输入的用户登录。你还应该看到当前登录用户的活动会话列表。
你可以通过执行以下操作来模拟我们在关于示例一节中讨论的流程:
- 打开一个新的无痕窗口并访问 localhost:8080/
- 输入以下内容进行登录:
- 用户名 user
- 密码 password
- 结束你原来的会话。
- 刷新原来的窗口,看到你已登出。