微服务认证方案对比与选型
微服务架构下,认证授权面临前所未有的挑战。本文深入对比 Session/Cookie、JWT、OAuth2、CAS 四种主流方案,帮助你做出最适合项目的技术选型。
一、问题背景
1.1 微服务架构下的认证挑战
微服务架构认证挑战
无状态认证
跨服务共享认证
安全防护
性能与扩展
Session无法跨服务共享
需要分布式Token方案
网关统一认证
服务间认证传递
Token防伪造
重放攻击防护
高并发处理
会话管理
1.2 认证方案全景图
| 方案 | 适用场景 | 复杂度 | 安全性 | 可扩展性 |
|---|---|---|---|---|
| Session/Cookie | 单体应用 | 低 | 中 | 低 |
| JWT | 微服务、API | 中 | 高 | 高 |
| OAuth2 | 第三方登录、SSO | 高 | 高 | 高 |
| CAS | 企业SSO | 高 | 高 | 中 |
二、方案一:Session/Cookie
2.1 原理概述
传统 Web 应用使用 Session 在服务端保存用户状态,Cookie 作为客户端标识。
S2 Redis集群 服务实例1 负载均衡器 用户浏览器 S2 Redis集群 服务实例1 负载均衡器 用户浏览器 POST /login {username, password} 路由到实例1 创建Session 存储Session数据 Session ID Set-Cookie: SESSIONID=xxx 登录成功,Cookie已设置 GET /api/user {Cookie: SESSIONID=xxx} 路由到实例2 查询Session 返回用户信息 返回用户数据 返回响应
2.2 Spring Session + Redis 配置
java
@Configuration
@EnableRedisHttpSession(
maxInactiveIntervalInSeconds = 3600,
redisNamespace = "spring:session"
)
public class SessionConfig {
@Bean
public RedisIndexedSessionRepository sessionRepository(
RedisConnectionFactory connectionFactory) {
RedisIndexedSessionRepository repository =
new RedisIndexedSessionRepository(connectionFactory);
repository.setDefaultSerializer(new JdkRedisSerializer());
repository.setFlushMode(RedisFlushMode.ON_SAVE);
repository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
return repository;
}
}
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated())
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home"))
.sessionManagement(session -> session
.maximumSessions(1)
.maxSessionsPreventsLogin(false));
return http.build();
}
}
2.3 优缺点分析
优点:
| 优点 | 说明 |
|---|---|
| 服务端可控 | 可随时禁用/失效用户会话 |
| 存储安全 | 用户数据保存在服务端 |
| 防篡改 | Cookie 只存储 Session ID |
| 天然防 CSRF | 可使用 SameSite Cookie |
缺点:
| 缺点 | 说明 |
|---|---|
| 扩展困难 | 分布式 Session 同步复杂 |
| 增加网络开销 | 每次请求需查询 Session |
| 存储成本 | 大量并发用户时存储压力大 |
| 不适合前后端分离 | API 调用需额外处理 |
2.4 适用场景
单体应用
前后端分离
是
否
微服务
是
否
技术选型
项目类型
Session ✓
JSON API?
JWT / OAuth2
Session + CORS
需要SSO?
OAuth2 / CAS
JWT
推荐使用场景:
- ✅ 传统单体 Web 应用
- ✅ 前后端不分离的 SSR 应用
- ✅ 需要强制下线用户的管理场景
- ✅ 对安全性要求极高的金融系统
不推荐场景:
- ❌ 微服务架构(除非使用 Spring Session)
- ❌ 高并发 API 服务
- ❌ 移动端 App 后端
三、方案二:JWT
3.1 原理概述
JWT(JSON Web Token)是一种自包含的令牌格式,将用户信息加密编码在 Token 中。
Base64
Base64
Base64
Signature区
HMACSHA256
Base64 Header
Base64 Payload
secret密钥
Payload区
sub: 用户ID
exp: 过期时间
roles: 权限
Header区
alg: HS256
type: JWT
Header
Token字符串
Payload
Signature
xxx.yyy.zzz
3.2 JJWT 使用示例
java
public class JwtTokenService {
private final SecretKey secretKey;
private final long accessTokenExpire;
private final long refreshTokenExpire;
@Autowired
public JwtTokenService(
@Value("${jwt.secret}") String secret,
@Value("${jwt.access-expire:3600}") long accessTokenExpire,
@Value("${jwt.refresh-expire:604800}") long refreshTokenExpire) {
this.secretKey = Keys.hmacShaKeyFor(
DatatypeConverter.parseBase64Binary(secret));
this.accessTokenExpire = accessTokenExpire;
this.refreshTokenExpire = refreshTokenExpire;
}
/**
* 生成Access Token
*/
public String generateAccessToken(UserDetails user) {
Map<String, Object> claims = new HashMap<>();
claims.put("type", "access");
claims.put("roles", user.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
return Jwts.builder()
.subject(user.getUsername())
.claims(claims)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() +
accessTokenExpire * 1000))
.signWith(secretKey)
.compact();
}
/**
* 生成Refresh Token
*/
public String generateRefreshToken(UserDetails user) {
Map<String, Object> claims = new HashMap<>();
claims.put("type", "refresh");
return Jwts.builder()
.subject(user.getUsername())
.claims(claims)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() +
refreshTokenExpire * 1000))
.signWith(secretKey)
.compact();
}
/**
* 解析Token
*/
public Claims parseToken(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();
}
/**
* 验证Token
*/
public boolean validateToken(String token) {
try {
parseToken(token);
return true;
} catch (JwtException e) {
return false;
}
}
/**
* 检查是否过期
*/
public boolean isTokenExpired(String token) {
try {
Claims claims = parseToken(token);
return claims.getExpiration().before(new Date());
} catch (JwtException e) {
return true;
}
}
/**
* 获取用户名
*/
public String getUsername(String token) {
return parseToken(token).getSubject();
}
}
3.3 网关 JWT 验证
java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenService jwtTokenService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
String token = authHeader.substring(7);
try {
// 1. 验证Token
if (!jwtTokenService.validateToken(token)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// 2. 解析用户信息
Claims claims = jwtTokenService.parseToken(token);
String username = claims.getSubject();
// 3. 验证黑名单
if (isTokenBlacklisted(token)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Token has been revoked");
return;
}
// 4. 创建认证信息
UserDetails userDetails = userDetailsService
.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
// 5. 存入SecurityContext
SecurityContextHolder.getContext()
.setAuthentication(authentication);
// 6. 传递用户信息到下游服务
request.setAttribute("claims", claims);
chain.doFilter(request, response);
} catch (JwtException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Invalid token: " + e.getMessage());
}
}
}
3.4 JWT 安全最佳实践
java
@Configuration
public class JwtSecurityConfig {
@Bean
public SecurityFilterChain jwtFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.decoder(jwtDecoder())));
return http.build();
}
/**
* Token黑名单(Redis实现)
*/
@Bean
public TokenBlacklistService tokenBlacklistService(
StringRedisTemplate redisTemplate) {
return new TokenBlacklistService(redisTemplate);
}
}
@Component
public class TokenBlacklistService {
private final StringRedisTemplate redisTemplate;
private static final String BLACKLIST_PREFIX = "jwt:blacklist:";
public TokenBlacklistService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 将Token加入黑名单
*/
public void blacklistToken(String token, long expireSeconds) {
String key = BLACKLIST_PREFIX + hashToken(token);
redisTemplate.opsForValue().set(key, "1",
Duration.ofSeconds(expireSeconds));
}
/**
* 检查Token是否在黑名单
*/
public boolean isBlacklisted(String token) {
String key = BLACKLIST_PREFIX + hashToken(token);
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
private String hashToken(String token) {
return DigestUtils.sha256Hex(token);
}
}
3.5 优缺点分析
优点:
| 优点 | 说明 |
|---|---|
| 无状态 | 服务端无需存储 Token |
| 可扩展 | 适合微服务架构 |
| 跨域支持 | 天然支持跨域请求 |
| 性能高 | 减少数据库查询 |
| 自包含 | 包含用户信息和签名 |
缺点:
| 缺点 | 说明 |
|---|---|
| 无法主动失效 | Token 过期前无法强制注销 |
| 数据量大 | Payload 过大会增加请求大小 |
| 无状态 | 增加了管理复杂度 |
| 安全风险 | 泄露后难以追回 |
3.6 适用场景
推荐使用场景:
- ✅ 微服务架构的 API 认证
- ✅ 前后端分离的 SPA 应用
- ✅ 需要跨域访问的 API
- ✅ 移动端 App 后端
- ✅ 无需强制下线用户的功能
不推荐场景:
- ❌ 需要强制下线用户的功能
- ❌ Token 有效期需要动态调整
- ❌ 对安全要求极高的金融系统
四、方案三:OAuth2
4.1 OAuth2 授权模式
OAuth2授权模式
授权码模式
密码凭证模式
客户端凭证模式
简化模式
最安全,推荐使用
适用于内部应用
适用于服务间调用
适用于纯前端
4.2 Spring Authorization Server
java
@Configuration
@EnableWebSecurity
public class AuthorizationServerSecurityConfig {
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(
HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults());
http
.exceptionHandling(exceptions -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML))))
.oauth2ResourceServer(resourceServer -> resourceServer
.jwt(Customizer.withDefaults()));
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(
HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/error").permitAll()
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
@Configuration
public class RegisteredClientConfig {
@Bean
public RegisteredClientRepository registeredClientRepository(
PasswordEncoder passwordEncoder) {
RegisteredClient loginClient = RegisteredClient
.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret(passwordEncoder.encode("secret"))
.clientAuthenticationMethod(
ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(
AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(
AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(
AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/spring")
.postLogoutRedirectUri("http://127.0.0.1:8080/")
.scope("openid")
.scope("profile")
.scope("message.read")
.scope("message.write")
.build();
return new InMemoryRegisteredClientRepository(loginClient);
}
}
4.3 Resource Server 配置
java
@Configuration
@EnableResourceServer
public class ResourceServerConfig {
@Bean
public SecurityFilterChain resourceServerFilterChain(
HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/actuator/health").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())));
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(new CustomJwtGrantedAuthoritiesConverter());
return converter;
}
}
public class CustomJwtGrantedAuthoritiesConverter
implements Converter<Jwt, Collection<GrantedAuthority>> {
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
List<GrantedAuthority> authorities = new ArrayList<>();
// 从 scope 提取
List<String> scopes = jwt.getClaimAsStringList("scope");
if (scopes != null) {
authorities.addAll(scopes.stream()
.map(scope -> new SimpleGrantedAuthority("SCOPE_" + scope))
.collect(Collectors.toList()));
}
// 从 roles 提取
List<String> roles = jwt.getClaimAsStringList("roles");
if (roles != null) {
authorities.addAll(roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList()));
}
return authorities;
}
}
4.4 优缺点分析
优点:
| 优点 | 说明 |
|---|---|
| 标准协议 | RFC 6749,国际通用 |
| 安全性高 | 授权码模式防泄露 |
| 第三方登录 | 支持 OAuth 登录 |
| SSO支持 | 可实现单点登录 |
| 细粒度权限 | 支持 Scope 权限控制 |
缺点:
| 缺点 | 说明 |
|---|---|
| 复杂度高 | 实现和维护成本大 |
| 学习曲线 | 需要理解协议细节 |
| 性能开销 | 多次网络请求 |
| 依赖外部 | 依赖授权服务器 |
4.5 适用场景
推荐使用场景:
- ✅ 需要第三方应用授权
- ✅ 企业级 SSO 场景
- ✅ 需要细粒度权限控制
- ✅ 多系统间的安全认证
不推荐场景:
- ❌ 简单的内部系统
- ❌ 资源受限的 IoT 设备
- ❌ 追求极致性能的场景
五、方案四:CAS单点登录
5.1 CAS 协议原理
应用B CAS服务器 应用A 用户 应用B CAS服务器 应用A 用户 访问应用A 重定向到CAS登录 输入用户名密码 验证用户 创建TGC 携带ST访问应用A 验证ST 验证成功,返回用户信息 返回受保护资源 访问应用B 重定向到CAS 携带TGC 验证TGC,创建新ST 携带新ST访问应用B 验证ST 验证成功,返回用户信息 返回受保护资源
5.2 CAS Server 部署
yaml
# docker-compose.yml
version: '3'
services:
cas:
image: apereo/cas:v6.6.0
container_name: cas-server
ports:
- "8443:8443"
environment:
- CAS_SERVER_NAME=https://cas.example.com
- CAS_SERVER_URL=https://cas.example.com:8443
- DATABASE_HOST=postgres
- DATABASE_NAME=cas
- DATABASE_USER=cas
- DATABASE_PASSWORD=cas_password
volumes:
- ./etc/cas:/etc/cas
- ./keys:/etc/cas/keys
depends_on:
- postgres
5.3 CAS Client 集成
java
@Configuration
@EnableWebSecurity
public class CasSecurityConfig {
@Value("${cas.server-url-prefix}")
private String casServerUrlPrefix;
@Value("${cas.server-login-url}")
private String casServerLoginUrl;
@Value("${cas.server-logout-url}")
private String casServerLogoutUrl;
@Value("${app.server-url}")
private String appServerUrl;
@Bean
public SecurityFilterChain casFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**", "/login").permitAll()
.anyRequest().authenticated())
.addFilterBefore(casAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class)
.logout(logout -> logout
.logoutSuccessUrl(casServerLogoutUrl + "?service=" +
URLEncoder.encode(appServerUrl, "UTF-8")));
return http.build();
}
@Bean
public CasAuthenticationFilter casAuthenticationFilter() {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
return filter;
}
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setUserDetailsService(userDetailsService());
provider.setServiceProperties(serviceProperties());
provider.setTicketValidator(ticketValidator());
provider.setKey("casAuthenticationProvider");
return provider;
}
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties properties = new ServiceProperties();
properties.setService(appServerUrl + "/login/cas");
properties.setSendRenew(false);
properties.setTicketValidator(ticketValidator());
return properties;
}
@Bean
public Cas20ServiceTicketValidator ticketValidator() {
return new Cas20ServiceTicketValidator(casServerUrlPrefix);
}
}
5.4 优缺点分析
优点:
| 优点 | 说明 |
|---|---|
| 开箱即用 | 完整的SSO解决方案 |
| 标准协议 | 基于CAS协议 |
| 会话管理 | 集中式会话控制 |
| 单点登出 | SLO支持 |
| 多协议支持 | CAS、OAuth、SAML |
缺点:
| 缺点 | 说明 |
|---|---|
| 架构复杂 | 需要独立部署CAS Server |
| 性能依赖 | CAS Server是单点 |
| 定制困难 | 配置复杂 |
| 社区活跃度 | 相比Spring Security较低 |
5.5 适用场景
推荐使用场景:
- ✅ 企业内部多系统SSO
- ✅ 需要统一认证中心
- ✅ 需要单点登出功能
- ✅ 有Java技术栈的团队
不推荐场景:
- ❌ 小型项目
- ❌ 追求轻量化的场景
- ❌ 非Java技术栈
六、方案对比分析
6.1 核心指标对比
| 指标 | Session | JWT | OAuth2 | CAS |
|---|---|---|---|---|
| 无状态性 | ❌ 有状态 | ✅ 无状态 | ✅ 无状态 | ⚠️ 有状态 |
| 跨域支持 | ⚠️ 需要配置 | ✅ 天然支持 | ✅ 支持 | ⚠️ 需要配置 |
| Token管理 | 服务端 | 客户端 | 授权服务器 | CAS Server |
| 过期控制 | ✅ 可立即失效 | ❌ 无法主动失效 | ✅ 可失效 | ✅ 可失效 |
| 实现复杂度 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 第三方登录 | ❌ | ❌ | ✅ | ⚠️ 需要扩展 |
6.2 安全特性对比
| 安全特性 | Session | JWT | OAuth2 | CAS |
|---|---|---|---|---|
| 防篡改 | ✅ | ✅ | ✅ | ✅ |
| 防重放 | ⚠️ Session超时 | ❌ | ✅ | ✅ |
| 防CSRF | ✅ SameSite | ⚠️ 需额外处理 | ✅ | ✅ |
| 防XSS | ✅ HttpOnly | ⚠️ 需额外处理 | ✅ | ✅ |
| 加密传输 | ✅ | ✅ | ✅ | ✅ |
6.3 性能对比
| 性能指标 | Session | JWT | OAuth2 | CAS |
|---|---|---|---|---|
| 首次认证延迟 | 中 | 低 | 高 | 高 |
| 后续请求延迟 | 中 | 低 | 低 | 中 |
| 存储需求 | Redis | 无 | Redis | 数据库 |
| 扩展性 | 中 | 高 | 高 | 中 |
七、场景化选型指南
7.1 选型决策树
单体应用
是
否
微服务
是
否
是
否
移动App
SPA
是
否
开始选型
项目类型
Session够用
使用Session/Cookie
考虑JWT
需要第三方登录
OAuth2
需要SSO
OAuth2 / CAS
JWT
推荐JWT + HTTPS
需要后端API
JWT / OAuth2
Session + SSR
7.2 行业场景选型
电商平台
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 用户登录 | Session + Redis | 需要强制下线、购物车会话 |
| 第三方登录 | OAuth2 | 微信、支付宝登录 |
| API网关 | JWT | 高性能、支持跨域 |
| 内部系统 | CAS | 统一认证中心 |
金融系统
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 用户交易 | Session + 强风控 | 需要强制下线、实时监控 |
| API开放平台 | OAuth2 + JWT | 第三方接入、细粒度权限 |
| 内部系统 | CAS | SSO、安全合规 |
SaaS平台
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 多租户认证 | JWT + 租户隔离 | 无状态、易扩展 |
| 管理后台 | OAuth2 | 细粒度权限、审计日志 |
| 用户登录 | 混合方案 | 兼容多种登录方式 |
7.3 团队能力选型
| 团队能力 | 推荐方案 |
|---|---|
| 初创团队,快速上线 | Session / JWT |
| 有安全专家 | OAuth2 |
| 有CAS使用经验 | CAS |
| 全栈团队,SPA应用 | JWT |
| 企业级,有SSO需求 | OAuth2 / CAS |
八、混合方案实践
8.1 JWT + Refresh Token 方案
java
@Service
public class HybridAuthService {
private final JwtTokenService jwtTokenService;
private final RefreshTokenRepository refreshTokenRepository;
/**
* 用户登录
*/
public AuthResult login(String username, String password) {
// 1. 验证用户名密码
UserDetails user = userDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("Invalid credentials");
}
// 2. 生成Access Token(短期)
String accessToken = jwtTokenService.generateAccessToken(user);
// 3. 生成Refresh Token(长期)
String refreshToken = jwtTokenService.generateRefreshToken(user);
// 4. 存储Refresh Token
RefreshToken token = new RefreshToken();
token.setToken(hashToken(refreshToken));
token.setUsername(username);
token.setExpiresAt(calculateRefreshExpiry());
token.setRevoked(false);
refreshTokenRepository.save(token);
return new AuthResult(accessToken, refreshToken,
jwtTokenService.getAccessTokenExpire());
}
/**
* 刷新Access Token
*/
public AuthResult refresh(String refreshToken) {
// 1. 验证Refresh Token
Claims claims = jwtTokenService.parseToken(refreshToken);
if (!"refresh".equals(claims.get("type"))) {
throw new InvalidTokenException("Invalid token type");
}
// 2. 检查是否已撤销
String hashedToken = hashToken(refreshToken);
RefreshToken stored = refreshTokenRepository
.findByToken(hashedToken)
.orElseThrow(() -> new InvalidTokenException("Token not found"));
if (stored.isRevoked()) {
throw new InvalidTokenException("Token has been revoked");
}
// 3. 获取用户信息
UserDetails user = userDetailsService
.loadUserByUsername(claims.getSubject());
// 4. 生成新的Access Token
String newAccessToken = jwtTokenService.generateAccessToken(user);
// 5. 可选:轮转Refresh Token
stored.setRevoked(true);
refreshTokenRepository.save(stored);
String newRefreshToken = jwtTokenService.generateRefreshToken(user);
RefreshToken newStored = new RefreshToken();
newStored.setToken(hashToken(newRefreshToken));
newStored.setUsername(claims.getSubject());
newStored.setExpiresAt(calculateRefreshExpiry());
newStored.setRevoked(false);
refreshTokenRepository.save(newStored);
return new AuthResult(newAccessToken, newRefreshToken,
jwtTokenService.getAccessTokenExpire());
}
/**
* 登出(撤销Refresh Token)
*/
public void logout(String refreshToken) {
String hashedToken = hashToken(refreshToken);
refreshTokenRepository.findByToken(hashedToken)
.ifPresent(token -> {
token.setRevoked(true);
refreshTokenRepository.save(token);
});
}
}
8.2 Session + JWT 混合方案
java
@Configuration
public class HybridAuthConfig {
@Bean
public SecurityFilterChain hybridFilterChain(
HttpSecurity http,
JwtAuthenticationFilter jwtFilter) throws Exception {
http
// Web端使用Session
.securityMatcher("/web/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/web/public/**").permitAll()
.anyRequest().authenticated())
.formLogin(form -> form
.loginProcessingUrl("/web/login")
.successHandler(webLoginSuccessHandler())
.failureHandler(webLoginFailureHandler()))
.sessionManagement(session -> session
.maximumSessions(1))
.logout(logout -> logout
.logoutUrl("/web/logout")
.logoutSuccessUrl("/web/login"));
// API端使用JWT
http.securityMatcher("/api/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated())
.addFilterBefore(jwtFilter,
UsernamePasswordAuthenticationFilter.class)
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
九、总结
9.1 选型决策矩阵
避免使用区域
低安全性 + 高复杂度
无认证方案
谨慎使用区域
中安全性 + 低复杂度
传统Session
视情况使用区域
高安全性 + 高复杂度
OAuth2
CAS
优先使用区域
高安全性 + 低复杂度
Session 方案
JWT 方案
9.2 最终建议
| 项目特征 | 推荐方案 |
|---|---|
| 小型单体应用 | Session |
| 微服务 + API | JWT |
| 需要第三方登录 | OAuth2 |
| 企业 SSO | CAS / OAuth2 |
| 移动 App | JWT |
| 电商平台 | 混合方案 |
| 金融系统 | Session + 额外安全措施 |
9.3 迁移路径
Session迁移JWT
JWT增强
接入OAuth2
接入CAS
当前方案
目标方案
逐步替换API端
加入Refresh Token
部署授权服务器
部署CAS服务器
双轨并行
灰度切换
完全切换
认证方案没有银弹,需要根据项目规模、团队能力、安全要求、性能需求综合考量。本文的对比分析和选型指南希望能帮助你在众多方案中做出最适合的选择。