一、前言:为什么还要学 Session 登录?
在 JWT、OAuth2、Token 风靡的今天,你可能会问:
“现在还有人用 Session 吗?”
答案是:有!而且很多!
- 企业内部系统(如 OA、ERP)仍广泛使用 Session
- 对安全性要求高、需强会话控制的场景(如银行后台)
- 不需要跨域、不追求无状态的中小型 Web 应用
Session 是理解 Web 认证机制的基石。掌握它,你才能真正理解“登录”背后的原理。
本文将带你:
✅ 深入 Session 工作原理
✅ 手动实现完整登录/登出流程
✅ 在 Spring Boot 中安全使用 Session
✅ 避开常见安全陷阱
二、Session 登录原理图解
+—————-+ +——————+
| 浏览器 | | 服务器 |
+—————-+ +——————+
| |
1. 提交用户名密码 | POST /login |
|————————>|
| |
2. 验证成功 | 创建 Session |
| 存储用户信息 |
| Set-Cookie: JSESSIONID=abc123
|<————————|
| |
3. 后续请求 | Cookie: JSESSIONID=abc123
|————————>|
| 服务器根据 ID 找到 Session
| 确认用户已登录 |
| |
4. 点击退出 | GET /logout |
|————————>|
| 删除 Session |
| Clear Cookie |
|<————————|
🔑 核心机制:
- 服务器创建 HttpSession,生成唯一 ID(如 JSESSIONID)
- 通过 Set-Cookie 将 ID 返回给浏览器
- 浏览器后续请求自动携带该 Cookie
- 服务器通过 ID 查找 Session,验证登录状态
三、Spring Boot 实现登录流程
1. 项目依赖(无需额外引入)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!– 内嵌 Tomcat 自带 Session 支持 –>
💡 Spring Boot 默认使用 内存存储 Session(重启丢失),生产环境可集成 Redis(见后文)。
2. 用户实体与模拟数据库
public class User {
private String username;
private String password; // 实际应存 BCrypt 加密后的值
// getter/setter…
}
@Component
public class UserDatabase {
private static final Map<String, User> users = new HashMap<>();
static {
// 模拟用户:admin / 123456
users.put("admin", new User("admin", "123456"));
}
public User findByUsername(String username) {
return users.get(username);
}
}
3. 登录控制器(核心逻辑)
@Controller
public class LoginController {
@Autowired
private UserDatabase userDatabase;
// 跳转到登录页
@GetMapping("/login")
public String loginPage() {
return "login"; // 返回 templates/login.html
}
// 处理登录请求
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpSession session,
RedirectAttributes redirectAttributes) {
// 1. 校验参数
if (username == null || username.trim().isEmpty()) {
redirectAttributes.addFlashAttribute("error", "用户名不能为空");
return "redirect:/login";
}
// 2. 查询用户
User user = userDatabase.findByUsername(username);
if (user == null || !user.getPassword().equals(password)) {
redirectAttributes.addFlashAttribute("error", "用户名或密码错误");
return "redirect:/login";
}
// 3. 登录成功:将用户信息存入 Session
session.setAttribute("currentUser", user.getUsername());
session.setMaxInactiveInterval(30 * 60); // 30分钟无操作自动过期
return "redirect:/home";
}
// 主页(需登录)
@GetMapping("/home")
public String home(HttpSession session, Model model) {
String currentUser = (String) session.getAttribute("currentUser");
if (currentUser == null) {
return "redirect:/login";
}
model.addAttribute("user", currentUser);
return "home";
}
// 退出登录
@GetMapping("/logout")
public String logout(HttpSession session) {
session.invalidate(); // 销毁整个 Session
return "redirect:/login";
}
}
4. 简单前端页面(Thymeleaf)
templates/login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>登录</title></head>
<body>
<h2>用户登录</h2>
<div th:if="${error}" style="color:red;" th:text="${error}"></div>
<form method="post" action="/login">
用户名: <input type="text" name="username" required><br><br>
密码: <input type="password" name="password" required><br><br>
<button type="submit">登录</button>
</form>
</body>
</html>
templates/home.html
<!DOCTYPE html>
<html>
<head><title>主页</title></head>
<body>
<h2>欢迎,<span th:text="${user}"></span>!</h2>
<a href="/logout">退出登录</a>
</body>
</html>
四、关键细节解析
1. HttpSession 是什么?
- 由 Servlet 容器(如 Tomcat)提供
- 每个会话一个实例,线程安全
- 数据存储在服务端内存中
- 生命周期:创建 → 使用 → 超时/手动销毁
2. session.setAttribute() 存什么?
- ✅ 推荐:只存必要标识(如 userId、username)
- ❌ 避免:存大对象(如用户完整信息、订单列表)
示例:
session.setAttribute("userId", 1001); // 好
session.setAttribute("user", fullUserObject); // 不推荐
3. 如何保护接口不被未登录访问?
方式一:每个 Controller 手动检查
@GetMapping("/profile")
public String profile(HttpSession session) {
if (session.getAttribute("currentUser") == null) {
return "redirect:/login";
}
// …
}
方式二:使用拦截器(推荐)
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("currentUser") == null) {
response.sendRedirect("/login");
return false;
}
return true;
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/home", "/profile", "/settings/**")
.excludePathPatterns("/login", "/css/**", "/js/**");
}
}
五、安全加固:避开常见陷阱
🔒 坑 1:密码明文存储/传输
- 修复:前端 HTTPS + 后端 BCrypt 加密
// 存储时加密
String hashedPassword = BCrypt.hashpw(rawPassword, BCrypt.gensalt());
// 验证时比对
if (BCrypt.checkpw(inputPassword, storedHash)) { … }
🔒 坑 2:Session 劫持(XSS + Cookie 窃取)
- 修复:设置 Cookie 安全属性
# application.yml
server:
servlet:
session:
cookie:
http-only: true # 禁止 JS 访问 Cookie
secure: false # 生产环境 HTTPS 时设为 true
same-site: strict # 防 CSRF(部分浏览器支持)
🔒 坑 3:Session 固定攻击
- 现象:攻击者诱导用户使用已知 Session ID
- 修复:登录成功后更换 Session ID
// 在 login 成功后
session = request.getSession(); // 获取当前 session
session.invalidate(); // 销毁旧 session
session = request.getSession(true); // 创建新 session
session.setAttribute("currentUser", username);
✅ Spring Security 默认已处理此问题,但手写登录需注意!
六、生产优化:Session 共享(集群部署)
单机 Session 存内存没问题,但多实例部署时会丢失会话!
解决方案:将 Session 存入 Redis
- 添加依赖:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
- 配置启用:
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig {}
- 配置 Redis 连接(application.yml)
🎉 此时所有实例共享 Session,无缝支持水平扩展!
七、Session vs Token:如何选择?
| 状态 | 有状态(服务端存储) | 无状态 |
| 跨域 | 困难(Cookie 限制) | 容易(Header 传递) |
| 安全性 | 高(可主动销毁) | 依赖签名,无法主动失效 |
| 适用场景 | 传统 Web 应用 | 前后端分离、移动端 |
📌 建议:
- 内部管理系统 → Session
- App/小程序/H5 → Token
八、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!
网硕互联帮助中心






评论前必须登录!
注册