本项目演示了三种常见的Web认证方式:Cookie认证、Session认证和JWT Token认证。通过Spring Boot后端和Vue3前端的实现,展示了不同认证机制的工作原理和使用场景。
文章目录
-
- 目录结构
- [1. Cookie认证](#1. Cookie认证)
- [2. Session认证](#2. Session认证)
- [3. JWT Token认证](#3. JWT Token认证)
- [4. 三种认证方式的比较](#4. 三种认证方式的比较)
- [5. 安全最佳实践](#5. 安全最佳实践)
-
- [5.1 Cookie安全](#5.1 Cookie安全)
- [5.2 Session安全](#5.2 Session安全)
- [5.3 JWT安全](#5.3 JWT安全)
- [6. 运行项目](#6. 运行项目)
-
- [6.1 后端运行](#6.1 后端运行)
- [6.2 前端运行](#6.2 前端运行)
- [6.3 测试账号](#6.3 测试账号)
- [7. 总结](#7. 总结)
目录结构
plain
auth-demo/
├── backend/ # Spring Boot后端项目
│ ├── src/main/java/com/example/authdemo/
│ │ ├── controller/ # 控制器
│ │ ├── model/ # 模型
│ │ ├── config/ # 配置
│ │ └── util/ # 工具类
│ └── pom.xml # Maven配置
├── frontend/ # Vue3前端项目
│ ├── src/
│ │ ├── components/ # 组件
│ │ ├── services/ # API服务
│ │ └── App.vue # 主应用
│ └── package.json # NPM配置
└── README.md # 本文档
1. Cookie认证
1.1 工作原理

Cookie认证是一种基于客户端存储的认证机制,主要通过以下步骤工作:
- 登录过程 :
- 用户提交用户名和密码
- 服务器验证凭据
- 验证成功后,生成会话ID
- 服务器将会话ID通过
Set-Cookie响应头设置到客户端Cookie中 - 会话ID与用户信息的映射存储在服务器端
- 请求验证 :
- 客户端发起后续请求时,自动在请求头中携带Cookie
- 服务器从Cookie中提取会话ID
- 服务器验证会话ID的有效性
- 如果会话ID有效,服务器识别用户身份并处理请求
- 登出过程 :
- 客户端发起登出请求
- 服务器从存储中删除会话ID
- 服务器设置Cookie的过期时间为0,使Cookie失效
1.2 代码实现
后端实现
CookieAuthController.java
java
package com.example.authdemo.controller;
import com.example.authdemo.model.User;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/cookie-auth")
@CrossOrigin(origins = "http://localhost:5174", allowCredentials = "true")
public class CookieAuthController {
// 模拟用户数据库
private static final Map<String, User> users = new HashMap<>();
// 模拟会话存储
private static final Map<String, String> sessions = new HashMap<>();
static {
// 预创建测试用户
users.put("admin", new User("admin", "password", "ADMIN"));
users.put("user", new User("user", "password", "USER"));
}
@PostMapping("/login")
public Map<String, String> login(@RequestBody User user, HttpServletResponse response) {
// 验证用户
User storedUser = users.get(user.getUsername());
if (storedUser != null && storedUser.getPassword().equals(user.getPassword())) {
// 生成会话ID
String sessionId = UUID.randomUUID().toString();
// 将会话ID与用户名关联
sessions.put(sessionId, user.getUsername());
// 创建Cookie
Cookie cookie = new Cookie("sessionId", sessionId);
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(3600); // 1小时有效期
// 添加Cookie到响应
response.addCookie(cookie);
Map<String, String> result = new HashMap<>();
result.put("message", "Login successful");
result.put("username", user.getUsername());
return result;
}
Map<String, String> error = new HashMap<>();
error.put("message", "Invalid username or password");
return error;
}
@PostMapping("/logout")
public Map<String, String> logout(HttpServletRequest request, HttpServletResponse response) {
// 获取Cookie中的会话ID
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("sessionId".equals(cookie.getName())) {
// 从会话存储中移除
sessions.remove(cookie.getValue());
// 使Cookie失效
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
break;
}
}
}
Map<String, String> result = new HashMap<>();
result.put("message", "Logout successful");
return result;
}
@GetMapping("/profile")
public Map<String, Object> getProfile(HttpServletRequest request) {
// 验证Cookie中的会话ID
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("sessionId".equals(cookie.getName())) {
String username = sessions.get(cookie.getValue());
if (username != null) {
User user = users.get(username);
Map<String, Object> profile = new HashMap<>();
profile.put("username", user.getUsername());
profile.put("role", user.getRole());
profile.put("authenticated", true);
return profile;
}
}
}
}
Map<String, Object> error = new HashMap<>();
error.put("message", "Not authenticated");
error.put("authenticated", false);
return error;
}
}
前端实现
api.js
javascript
export const cookieAuthApi = {
// 登录
login: async (username, password) => {
return await api.post('/cookie-auth/login', { username, password }, {
withCredentials: true // 允许携带cookie
});
},
// 登出
logout: async () => {
return await api.post('/cookie-auth/logout', {}, {
withCredentials: true
});
},
// 获取用户信息
getProfile: async () => {
return await api.get('/cookie-auth/profile', {
withCredentials: true
});
}
};
1.3 优缺点
优点:
- 实现简单,无需客户端特殊处理
- 自动管理,浏览器自动携带Cookie
- 可以设置HttpOnly标志,提高安全性
缺点:
- 受同源策略限制
- 服务器需要存储会话状态,不利于水平扩展
- 可能存在CSRF攻击风险
2. Session认证
2.1 工作原理
Session认证是基于服务器端存储的认证机制,Spring Boot默认提供了Session支持:

- 登录过程 :
- 用户提交用户名和密码
- 服务器验证凭据
- 验证成功后,创建HttpSession对象
- 将用户信息存储在Session对象中
- 服务器自动设置JSESSIONID Cookie到客户端
- 请求验证 :
- 客户端发起请求时携带JSESSIONID Cookie
- 服务器根据JSESSIONID查找对应的HttpSession对象
- 如果Session存在且有效,从Session中获取用户信息
- 登出过程 :
- 客户端发起登出请求
- 服务器调用session.invalidate()使Session失效
- JSESSIONID Cookie也会相应失效
2.2 代码实现
后端实现
SessionAuthController.java
java
package com.example.authdemo.controller;
import com.example.authdemo.model.User;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/session-auth")
@CrossOrigin(origins = "http://localhost:5174", allowCredentials = "true")
public class SessionAuthController {
// 模拟用户数据库
private static final Map<String, User> users = new HashMap<>();
static {
// 预创建测试用户
users.put("admin", new User("admin", "password", "ADMIN"));
users.put("user", new User("user", "password", "USER"));
}
@PostMapping("/login")
public Map<String, String> login(@RequestBody User user, HttpSession session) {
// 验证用户
User storedUser = users.get(user.getUsername());
if (storedUser != null && storedUser.getPassword().equals(user.getPassword())) {
// 将用户信息存储到Session中
session.setAttribute("user", storedUser);
session.setMaxInactiveInterval(3600); // 1小时有效期
Map<String, String> result = new HashMap<>();
result.put("message", "Login successful");
result.put("username", user.getUsername());
result.put("sessionId", session.getId());
return result;
}
Map<String, String> error = new HashMap<>();
error.put("message", "Invalid username or password");
return error;
}
@PostMapping("/logout")
public Map<String, String> logout(HttpSession session) {
// 使Session失效
session.invalidate();
Map<String, String> result = new HashMap<>();
result.put("message", "Logout successful");
return result;
}
@GetMapping("/profile")
public Map<String, Object> getProfile(HttpSession session) {
// 从Session中获取用户信息
User user = (User) session.getAttribute("user");
if (user != null) {
Map<String, Object> profile = new HashMap<>();
profile.put("username", user.getUsername());
profile.put("role", user.getRole());
profile.put("authenticated", true);
profile.put("sessionId", session.getId());
return profile;
}
Map<String, Object> error = new HashMap<>();
error.put("message", "Not authenticated");
error.put("authenticated", false);
return error;
}
}
前端实现
api.js
javascript
export const sessionAuthApi = {
// 登录
login: async (username, password) => {
return await api.post('/session-auth/login', { username, password }, {
withCredentials: true
});
},
// 登出
logout: async () => {
return await api.post('/session-auth/logout', {}, {
withCredentials: true
});
},
// 获取用户信息
getProfile: async () => {
return await api.get('/session-auth/profile', {
withCredentials: true
});
}
};

2.3 优缺点
优点:
- Spring Boot自动管理,使用简单
- 安全性较高,Session数据存储在服务器端
- 可以存储复杂的用户信息
缺点:
- 服务器需要维护Session状态,水平扩展时需要考虑Session共享
- 受同源策略限制
- 可能存在CSRF攻击风险
3. JWT Token认证
3.1 工作原理
JWT (JSON Web Token) 是一种基于Token的无状态认证机制:

- 登录过程 :
- 用户提交用户名和密码
- 服务器验证凭据
- 验证成功后,生成JWT Token(包含用户信息、过期时间等)
- 服务器将Token返回给客户端
- 请求验证 :
- 客户端将Token存储在localStorage或sessionStorage中
- 发起请求时,在Authorization头中携带Token:
Bearer {token} - 服务器接收到请求后,验证Token的签名和有效性
- 验证通过后,从Token中提取用户信息
- 登出过程 :
- 客户端删除存储的Token
- 服务器无需特殊处理(无状态)
3.2 JWT Token结构
JWT由三部分组成,用点(.)连接:
plain
header.payload.signature
- Header:包含Token类型和签名算法
- Payload:包含声明(claims),如用户ID、角色、过期时间等
- Signature:使用密钥对前两部分进行签名,用于验证Token的完整性
3.3 代码实现
后端实现
JwtUtil.java
java
public class JwtUtil {
// 生成安全的密钥
private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
private static final long EXPIRATION_TIME = 3600000; // 1小时,单位毫秒
// 生成Token
public static String generateToken(String username, String role) {
return Jwts.builder()
.setSubject(username)
.claim("role", role)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SECRET_KEY)
.compact();
}
// 验证Token是否有效
public static boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
// 其他方法...
}
JwtAuthController.java
java
package com.example.authdemo.controller;
import com.example.authdemo.model.User;
import com.example.authdemo.util.JwtUtil;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/jwt-auth")
@CrossOrigin(origins = "http://localhost:5174")
public class JwtAuthController {
// 模拟用户数据库
private static final Map<String, User> users = new HashMap<>();
static {
// 预创建测试用户
users.put("admin", new User("admin", "password", "ADMIN"));
users.put("user", new User("user", "password", "USER"));
}
@PostMapping("/login")
public Map<String, String> login(@RequestBody User user) {
// 验证用户
User storedUser = users.get(user.getUsername());
if (storedUser != null && storedUser.getPassword().equals(user.getPassword())) {
// 生成JWT Token
String token = JwtUtil.generateToken(user.getUsername(), user.getRole());
Map<String, String> result = new HashMap<>();
result.put("message", "Login successful");
result.put("username", user.getUsername());
result.put("token", token);
return result;
}
Map<String, String> error = new HashMap<>();
error.put("message", "Invalid username or password");
return error;
}
@GetMapping("/profile")
public Map<String, Object> getProfile(@RequestHeader("Authorization") String authorizationHeader) {
// 检查Authorization头
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
Map<String, Object> error = new HashMap<>();
error.put("message", "Authorization header is required");
error.put("authenticated", false);
return error;
}
// 提取token
String token = authorizationHeader.substring(7);
// 验证token
if (JwtUtil.validateToken(token)) {
String username = JwtUtil.getUsernameFromToken(token);
String role = JwtUtil.getRoleFromToken(token);
Map<String, Object> profile = new HashMap<>();
profile.put("username", username);
profile.put("role", role);
profile.put("authenticated", true);
return profile;
}
Map<String, Object> error = new HashMap<>();
error.put("message", "Invalid token");
error.put("authenticated", false);
return error;
}
@GetMapping("/validate")
public Map<String, Boolean> validateToken(@RequestHeader("Authorization") String authorizationHeader) {
Map<String, Boolean> result = new HashMap<>();
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
result.put("valid", false);
return result;
}
String token = authorizationHeader.substring(7);
result.put("valid", JwtUtil.validateToken(token));
return result;
}
}
前端实现
api.js
javascript
export const jwtAuthApi = {
// 登录
login: async (username, password) => {
return await api.post('/jwt-auth/login', { username, password });
},
// 获取用户信息
getProfile: async (token) => {
return await api.get('/jwt-auth/profile', {
headers: {
'Authorization': `Bearer ${token}`
}
});
},
// 验证token
validateToken: async (token) => {
return await api.get('/jwt-auth/validate', {
headers: {
'Authorization': `Bearer ${token}`
}
});
}
};
authStorage.js
javascript
// 认证存储管理
export const authStorage = {
// 保存JWT token
saveToken: (token) => {
localStorage.setItem('jwt_token', token);
},
// 获取JWT token
getToken: () => {
return localStorage.getItem('jwt_token');
},
// 移除JWT token
removeToken: () => {
localStorage.removeItem('jwt_token');
},
// 其他方法...
};

3.4 优缺点
优点:
- 无状态,服务器不需要存储会话信息,便于水平扩展
- 支持跨域,不依赖Cookie
- 自包含,Token中包含所有必要信息
缺点:
- Token一旦签发,无法直接撤销(除非服务端维护黑名单)
- Token可能较大,增加请求大小
- 需要客户端手动管理Token的存储和发送
4. 三种认证方式的比较
| 特性 | Cookie认证 | Session认证 | JWT Token认证 |
|---|---|---|---|
| 存储位置 | 客户端Cookie | 服务器Session + 客户端Cookie | 客户端localStorage/SessionStorage |
| 状态管理 | 有状态 | 有状态 | 无状态 |
| 跨域支持 | 不支持 | 不支持 | 支持 |
| 水平扩展 | 困难 | 需要Session共享 | 简单 |
| 安全性 | 中等 | 高 | 高 |
| 实现复杂度 | 简单 | 简单(Spring Boot自动支持) | 中等 |
| 适合场景 | 简单的Web应用 | 传统Web应用 | 前后端分离、微服务、移动应用 |
5. 安全最佳实践
5.1 Cookie安全
- 设置HttpOnly标志,防止XSS攻击获取Cookie
- 设置Secure标志,确保只通过HTTPS传输
- 设置SameSite属性,防止CSRF攻击
- 使用较短的过期时间
5.2 Session安全
- 使用强随机数生成Session ID
- 限制Session的生命周期
- 登出时立即销毁Session
- 考虑使用Redis等进行Session共享
5.3 JWT安全
- 使用强密钥并定期轮换
- 设置合理的过期时间
- 敏感信息不放入Payload(因为Payload可以被解码)
- 使用HTTPS传输Token
- 实现Token刷新机制
6. 运行项目
6.1 后端运行
bash
cd auth-demo/backend
mvn spring-boot:run
6.2 前端运行
bash
cd auth-demo/frontend
npm install
npm run dev
6.3 测试账号
- 用户名: admin, 密码: password (管理员权限)
- 用户名: user, 密码: password (普通用户权限)
7. 总结
三种认证方式各有优缺点,选择哪种方式应该根据具体的应用场景:
- Cookie认证:适合简单的Web应用,实现简单但扩展性有限
- Session认证:适合传统的Web应用,Spring Boot原生支持,安全性较高
- JWT Token认证:适合前后端分离、微服务架构和移动应用,无状态设计便于扩展
在实际项目中,还可以根据需要结合使用这些认证方式,例如使用JWT进行API认证,同时使用Session管理一些临时状态。