分布式Session:粘性Session、共享Session与JWT
分布式Session:粘性Session、共享Session与JWT
分布式Session挑战
单体应用的Session存储在单个服务器内存中,分布式环境下需要解决Session共享问题。常见方案包括粘性Session、Session复制、Session集中存储和无状态JWT。
// Session管理核心接口
public interface SessionManager {
Session createSession(String sessionId);
Session getSession(String sessionId);
void updateSession(Session session);
void deleteSession(String sessionId);
void invalidateSession(String sessionId);
}
// Session数据模型
public class Session {
private String id;
private String userId;
private Map<String, Object> attributes;
private Instant createdAt;
private Instant lastAccessedAt;
private Duration maxInactiveInterval;
}
Redis Session共享
使用Redis作为Session存储中心,所有服务器共享Session数据。Spring Session提供了开箱即用的分布式Session支持。
// Spring Session + Redis配置
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("redis", 6379);
return new LettuceConnectionFactory(config);
}
}
// 使用Session
@Controller
public class UserController {
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpSession session) {
User user = authService.authenticate(username, password);
if (user != null) {
session.setAttribute("user", user);
session.setAttribute("loginTime", Instant.now());
return "redirect:/dashboard";
}
return "login";
}
@GetMapping("/dashboard")
public String dashboard(HttpSession session) {
User user = (User) session.getAttribute("user");
if (user == null) {
return "redirect:/login";
}
return "dashboard";
}
}
// 自定义Session序列化
public class SessionSerializer implements RedisSerializer<Session> {
@Override
public byte[] serialize(Session session) {
Map<String, Object> data = new HashMap<>();
data.put("id", session.getId());
data.put("userId", session.getUserId());
data.put("attributes", session.getAttributes());
return objectMapper.writeValueAsBytes(data);
}
@Override
public Session deserialize(byte[] bytes) {
Map<String, Object> data = objectMapper.readValue(bytes, Map.class);
Session session = new Session((String) data.get("id"));
session.setUserId((String) data.get("userId"));
session.setAttributes((Map<String, Object>) data.get("attributes"));
return session;
}
}
JWT无状态方案
JWT将用户信息编码在Token中,服务器无需存储Session状态,天然支持分布式环境。
// JWT工具类
class JwtUtils {
private static SECRET_KEY = process.env.JWT_SECRET;
private static EXPIRY = 3600; // 1小时
static generateToken(user: User): string {
const payload = {
sub: user.id,
username: user.username,
roles: user.roles,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + this.EXPIRY,
};
return jwt.sign(payload, this.SECRET_KEY);
}
static verifyToken(token: string): any {
try {
return jwt.verify(token, this.SECRET_KEY);
} catch (error) {
throw new Error('Invalid token');
}
}
static refreshToken(token: string): string {
const payload = this.verifyToken(token);
delete payload.iat;
delete payload.exp;
return this.generateToken(payload);
}
}
// JWT中间件
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const payload = JwtUtils.verifyToken(token);
req.user = payload;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
// 刷新Token
app.post('/refresh', authMiddleware, (req, res) => {
const newToken = JwtUtils.refreshToken(req.headers.authorization.split(' ')[1]);
res.json({ token: newToken });
});
Session方案对比
| 特性 | 粘性Session | Redis Session | JWT |
|---|---|---|---|
| 服务器依赖 | 无 | Redis | 无 |
| 扩展性 | 差 | 好 | 好 |
| Session过期 | 服务器管理 | Redis TTL | 客户端管理 |
| 安全性 | 高 | 高 | 中 |
| 跨域支持 | 困难 | 支持 | 支持 |
| 适用场景 | 小规模集群 | 中大规模 | 微服务/API |