Appearance
Spring Boot Actuator Sessions 端点详解 🔐
什么是 Sessions 端点?
Spring Boot Actuator 的 sessions
端点是一个专门用于管理和监控应用程序 HTTP 会话的强大工具。它基于 Spring Session 框架,为开发者提供了查看、管理用户会话的能力。
NOTE
Sessions 端点只有在应用程序集成了 Spring Session 时才会自动启用。Spring Session 是 Spring 生态系统中用于管理用户会话的专业框架。
为什么需要 Sessions 端点? 🤔
传统会话管理的痛点
在没有专业会话管理工具之前,开发者面临以下挑战:
kotlin
@RestController
class UserController {
// 问题1:无法查看当前活跃的会话
@GetMapping("/login")
fun login(request: HttpServletRequest): String {
val session = request.getSession(true)
session.setAttribute("user", "alice")
// 开发者无法知道系统中有多少个活跃会话
return "登录成功"
}
// 问题2:无法主动管理会话
@GetMapping("/admin/sessions")
fun getSessions(): String {
// 传统方式无法获取所有会话信息
return "无法实现"
}
// 问题3:无法强制踢出用户
@DeleteMapping("/admin/kick-user")
fun kickUser(@RequestParam userId: String): String {
// 无法根据用户ID找到并删除对应的会话
return "无法实现"
}
}
kotlin
@RestController
class SessionManagementController {
@Autowired
private lateinit var restTemplate: RestTemplate
// 解决方案1:可以查看指定用户的所有会话
@GetMapping("/admin/user-sessions")
fun getUserSessions(@RequestParam username: String): ResponseEntity<String> {
val url = "http://localhost:8080/actuator/sessions?username=$username"
return restTemplate.getForEntity(url, String::class.java)
}
// 解决方案2:可以查看特定会话详情
@GetMapping("/admin/session/{sessionId}")
fun getSessionDetail(@PathVariable sessionId: String): ResponseEntity<String> {
val url = "http://localhost:8080/actuator/sessions/$sessionId"
return restTemplate.getForEntity(url, String::class.java)
}
// 解决方案3:可以强制删除会话(踢出用户)
@DeleteMapping("/admin/session/{sessionId}")
fun kickUser(@PathVariable sessionId: String): ResponseEntity<Void> {
val url = "http://localhost:8080/actuator/sessions/$sessionId"
restTemplate.delete(url)
return ResponseEntity.ok().build()
}
}
Sessions 端点解决的核心问题
- 会话可视化 - 让"看不见"的会话变得"看得见"
- 会话管理 - 提供主动管理会话的能力
- 安全控制 - 支持强制用户下线等安全操作
- 运维监控 - 帮助运维人员了解系统会话状态
Sessions 端点的核心功能 ⚡
1. 获取用户会话列表
实际应用示例
kotlin
@Configuration
@EnableSpringHttpSession
class SessionConfig {
/**
* 配置会话存储方式
* 这里使用内存存储,生产环境建议使用 Redis
*/
@Bean
fun sessionRepository(): MapSessionRepository {
return MapSessionRepository(ConcurrentHashMap())
}
/**
* 配置会话超时时间
*/
@Bean
fun sessionTimeout(): Duration {
return Duration.ofMinutes(30) // 30分钟超时
}
}
kotlin
@RestController
@RequestMapping("/api/auth")
class AuthController {
@PostMapping("/login")
fun login(
@RequestParam username: String,
@RequestParam password: String,
request: HttpServletRequest
): ResponseEntity<Map<String, Any>> {
// 模拟用户验证
if (isValidUser(username, password)) {
val session = request.getSession(true)
// 在会话中存储用户信息
session.setAttribute("username", username)
session.setAttribute("loginTime", System.currentTimeMillis())
session.setAttribute("role", getUserRole(username))
return ResponseEntity.ok(mapOf(
"message" to "登录成功",
"sessionId" to session.id,
"username" to username
))
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(mapOf("error" to "用户名或密码错误"))
}
private fun isValidUser(username: String, password: String): Boolean {
// 实际项目中这里会查询数据库验证用户
return username == "alice" && password == "password123"
}
private fun getUserRole(username: String): String {
return if (username == "alice") "ADMIN" else "USER"
}
}
查询会话的 HTTP 请求示例
bash
# 查询用户 alice 的所有会话
curl 'http://localhost:8080/actuator/sessions?username=alice' \
-H 'Accept: application/json'
响应结构解析
json
{
"sessions": [
{
"id": "220c01e4-fb37-404f-ba6e-93f9d9c1fed4",
"attributeNames": ["username", "loginTime", "role"],
"creationTime": "2025-05-22T08:03:34.888566190Z",
"lastAccessedTime": "2025-05-22T20:02:49.888573274Z",
"maxInactiveInterval": 1800,
"expired": false
}
]
}
TIP
maxInactiveInterval
的单位是秒,1800 秒 = 30 分钟。这意味着如果用户在 30 分钟内没有任何活动,会话将自动过期。
2. 获取单个会话详情
当你需要查看特定会话的详细信息时:
kotlin
@RestController
@RequestMapping("/api/admin")
class SessionAdminController {
@Autowired
private lateinit var restTemplate: RestTemplate
/**
* 获取会话详细信息
* 通常用于管理员查看用户会话状态
*/
@GetMapping("/session/{sessionId}")
fun getSessionDetail(@PathVariable sessionId: String): ResponseEntity<SessionInfo> {
try {
val actuatorUrl = "http://localhost:8080/actuator/sessions/$sessionId"
val response = restTemplate.getForEntity(actuatorUrl, SessionInfo::class.java)
return ResponseEntity.ok(response.body)
} catch (e: Exception) {
return ResponseEntity.notFound().build()
}
}
}
/**
* 会话信息数据类
*/
data class SessionInfo(
val id: String,
val attributeNames: List<String>,
val creationTime: String,
val lastAccessedTime: String,
val maxInactiveInterval: Int,
val expired: Boolean
)
3. 删除会话(强制用户下线)
这是一个非常实用的功能,特别适用于以下场景:
使用场景
- 发现可疑登录活动时强制用户下线
- 用户忘记退出,需要管理员帮助清理会话
- 系统维护时需要清空所有活跃会话
- 用户账户被盗用时的紧急处理
kotlin
@RestController
@RequestMapping("/api/admin")
class SecurityController {
@Autowired
private lateinit var restTemplate: RestTemplate
/**
* 强制用户下线
* 删除指定的会话,用户需要重新登录
*/
@DeleteMapping("/kick-user/{sessionId}")
fun kickUser(
@PathVariable sessionId: String,
@RequestParam reason: String = "管理员操作"
): ResponseEntity<Map<String, String>> {
return try {
// 调用 Actuator 端点删除会话
val actuatorUrl = "http://localhost:8080/actuator/sessions/$sessionId"
restTemplate.delete(actuatorUrl)
// 记录操作日志
logSecurityAction("KICK_USER", sessionId, reason)
ResponseEntity.ok(mapOf(
"message" to "用户已被强制下线",
"sessionId" to sessionId,
"reason" to reason
))
} catch (e: Exception) {
ResponseEntity.badRequest().body(mapOf(
"error" to "操作失败",
"details" to (e.message ?: "未知错误")
))
}
}
/**
* 批量清理过期会话
*/
@PostMapping("/cleanup-sessions")
fun cleanupExpiredSessions(@RequestParam username: String): ResponseEntity<Map<String, Any>> {
val expiredSessions = mutableListOf<String>()
try {
// 获取用户的所有会话
val sessionsUrl = "http://localhost:8080/actuator/sessions?username=$username"
val response = restTemplate.getForEntity(sessionsUrl, SessionsResponse::class.java)
response.body?.sessions?.forEach { session ->
if (session.expired) {
// 删除过期的会话
restTemplate.delete("http://localhost:8080/actuator/sessions/${session.id}")
expiredSessions.add(session.id)
}
}
return ResponseEntity.ok(mapOf(
"message" to "清理完成",
"cleanedSessions" to expiredSessions.size,
"sessionIds" to expiredSessions
))
} catch (e: Exception) {
return ResponseEntity.badRequest().body(mapOf(
"error" to "清理失败",
"details" to (e.message ?: "未知错误")
))
}
}
private fun logSecurityAction(action: String, sessionId: String, reason: String) {
// 实际项目中这里会写入安全日志
println("SECURITY_LOG: $action - SessionID: $sessionId - Reason: $reason - Time: ${System.currentTimeMillis()}")
}
}
data class SessionsResponse(
val sessions: List<SessionInfo>
)
实际业务场景应用 🎯
场景1:在线用户管理系统
kotlin
@RestController
@RequestMapping("/api/admin/users")
class OnlineUserController {
@Autowired
private lateinit var restTemplate: RestTemplate
/**
* 获取在线用户列表
* 管理员可以看到当前所有在线用户
*/
@GetMapping("/online")
fun getOnlineUsers(): ResponseEntity<List<OnlineUser>> {
val onlineUsers = mutableListOf<OnlineUser>()
// 这里需要遍历所有可能的用户名
// 实际项目中可能需要从数据库获取用户列表
val usernames = listOf("alice", "bob", "charlie") // 示例用户名
usernames.forEach { username ->
try {
val url = "http://localhost:8080/actuator/sessions?username=$username"
val response = restTemplate.getForEntity(url, SessionsResponse::class.java)
response.body?.sessions?.forEach { session ->
if (!session.expired) {
onlineUsers.add(OnlineUser(
username = username,
sessionId = session.id,
loginTime = session.creationTime,
lastActivity = session.lastAccessedTime,
timeoutIn = session.maxInactiveInterval
))
}
}
} catch (e: Exception) {
// 用户没有活跃会话,跳过
}
}
return ResponseEntity.ok(onlineUsers)
}
}
data class OnlineUser(
val username: String,
val sessionId: String,
val loginTime: String,
val lastActivity: String,
val timeoutIn: Int
)
场景2:安全监控和异常检测
kotlin
@Component
class SessionSecurityMonitor {
@Autowired
private lateinit var restTemplate: RestTemplate
/**
* 定期检查可疑会话
* 例如:同一用户有多个活跃会话可能表示账户被盗用
*/
@Scheduled(fixedRate = 300000) // 每5分钟检查一次
fun checkSuspiciousSessions() {
val suspiciousUsers = mutableListOf<String>()
val usernames = getUsernamesFromDatabase() // 从数据库获取用户列表
usernames.forEach { username ->
try {
val url = "http://localhost:8080/actuator/sessions?username=$username"
val response = restTemplate.getForEntity(url, SessionsResponse::class.java)
val activeSessions = response.body?.sessions?.filter { !it.expired } ?: emptyList()
// 如果用户有超过2个活跃会话,标记为可疑
if (activeSessions.size > 2) {
suspiciousUsers.add(username)
// 发送安全警报
sendSecurityAlert(username, activeSessions.size)
// 可选:自动踢出除最新会话外的所有会话
autoCleanupOldSessions(username, activeSessions)
}
} catch (e: Exception) {
// 处理异常
}
}
if (suspiciousUsers.isNotEmpty()) {
println("发现可疑用户: $suspiciousUsers")
}
}
private fun getUsernamesFromDatabase(): List<String> {
// 实际项目中从数据库查询
return listOf("alice", "bob", "charlie")
}
private fun sendSecurityAlert(username: String, sessionCount: Int) {
// 发送邮件、短信或推送通知
println("SECURITY ALERT: 用户 $username 有 $sessionCount 个活跃会话")
}
private fun autoCleanupOldSessions(username: String, sessions: List<SessionInfo>) {
// 保留最新的会话,删除其他的
val sortedSessions = sessions.sortedByDescending { it.lastAccessedTime }
sortedSessions.drop(1).forEach { session ->
try {
restTemplate.delete("http://localhost:8080/actuator/sessions/${session.id}")
println("自动清理用户 $username 的旧会话: ${session.id}")
} catch (e: Exception) {
println("清理会话失败: ${e.message}")
}
}
}
}
配置和最佳实践 🛠️
1. 启用 Sessions 端点
kotlin
# application.yml
management:
endpoints:
web:
exposure:
include: "sessions" # 只暴露 sessions 端点
# include: "*" # 或者暴露所有端点(不推荐生产环境)
endpoint:
sessions:
enabled: true
# Spring Session 配置
spring:
session:
store-type: redis # 生产环境推荐使用 Redis
timeout: 30m # 会话超时时间
2. 安全配置
IMPORTANT
Sessions 端点包含敏感信息,必须进行适当的安全配置!
kotlin
@Configuration
@EnableWebSecurity
class ActuatorSecurityConfig {
@Bean
fun actuatorSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
return http
.requestMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests { requests ->
requests
.requestMatchers(EndpointRequest.to("sessions"))
.hasRole("ADMIN") // 只有管理员可以访问 sessions 端点
.anyRequest().authenticated()
}
.httpBasic(Customizer.withDefaults())
.build()
}
}
3. 生产环境配置建议
kotlin
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
class RedisSessionConfig {
@Bean
fun redisConnectionFactory(): RedisConnectionFactory {
val factory = LettuceConnectionFactory()
factory.hostName = "localhost"
factory.port = 6379
factory.database = 0
return factory
}
@Bean
fun redisTemplate(): RedisTemplate<String, Any> {
val template = RedisTemplate<String, Any>()
template.connectionFactory = redisConnectionFactory()
template.keySerializer = StringRedisSerializer()
template.valueSerializer = GenericJackson2JsonRedisSerializer()
return template
}
}
yaml
# application-prod.yml
management:
endpoints:
web:
exposure:
include: "health,info,sessions"
base-path: "/actuator"
endpoint:
sessions:
enabled: true
security:
enabled: true
spring:
session:
store-type: redis
timeout: 30m
redis:
flush-mode: on_save
namespace: "myapp:session"
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
database: 0
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
logging:
level:
org.springframework.session: DEBUG
常见问题和解决方案 ❓
Q1: Sessions 端点返回空结果
WARNING
如果 /actuator/sessions?username=alice
返回空的 sessions 数组,可能的原因:
- 用户没有活跃会话 - 用户未登录或会话已过期
- Spring Session 未正确配置 - 检查
@EnableSpringHttpSession
注解 - 会话存储问题 - 检查 Redis 连接或内存存储配置
kotlin
// 调试代码:检查会话是否正确创建
@RestController
class SessionDebugController {
@GetMapping("/debug/create-session")
fun createTestSession(request: HttpServletRequest): Map<String, Any> {
val session = request.getSession(true)
session.setAttribute("username", "alice")
session.setAttribute("testData", "debugging")
return mapOf(
"sessionId" to session.id,
"creationTime" to session.creationTime,
"maxInactiveInterval" to session.maxInactiveInterval,
"attributes" to session.attributeNames.toList()
)
}
}
Q2: 删除会话后用户仍然可以访问
这可能是因为应用程序缓存了会话信息。解决方案:
kotlin
@Component
class SessionEventListener {
@EventListener
fun handleSessionDestroyed(event: SessionDestroyedEvent) {
val sessionId = event.sessionId
// 清理应用程序缓存
clearUserCache(sessionId)
// 记录会话销毁日志
println("会话已销毁: $sessionId")
}
private fun clearUserCache(sessionId: String) {
// 实现缓存清理逻辑
// 例如:从 Redis 缓存中移除用户信息
}
}
总结 🎉
Spring Boot Actuator 的 Sessions 端点为我们提供了强大的会话管理能力:
✅ 可视化管理 - 让会话状态一目了然
✅ 安全控制 - 支持强制用户下线等安全操作
✅ 运维监控 - 帮助识别异常会话和安全威胁
✅ 灵活配置 - 支持多种存储方式和安全配置
通过合理使用 Sessions 端点,我们可以构建更加安全、可控的用户会话管理系统。记住在生产环境中一定要配置适当的安全措施,保护敏感的会话信息!
TIP
建议将 Sessions 端点与监控系统集成,设置会话数量告警,及时发现异常情况。同时,定期审查会话管理策略,确保符合安全最佳实践。