在构建Web应用或API时,你是否曾对Token、Session、Cookie、JWT和OAuth2这些概念感到困惑?它们之间究竟有何区别与联系?例如,实现登录功能时,是该选择经典的Session方案,还是时髦的JWT?OAuth2和我们常说的Token又是什么关系?为什么有些架构会把Token存进Cookie里?本文将为你系统梳理这五大核心概念,通过类比、代码和实践,帮助你做出更清晰的技术选型。
一、从餐厅就餐模型理解认证与授权
为了直观理解这些抽象概念,我们可以用一个餐厅就餐的比喻来开场。想象两种不同的就餐身份验证方式。

- 左侧 - 会员卡(Session方案):你在餐厅前台办理一张会员卡(Session ID),餐厅在后台的会员系统(Session存储)里记录你的详细信息。之后每次消费,你只需出示这张卡,服务员通过卡号就能在后台查到你的所有信息。这里,卡是凭证,你的信息完全由餐厅(服务器)保管。
- 右侧 - 二维码令牌(Token方案):餐厅直接给你一个加密的二维码(Token),这个二维码里就包含了你的会员等级、折扣等信息。之后每次消费,你直接出示这个二维码,服务员用特定的扫描仪(验证算法)一扫就能读出信息并验证真伪,无需查询后台。这里,信息就包含在令牌本身之中。
这个比喻清晰地揭示了 有状态(Session) 与 无状态(Token) 两种认证模式的核心差异。接下来,让我们深入每个概念的技术细节。
二、Cookie:HTTP协议的状态管理机制
2.1 什么是Cookie?
Cookie是存储在浏览器端的一小段文本数据,它是HTTP协议用于管理状态的标准机制。服务器通过HTTP响应头的 Set-Cookie 字段将Cookie发送给浏览器,之后浏览器在每次向同一服务器发起请求时,都会自动通过 Cookie 请求头将其携带回服务器。
工作原理:

2.2 Cookie实战代码
// 服务器设置Cookie
@PostMapping("/login")
public ResponseEntity login(@RequestBody User user, HttpServletResponse response){
if (authService.authenticate(user)) {
Cookie cookie = new Cookie("session_id", generateSessionId());
cookie.setMaxAge(3600); // 1小时有效期
cookie.setHttpOnly(true); // 防止XSS攻击
cookie.setSecure(true); // 仅HTTPS传输
cookie.setPath("/"); // 对整个站点有效
response.addCookie(cookie);
return ResponseEntity.ok().build();
}
return ResponseEntity.status(401).build();
}
// 读取Cookie
@GetMapping("/profile")
public ResponseEntity getProfile(@CookieValue("session_id") String sessionId) {
User user = sessionService.getUserBySession(sessionId);
return ResponseEntity.ok(user);
}
2.3 Cookie的重要安全属性
| 属性 |
作用 |
安全建议 |
| HttpOnly |
阻止JavaScript通过 document.cookie API访问此Cookie |
必须设置为true,这是防御XSS攻击窃取Cookie的关键手段 |
| Secure |
指示浏览器仅在通过HTTPS协议发起请求时才发送此Cookie |
生产环境必须设置为true,防止明文传输被截获 |
| SameSite |
控制跨站请求时是否发送Cookie,用于防御CSRF攻击 |
建议设置为 Strict 或 Lax |
| Max-Age |
设置Cookie的有效期(秒) |
根据业务安全要求合理设置,平衡用户体验与安全 |
理解Cookie是理解后续概念的基础,因为它常常是Session和Token的“运输载体”。更多关于HTTP协议及网络安全的深入讨论,可以在云栈社区的网络/系统板块找到。
三、Session:服务端的用户会话状态
3.1 什么是Session?
Session是存储在服务器端的用户状态信息。它为每个用户会话创建一个唯一的ID(Session ID),通常通过Cookie将这个ID传递给客户端。客户端在后续请求中携带此ID,服务器便能识别出用户身份并获取其相关的状态数据。
Session存储结构示例:
// 典型的Session数据结构
public class UserSession {
private String sessionId;
private String userId;
private String username;
private Date loginTime;
private Date lastAccessTime;
private Map<String, Object> attributes; // 自定义属性
// 省略getter/setter
}
3.2 Session实战代码
// 基于Spring Session的实现
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpSession session){
User user = userService.authenticate(username, password);
if (user != null) {
// 将用户信息存入Session
session.setAttribute("currentUser", user);
session.setAttribute("loginTime", new Date());
return "redirect:/dashboard";
}
return "login?error=true";
}
@GetMapping("/dashboard")
public String dashboard(HttpSession session){
// 从Session获取用户信息
User user = (User) session.getAttribute("currentUser");
if (user == null) {
return "redirect:/login";
}
return "dashboard";
}
3.3 Session的存储方案与集群挑战
1. 内存存储(默认)
简单易用,但服务器重启数据丢失,且无法在集群中共享。
# application.yml
server:
servlet:
session:
timeout: 1800 # 30分钟过期时间
2. Redis分布式存储
解决多实例间的Session共享问题,是集群架构下的标准方案。
@Configuration
@EnableRedisHttpSession // 启用Redis Session存储
public class SessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory(){
return new LettuceConnectionFactory();
}
}
3. Session集群同步问题
在传统架构中,Session同步是一大挑战。

当应用部署为多实例并通过负载均衡对外服务时,必须解决Session共享问题,否则用户请求被转发到不同服务器会导致登录状态丢失。使用如Redis这样的集中式存储是常见解决方案,关于数据库/中间件的选型与优化,涉及许多架构知识。
四、Token:去中心化的身份凭证
4.1 什么是Token?
Token是一种自包含的身份凭证。与Session不同,服务器在验证Token有效性后,无需在服务端存储任何会话状态。所有必要的用户身份和授权信息(即“声明”)都经过签名或加密后包含在Token字符串本身中。
Token vs Session 核心区别:

4.2 Token实战代码
// 生成Token (使用Auth0 JWT库示例)
public String generateToken(User user){
long currentTime = System.currentTimeMillis();
return JWT.create()
.withIssuer("myapp") // 签发者
.withSubject(user.getId()) // 用户ID
.withClaim("username", user.getUsername())
.withClaim("role", user.getRole())
.withIssuedAt(new Date(currentTime)) // 签发时间
.withExpiresAt(new Date(currentTime + 3600000)) // 过期时间
.sign(Algorithm.HMAC256(secret)); // 签名密钥
}
// 验证Token
public boolean validateToken(String token){
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret))
.withIssuer("myapp")
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (JWTVerificationException exception) {
return false;
}
}
五、JWT:现代化的Token标准
5.1 什么是JWT?
JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。这些信息可以被验证和信任,因为它是数字签名的。JWT是Token的一种非常流行和标准化的实现。
JWT结构:
由三部分组成,通过点(.)分隔:Header.Payload.Signature
解码示例:
// Header
{
"alg": "HS256",
"typ": "JWT"
}
// Payload
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
// Signature
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
5.2 JWT实战代码
// 创建JWT (使用jjwt库示例)
public String createJWT(User user){
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(user.getId())
.setIssuer("myapp")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000))
.claim("username", user.getUsername())
.claim("role", user.getRole())
.signWith(SignatureAlgorithm.HS256, secret.getBytes())
.compact();
}
// 解析JWT
public Claims parseJWT(String jwt){
return Jwts.parser()
.setSigningKey(secret.getBytes())
.parseClaimsJws(jwt)
.getBody();
}
// 在Spring Security中使用JWT过滤器
@Component
public class JwtFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String token = resolveToken(request);
if (token != null && validateToken(token)) {
Authentication auth = getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
5.3 JWT的最佳实践
1. 安全存储
// 前端安全存储方案
// 不推荐:localStorage(易受XSS攻击)
// 推荐:HttpOnly Cookie(防XSS)或内存存储
2. 令牌刷新机制(双Token机制)
为避免Access Token过期导致用户频繁登录,同时保证安全,常采用Access Token(短期) + Refresh Token(长期)的方案。
public class TokenPair {
private String accessToken; // 短期有效:1小时
private String refreshToken; // 长期有效:7天
}
// 刷新令牌接口
@PostMapping("/refresh")
public ResponseEntity refresh(@RequestBody RefreshRequest request){
String refreshToken = request.getRefreshToken();
if (validateRefreshToken(refreshToken)) {
String userId = extractUserId(refreshToken);
String newAccessToken = generateAccessToken(userId);
return ResponseEntity.ok(new TokenPair(newAccessToken, refreshToken));
}
return ResponseEntity.status(401).build();
}
六、OAuth 2.0:标准的授权框架
6.1 什么是OAuth 2.0?
OAuth 2.0是一个授权框架,而非认证协议。它专注于解决一个核心问题:让第三方应用在获得用户授权后,能够代表用户访问该用户在某个服务提供商那里的受保护资源,而无需分享用户的密码。
OAuth 2.0核心角色:
- 资源所有者(Resource Owner):即用户。
- 客户端(Client):想要访问用户资源的第三方应用。
- 授权服务器(Authorization Server):验证用户身份并颁发访问令牌(Access Token)的服务器。
- 资源服务器(Resource Server):托管用户受保护资源的服务器(通常和授权服务器是同一实体)。
6.2 OAuth 2.0授权码流程(最安全、最常用)

6.3 OAuth 2.0实战代码(Spring Security OAuth2 简化示例)
// 授权服务器配置
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("clientapp")
.secret(passwordEncoder.encode("123456"))
.authorizedGrantTypes("authorization_code", "refresh_token")
.scopes("read", "write")
.redirectUris("http://localhost:8080/callback");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints){
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
}
// 资源服务器配置
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/private/**").authenticated()
.antMatchers("/api/admin/**").hasRole("ADMIN");
}
}
这里,OAuth2框架颁发的Access Token,其本身就可以是一个JWT,从而结合了OAuth2的标准化授权流程和JWT的自包含特性。
七、五大概念全方位对比
为了更清晰地理解这些概念的关系与区别,以下从多个维度进行对比。
7.1 功能定位对比
| 概念 |
本质 |
存储位置 |
主要用途 |
核心特点 |
| Cookie |
HTTP状态管理机制 |
浏览器 |
在客户端持久化存储少量数据,维持HTTP状态 |
由浏览器自动管理,随请求自动携带,有大小(4KB)和数量限制 |
| Session |
服务端会话信息 |
服务器 |
在服务端存储用户会话状态(如登录用户信息) |
有状态,服务端需要存储和管理会话数据 |
| Token |
访问凭证 |
客户端持有 |
作为身份认证和授权的凭证 |
无状态,凭证自包含信息,服务器只需验证 |
| JWT |
Token的一种实现标准 |
客户端持有 |
安全地传输声明信息,常用于作为Access Token |
标准化格式(Header.Payload.Signature),自包含、可验证、可签名 |
| OAuth2 |
授权框架 |
不直接定义存储 |
标准化第三方应用获取用户资源授权的流程 |
定义角色、流程和端点,不限定Token具体格式(常用JWT) |
7.2 应用场景对比
| 场景 |
推荐方案 |
原因说明 |
| 传统单体Web应用(服务端渲染) |
Session + Cookie |
简单易用,生态成熟,框架内置支持好(如Spring MVC) |
| 前后端分离应用(SPA、移动端API) |
JWT |
无状态,适合RESTful API认证,天然支持跨域,减轻服务器存储压力 |
| 第三方登录(微信登录、GitHub登录) |
OAuth 2.0 |
行业标准,安全可靠,用户无需向第三方提供密码 |
| 微服务/分布式系统架构 |
JWT 或 OAuth2 + JWT |
无状态,无需会话同步,Token可在各服务间传递并验证 |
| 同域简单应用或需要高度兼容性的场景 |
Token (可放在Cookie中) |
利用Cookie的自动管理特性,避免前端手动处理Header |
7.3 安全考虑对比
| 安全威胁 |
Cookie/Session方案防护 |
Token/JWT方案防护 |
| XSS攻击 |
设置 HttpOnly Cookie,阻止JS窃取。 |
避免将Token存储在localStorage,可存于HttpOnly Cookie或内存中。 |
| CSRF攻击 |
使用 SameSite Cookie属性,或添加CSRF Token校验。 |
Token通常放在Authorization Header中,不受CSRF影响(但需防范XSS窃取Token后伪造请求)。 |
| 令牌泄露/窃取 |
Session ID泄露等同于身份被盗。需使用HTTPS,设置合理过期时间。 |
Token泄露等同于身份被盗。需使用HTTPS,设置短期有效期,并使用Refresh Token机制。 |
| 数据篡改 |
Session数据在服务端,相对安全。Cookie内容需防篡改(可签名)。 |
通过Token的签名(如JWT)防止载荷被篡改,验证失败则拒绝。 |
总结与选型建议
经过以上的梳理,我们可以清晰地看到这五大概念的定位:
- Cookie是载体:HTTP协议层面的状态管理机制,是Session ID和Token可能的传输媒介之一。
- Session是状态:服务端维护的有状态的会话信息,需要借助Cookie或URL重写来关联客户端。
- Token是凭证:一种无状态的认证授权凭证,可以放在Cookie、HTTP Header或URL中。
- JWT是标准:Token的一种标准化、自包含、可验证的实现格式,常用于现代API。
- OAuth2是框架:一个解决第三方授权问题的标准化流程框架,其颁发的访问令牌常常采用JWT格式。
最终技术选型建议:
- 传统服务端渲染Web应用:
Session + HttpOnly & Secure Cookie 是不出错的选择。
- 前后端分离的API架构:
JWT + HTTP Authorization Header 是无状态和分布式场景下的优选。
- 需要接入第三方或提供第三方接入:
OAuth 2.0 是必选项,其Access Token推荐使用 JWT 格式。
- 微服务内部认证:
JWT 或 专门的API网关统一认证后转发Token。
记住,没有放之四海而皆准的“最佳方案”,只有最适合当前具体场景的“合适方案”。理解每个技术背后的本质、优缺点和适用场景,结合你的业务需求、团队技术栈和安全要求,才能做出明智的架构决策。希望这篇文章能帮助你在云栈社区的技术探索之路上,更清晰地把握这些核心概念。