认证方式
在构建 Web 系统时,认证(Authentication) 和 会话管理(Session Management) 是安全设计的核心环节。传统的 Session-Cookie 和现代的 JWT(JSON Web Token) 是两种最常用的方案。本节将简述二者的差异,并重点讨论JWT的原理和实践。
JWT 的核心原理
jwt介绍
JSON Web Token(JWT) 是一种基于 JSON 的开放标准,用于在各方之间安全传输声明信息。它通常用于互联网应用程序中,用于身份验证和授权,是目前最常用和流行的跨域认证解决方案。
jwt组成
实际的jwt如下:
jwt是一个很长的字符串,通过.
字符分割成3个部分:第一部分被称为头部(header),第二部分被称为载荷(payload),第三部分被称为签名(signature)
这三个部分按header.payload.signature
的格式组成了jwt字符串。
通过jwt解密平台,我们可以看到这三部分在解密后分别包含的信息:
下面我们来详细讲述一下这3个部分的组成和功能。
header
这部分是一个JSON字符串,包含了签名算法alg
和Token类型type
信息。这个字符串在经过了Base64编码后成为了JWT的header部分。
{
"alg": "HS256",
"typ": "JWT"
}
payload
paylaod也是一个JSON字符串。其中有两种信息,一是官方规定的字段,诸如生效时间、过期时间、主题等。二是自定义的私有字段。这个字符串在经过了Base64编码后成为了JWT的payload部分。
{
"sub": "1", //官方字段:主题
"username": "Test User", //自定义字段:用户名
"permissions": ["ORDER_MANAGE"], //自定义字段:权限
"iat": 1743515030, //官方字段:签发时间
"exp": 1743518630 //官方字段:过期时间
}
⚠️由于包含的自定义信息会被解密出来,所以不要将敏感信息(手机号、邮箱号)等信息自定义在payload中。
signature
这部分是对Header 和 Payload 的签名,防止数据篡改。通过header中定义的加密算法和服务器中存储的密钥,对header.payload
进行加密后生成签名
jwt优缺点
优点 | 缺点 |
---|---|
无状态,适合分布式系统 | Token 无法主动失效 |
跨语言支持 | Payload 数据明文可见(需加密) |
自包含,减少数据库查询 | 密钥管理复杂 |
Session-Cookie认证原理
核心流程
-
用户登录
用户提交凭证(如用户名密码),服务端验证后生成 Session ID,存入服务端存储(如内存、Redis),并通过Set-Cookie
返回给客户端。 -
会话保持
客户端后续请求自动携带 Cookie(含 Session ID),服务端通过 Session ID 查询会话信息(如用户身份、权限)。// 服务端获取 Session HttpSession session = request.getSession(false); // 不创建新 Session if (session != null) { String userId = (String) session.getAttribute("userId"); String role = (String) session.getAttribute("role"); // 校验用户权限... } else { response.sendRedirect("/login"); // 会话过期,重定向到登录页 }
-
会话销毁
Session的销毁包括如下场景:- 显式注销:调用
session.invalidate()
。 - 超时销毁:通过
session.setMaxInactiveInterval(1800)
设置过期时间(如 30 分钟)。 - 服务端重启:若 Session 存储在内存中,重启后丢失(需持久化存储解决)。
- 显式注销:调用
Session 的生成
当用户首次登录时,服务端会创建一个唯一的 Session ID,并与用户数据绑定存储。
// 服务端创建 Session
HttpSession session = request.getSession(true); // 若不存在则创建新 Session
session.setAttribute("userId", "12345");
session.setAttribute("role", "admin");
- Session ID:通常是一个随机生成的字符串(如
D79E8C7E3C8B4E2A8F6E1D3C7A8B4E2A
)。 - 存储位置:服务端内存、数据库(如 MySQL)或缓存(如 Redis)。
Session 的验证流程
- 客户端发起请求
后续请求中,浏览器自动附加 Cookie 到请求头:
GET /user/profile HTTP/1.1
Host: example.com
Cookie: JSESSIONID=D79E8C7E3C8B4E2A8F6E1D3C7A8B4E2A
- 服务端验证 Session
服务端根据 Session ID 查询会话数据:
// 服务端获取 Session
HttpSession session = request.getSession(false); // 不创建新 Session
if (session != null) {
String userId = (String) session.getAttribute("userId");
String role = (String) session.getAttribute("role");
// 校验用户权限...
} else {
response.sendRedirect("/login"); // 会话过期,重定向到登录页
}
JWT与Session-Cookie对比
对比维度 | Session-Cookie | JWT |
---|---|---|
状态管理 | 有状态(服务端存储 Session) | 无状态(Token 自包含) |
扩展性 | 依赖集中式存储(集群部署需共享 Session) | 天然适合分布式系统 |
安全性 | 易防御 CSRF(结合 SameSite Cookie) | 依赖 HTTPS 和前端安全实践(防 XSS) |
性能 | 每次请求需查询 Session 存储 | 只需验证签名(无存储查询) |
主动失效 | 服务端可立即删除 Session | 依赖 Token 过期时间,无法主动失效 |
移动端兼容性 | 依赖 Cookie(部分 Native App 支持不完善) | 无 Cookie 依赖,更适合移动端 |
跨域支持 | 需处理 CORS 和 Cookie 域限制 | 无 Cookie,跨域更方便 |
适合 Session 的场景
- 单体应用:所有请求由同一服务处理,Session 管理简单。
- 需要实时权限控制:如银行系统需立即冻结用户账户。
- 对 CSRF 敏感:依赖
SameSite Cookie
提供内置防护。
适合 JWT 的场景
- 分布式/微服务架构:服务实例无需共享状态。
- 第三方认证(OAuth2):如使用 Google/Facebook 登录。
- 移动端/跨平台应用:避免 Cookie 兼容性问题。
- 高并发 API 服务:减少数据库/缓存查询压力。
JWT + Spring Security 实践
在使用JWT完成认证过程的实践中,使用SpringBoot框架的情况下引入Spring Security来做技术实现是一个非常好的选择。
Spring Security 的架构
Spring Security 是一个高度可定制的安全框架,其核心基于 过滤器链(Filter Chain) 和 责任链模式,通过一系列组件协作实现认证(Authentication)与授权(Authorization)。
Spring Security 通过 过滤器链(Filter Chain) 实现安全控制,核心流程如下:
关键组件
- SecurityContextHolder:存储当前线程的安全上下文(如用户身份)。
- AuthenticationManager:认证入口,委托给
Provider
实现(如DaoAuthenticationProvider
)。 - UserDetailsService:加载用户数据,从数据库
users
和authorities
查询用户信息。
JWT 与 Spring Security 的整合实践
在 Web 安全领域,Spring Security 和 JWT 各有其独特优势,但将它们结合使用可以解决传统 Session 架构的痛点,并适应现代分布式系统的需求。
- 自定义 JWT 过滤器
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 从 Header 提取 Token
String token = jwtTokenProvider.resolveToken(request);
if (token != null && jwtTokenProvider.validateToken(token)) {
// 解析 Token 获取用户信息
Authentication auth = jwtTokenProvider.getAuthentication(token);
// 将 Authentication 存入 SecurityContext
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
-
JWT核心工具类
public class JwtTokenProvider { private final JwtProperties jwtProperties; private SecretKey secretKey; @PostConstruct protected void init() { secretKey = Keys.hmacShaKeyFor(jwtProperties.getSecretKey().getBytes(StandardCharsets.UTF_8)); } public String createToken(User user) { Claims claims = Jwts.claims().setSubject(String.valueOf(user.getId())); claims.put("username", user.getUsername()); claims.put("permissions", user.getPermissions().stream() .map(Permission::getCode) .collect(Collectors.toSet())); Date now = new Date(); Date validity = new Date(now.getTime() + jwtProperties.getValidityInMs()); return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(validity) .signWith(secretKey) .compact(); } public JwtTokenInfo validateToken(String token) { Claims claims = Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token) .getBody(); return JwtTokenInfo.builder() .userId(Long.parseLong(claims.getSubject())) .username(claims.get("username", String.class)) .permissions(claims.get("permissions", List.class)) .build(); } }
-
Spring Security配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // 禁用 CSRF(无状态 API 不需要)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll() // 公开登录接口
.anyRequest().authenticated() // 其他接口需认证
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
}
}
纯 Spring Security 方案的对比
维度 | 纯 Spring Security (Session) | JWT + Spring Security |
---|---|---|
状态管理 | 有状态(服务端存储 Session) | 无状态(客户端存储 Token) |
扩展性 | 需 Session 共享(如 Redis) | 天然支持分布式 |
跨域支持 | 依赖 Cookie,需处理 CORS | 无 Cookie 限制,更灵活 |
性能 | 每次请求查询 Session 存储 | 仅需验证签名,性能更高 |
移动端兼容性 | 需处理 Cookie 兼容性问题 | 无 Cookie 依赖,更适合移动端 |
适用场景 | 单体应用、需要实时权限控制 | 微服务、高并发 API、第三方登录 |
总结
通过整合 JWT 与 Spring Security,开发者可以构建一个 无状态、高扩展、跨平台 的现代安全架构,尤其适合分布式系统。
留言