1. 为什么需要Cookie和Session?
在Web开发中,HTTP协议本身是无状态的,这意味着服务器无法自动识别两次请求是否来自同一个用户。想象一下,每次刷新页面都需要重新登录的网站会有多糟糕。这就是为什么我们需要Cookie和Session这对黄金搭档。
Cookie是存储在用户浏览器中的小型文本数据,而Session则是存储在服务器端的用户状态信息。它们协同工作,就像电影院的门票系统:Cookie是你的票根(包含座位号),而Session是影院内部的座位表(记录谁坐在哪个位置)。
重要提示:虽然Cookie和Session经常一起使用,但它们解决的问题不同。Cookie主要用于客户端状态保持,Session则用于服务端状态管理。
2. Spring Boot中的Cookie实战
2.1 创建和发送Cookie
在Spring Boot中操作Cookie非常简单。以下是一个完整的登录接口示例,它会在认证成功后设置Cookie:
@PostMapping("/login") public ResponseEntity<String> login(@RequestBody LoginRequest request, HttpServletResponse response) { if (authenticate(request)) { // 创建Cookie Cookie cookie = new Cookie("user_token", generateToken(request.getUsername())); // 设置关键属性 cookie.setMaxAge(7 * 24 * 60 * 60); // 7天有效期 cookie.setPath("/"); // 全站可用 cookie.setHttpOnly(true); // 防止XSS攻击 cookie.setSecure(true); // 仅HTTPS传输 // 添加到响应 response.addCookie(cookie); return ResponseEntity.ok("登录成功"); } return ResponseEntity.status(401).body("认证失败"); }2.2 Cookie的安全配置
在实际项目中,必须关注Cookie的安全性:
- HttpOnly:防止JavaScript读取,防范XSS攻击
- Secure:仅通过HTTPS传输,防止中间人攻击
- SameSite:现代浏览器的重要安全特性(Spring Boot 2.4+支持)
// SameSite配置示例(需要Spring Boot 2.4+) @Bean public WebServerFactoryCustomizer<TomcatServletWebServerFactory> cookieProcessorCustomizer() { return factory -> factory.addContextCustomizers(context -> { context.setCookieProcessor(new LegacyCookieProcessor() { @Override public void parseCookieHeader(org.apache.tomcat.util.http.Rfc6265CookieProcessor processor) { processor.setSameSiteCookies("Lax"); } }); }); }2.3 读取和验证Cookie
从请求中获取Cookie并验证:
@GetMapping("/profile") public ResponseEntity<UserProfile> getProfile(@CookieValue(value = "user_token", required = false) String token) { if (token == null || !validateToken(token)) { return ResponseEntity.status(401).build(); } String username = extractUsernameFromToken(token); UserProfile profile = userService.getProfile(username); return ResponseEntity.ok(profile); }3. Spring Session深度解析
3.1 Session工作原理
Spring Session的底层机制可以用这张表格说明:
| 组件 | 作用 | 默认实现 |
|---|---|---|
| SessionRepository | 管理Session的CRUD | MapSessionRepository |
| SessionIdResolver | 解析Session ID | CookieSessionIdResolver |
| HttpSessionStrategy | Session传输策略 | CookieHttpSessionStrategy |
当请求到达时,Spring Session会:
- 通过SessionIdResolver从请求中提取Session ID
- 使用SessionRepository加载对应Session
- 将Session存入SecurityContextHolder
- 请求处理完成后保存Session变更
3.2 分布式Session方案
在微服务架构中,我们需要分布式Session解决方案。以下是几种常见方案的对比:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Redis | 高性能,支持持久化 | 需要额外基础设施 | 大多数分布式系统 |
| JDBC | 无需额外中间件 | 性能较差 | 小型系统,已有数据库 |
| Hazelcast | 内存速度快 | 集群管理复杂 | 内存密集型应用 |
| JWT | 无状态,扩展性好 | 无法主动失效 | 前后端分离项目 |
Redis配置示例:
@Configuration @EnableRedisHttpSession public class SessionConfig extends AbstractHttpSessionApplicationInitializer { @Bean public LettuceConnectionFactory connectionFactory() { return new LettuceConnectionFactory(); } }3.3 Session超时与并发控制
实际项目中经常需要精细控制Session:
@Configuration public class SessionManagementConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new ConcurrentSessionControlInterceptor( sessionRegistry())).addPathPatterns("/api/**"); } @Bean public SessionRegistry sessionRegistry() { return new SessionRegistryImpl(); } @Bean public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); } }然后在application.properties中配置:
server.servlet.session.timeout=1800 # 30分钟 spring.session.redis.flush-mode=on_save spring.session.redis.namespace=myapp:session4. 登录状态保持的最佳实践
4.1 记住我(Remember-Me)实现
自动登录功能需要特别注意安全性:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.rememberMe() .key("uniqueAndSecret") .tokenValiditySeconds(86400 * 30) // 30天 .rememberMeParameter("remember-me") .rememberMeCookieName("remember-me") .userDetailsService(userDetailsService()); } }安全增强建议:
- 使用持久化token而非简单加密
- 每次验证后更新token
- 提供全局注销功能
4.2 防御会话固定攻击
会话固定(Session Fixation)是常见的安全威胁,Spring Security提供了内置防护:
@Override protected void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionFixation().migrateSession() // 登录后创建新Session .maximumSessions(1) .maxSessionsPreventsLogin(false) .expiredUrl("/login?expired"); }4.3 移动端适配方案
对于APP等非浏览器客户端,通常采用Token方案:
@Configuration public class MultiAuthSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/api/**").authenticated() .and() .apply(new TokenAuthConfigurer<>()) .tokenValidator(apiTokenValidator()); } @Bean public TokenValidator apiTokenValidator() { return token -> { // 实现token验证逻辑 return new UsernamePasswordAuthenticationToken( parseUserFromToken(token), null, parseAuthoritiesFromToken(token)); }; } }5. 常见问题排查指南
5.1 Cookie未生效的排查步骤
- 检查浏览器是否禁用了Cookie
- 确认域名和路径设置正确
- 检查Secure标记与协议匹配(HTTPS必须)
- 验证SameSite配置是否与跨域需求冲突
- 查看响应头中是否确实包含Set-Cookie
调试技巧:使用Chrome开发者工具的Application > Cookies面板实时观察Cookie变化
5.2 Session丢失问题分析
当遇到神秘的Session丢失时,按这个流程排查:
- 检查服务器日志是否有异常
- 确认所有请求都携带了正确的Session ID
- 验证Session存储后端(如Redis)是否正常运行
- 检查Session超时设置是否过短
- 排查是否有代码手动调用了session.invalidate()
5.3 分布式环境下的同步问题
在集群环境中,可能会遇到:
幽灵Session:一个节点创建的Session在其他节点不可见
- 解决方案:确保所有节点时钟同步
- 检查网络延迟是否过高
并发修改冲突:
- 使用@SessionAttributes时要特别小心
- 考虑使用乐观锁机制
@RestController @SessionAttributes("shoppingCart") public class CartController { @PostMapping("/addItem") public String addItem(@ModelAttribute("shoppingCart") ShoppingCart cart, @RequestParam Item item, SessionStatus status) { // 操作购物车 if (cart.isComplete()) { status.setComplete(); // 明确标记Session完成 } return "success"; } }我在实际项目中发现,合理组合使用Cookie和Session可以构建既安全又用户友好的认证系统。对于关键业务系统,建议定期审计Session使用情况,并建立完整的会话生命周期监控。