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

微服务架构下,认证授权面临前所未有的挑战。本文深入对比 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 作为客户端标识。

S2Redis集群服务实例1负载均衡器用户浏览器S2Redis集群服务实例1负载均衡器用户浏览器POST /login {username, password}路由到实例1创建Session存储Session数据Session IDSet-Cookie: SESSIONID=xxx登录成功,Cookie已设置GET /api/user {Cookie: SESSIONID=xxx}路由到实例2查询Session返回用户信息返回用户数据返回响应

2.2 Spring Session + Redis 配置

@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;
    }
}
@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 使用示例

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 验证

@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 安全最佳实践

@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

@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 配置

@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 协议原理

应用BCAS服务器应用A用户应用BCAS服务器应用A用户访问应用A重定向到CAS登录输入用户名密码验证用户创建TGC携带ST访问应用A验证ST验证成功,返回用户信息返回受保护资源访问应用B重定向到CAS携带TGC验证TGC,创建新ST携带新ST访问应用B验证ST验证成功,返回用户信息返回受保护资源

5.2 CAS Server 部署

# 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 集成

@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 核心指标对比

指标SessionJWTOAuth2CAS
无状态性❌ 有状态✅ 无状态✅ 无状态⚠️ 有状态
跨域支持⚠️ 需要配置✅ 天然支持✅ 支持⚠️ 需要配置
Token管理服务端客户端授权服务器CAS Server
过期控制✅ 可立即失效❌ 无法主动失效✅ 可失效✅ 可失效
实现复杂度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
第三方登录⚠️ 需要扩展

6.2 安全特性对比

安全特性SessionJWTOAuth2CAS
防篡改
防重放⚠️ Session超时
防CSRF✅ SameSite⚠️ 需额外处理
防XSS✅ HttpOnly⚠️ 需额外处理
加密传输

6.3 性能对比

性能指标SessionJWTOAuth2CAS
首次认证延迟
后续请求延迟
存储需求RedisRedis数据库
扩展性

七、场景化选型指南

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第三方接入、细粒度权限
内部系统CASSSO、安全合规

SaaS平台

场景推荐方案理由
多租户认证JWT + 租户隔离无状态、易扩展
管理后台OAuth2细粒度权限、审计日志
用户登录混合方案兼容多种登录方式

7.3 团队能力选型

团队能力推荐方案
初创团队,快速上线Session / JWT
有安全专家OAuth2
有CAS使用经验CAS
全栈团队,SPA应用JWT
企业级,有SSO需求OAuth2 / CAS

八、混合方案实践

8.1 JWT + Refresh Token 方案

@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 混合方案

@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
微服务 + APIJWT
需要第三方登录OAuth2
企业 SSOCAS / OAuth2
移动 AppJWT
电商平台混合方案
金融系统Session + 额外安全措施

9.3 迁移路径

Session迁移JWT

JWT增强

接入OAuth2

接入CAS

当前方案

目标方案

逐步替换API端

加入Refresh Token

部署授权服务器

部署CAS服务器

双轨并行

灰度切换

完全切换


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

更多推荐