微服务认证方案对比与选型

微服务认证方案对比与选型

微服务架构下,认证授权面临前所未有的挑战。本文深入对比 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服务器
双轨并行
灰度切换
完全切换


认证方案没有银弹,需要根据项目规模、团队能力、安全要求、性能需求综合考量。本文的对比分析和选型指南希望能帮助你在众多方案中做出最适合的选择。

相关推荐
运维全栈笔记2 小时前
K8S部署Redis高可用全攻略:1主2从3哨兵架构实战
redis·docker·云原生·容器·架构·kubernetes·bootstrap
AI攻城狮3 小时前
AI Agent 从上线到删库跑路始末
云原生
weixin_446260854 小时前
城市智能化的底层基石:基于腾讯地图服务生态的移动定位与导航架构指引
大数据·人工智能·架构
@#¥&~是乱码鱼啦6 小时前
Spring分层架构:Controller、Service、Mapper数据链路,IOC的真实工作意义
java·spring·架构
vortex56 小时前
SafeLine 雷池WAF 真实体验,谈谈架构与原理
架构
该昵称用户已存在7 小时前
MyEMS 开源能源管理系统:模块化架构赋能精细化能源管控
架构·开源·能源
Ulyanov7 小时前
《现代 Python 桌面应用架构实战:PySide6 + QML 从入门到工程化》 开发环境搭建与工具链极简主义 —— 拒绝臃肿,构建工业级基座
开发语言·python·qt·ui·架构·系统仿真
郭龙_Jack7 小时前
Kubernetes 架构一张图讲透
架构
渣渣盟9 小时前
数据仓库 vs 数据湖 vs 湖仓一体:架构演进与选型
数据仓库·架构