Serverless 安全增强篇:整合 OAuth2 + Token 黑名单 + Redis 缓存机制

随着 Serverless 架构的广泛应用,传统 Web 应用中依赖 Session 的认证模式面临重大挑战。本文将以 Spring Security 为核心,构建一套无状态、可扩展、支持 OAuth2、JWT、Redis 黑名单与 Refresh Token 的安全认证体系,适用于 Serverless 应用场景。

Serverless 应用的安全挑战

Serverless 应用的无状态特性决定了其认证模型不能依赖传统的会话管理。核心挑战包括:

身份认证用户身份需要跨请求验证,无 Session。权限校验如何高效识别用户角色与权限。Token 生命周期管理访问令牌的过期续签、注销。密钥管理JWT 签名密钥的安全管理。

Spring Security + JWT 构建轻量认证模型

我们采用如下组件构建无状态认证体系:

组件

作用

JWT

用户身份令牌,无状态传递

OAuth2

多客户端支持与统一授权

Redis

黑名单 + RefreshToken 存储

Spring Security

安全拦截器与权限控制

系统结构设计图
复制
+-----------------------------+ | 前端调用接口 | +-----------------------------+ | v +-----------------------------+ | API网关 / Serverless函数 | +-----------------------------+ | v +-----------------------------+ | Spring Security + Token拦截 | +-----------------------------+ | v +------------+ Redis +-------------+ | JWT Token校验 | <----> | Token黑名单 | +------------+ +-------------+ | v +--------------------------+ | 用户业务逻辑处理 | +--------------------------+1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.

关键模块代码实现

Spring Boot 启动类
复制
@SpringBootApplication public class ServerlessSecurityApplication { public static void main(String[] args) { SpringApplication.run(ServerlessSecurityApplication.class, args); } }1.2.3.4.5.6.
Security 配置类(无状态、JWT、资源服务器)
复制
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers(\"/api/public/**\", \"/api/token/refresh\").permitAll() .anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) .build(); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.
JWT 工具类
复制
public class JwtUtils { private static final Key key = Keys.hmacShaKeyFor(\"0123456789abcdef0123456789abcdef\".getBytes()); public static String generateAccessToken(String username, String roles, String jti) { return Jwts.builder() .setSubject(username) .setId(jti) .claim(\"roles\", roles) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 3600_000)) // 1小时 .signWith(key) .compact(); } public static String generateRefreshToken(String username, String jti) { return Jwts.builder() .setSubject(username) .setId(jti) .claim(\"type\", \"refresh\") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600_000)) // 7天 .signWith(key) .compact(); } public static Claims getClaims(String token) throws JwtException { return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.
Redis Token 黑名单工具类
复制
@Component public class TokenBlacklistUtil { private final StringRedisTemplate redisTemplate; public TokenBlacklistUtil(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } public void blacklistToken(String jti, long ttlSeconds) { redisTemplate.opsForValue().set(\"blacklist:\" + jti, \"1\", ttlSeconds, TimeUnit.SECONDS); } public boolean isTokenBlacklisted(String jti) { return redisTemplate.hasKey(\"blacklist:\" + jti); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.
RefreshToken 存储与刷新接口
复制
@RestController @RequestMapping(\"/api/token\") public class TokenController { @Autowired private StringRedisTemplate redisTemplate; @PostMapping(\"/refresh\") public ResponseEntity<?> refresh(@RequestParam String refreshToken) { Claims claims = JwtUtils.getClaims(refreshToken); String jti = claims.getId(); String username = claims.getSubject(); // 校验类型与黑名单 if (!\"refresh\".equals(claims.get(\"type\"))) { return ResponseEntity.badRequest().body(\"非法 token 类型\"); } if (!Boolean.TRUE.equals(redisTemplate.hasKey(\"refresh:\" + jti))) { return ResponseEntity.status(401).body(\"refreshToken 已失效\"); } // 生成新 token String newJti = UUID.randomUUID().toString(); String newAccessToken = JwtUtils.generateAccessToken(username, \"USER\", newJti); return ResponseEntity.ok(Map.of(\"accessToken\", newAccessToken)); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.
登录、退出控制器(模拟)
复制
@RestController @RequestMapping(\"/api/auth\") public class AuthController { @Autowired private StringRedisTemplate redisTemplate; @Autowired private TokenBlacklistUtil blacklistUtil; @PostMapping(\"/login\") public Map<String, String> login(@RequestParam String username) { String jti = UUID.randomUUID().toString(); String accessToken = JwtUtils.generateAccessToken(username, \"USER\", jti); String refreshToken = JwtUtils.generateRefreshToken(username, jti); // 保存 refreshToken 到 redis redisTemplate.opsForValue().set(\"refresh:\" + jti, username, 7, TimeUnit.DAYS); return Map.of(\"accessToken\", accessToken, \"refreshToken\", refreshToken); } @PostMapping(\"/logout\") public ResponseEntity<Void> logout(@RequestHeader(\"Authorization\") String authHeader) { String token = authHeader.replace(\"Bearer \", \"\"); Claims claims = JwtUtils.getClaims(token); String jti = claims.getId(); long remaining = (claims.getExpiration().getTime() - System.currentTimeMillis()) / 1000; blacklistUtil.blacklistToken(jti, remaining); // 同时清除 refreshToken redisTemplate.delete(\"refresh:\" + jti); return ResponseEntity.ok().build(); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.
application.yml 配置
复制
spring: security: oauth2: resourceserver: jwt: issuer-uri: https://auth.icoderoad.com/oauth2 jwk-set-uri: https://auth.icoderoad.com/oauth2/jwks redis: host: localhost port: 63791.2.3.4.5.6.7.8.9.10.

总结

无状态 Serverless 环境下,Spring Security 可通过 JWT、OAuth2 与 Redis 轻松实现高效认证体系:

不依赖 Session支持访问控制 + 黑名单管理Refresh Token 保证登录体验Redis 做 Token 生命周期缓存

今天就讲到这里,如果有问题需要咨询,大家可以直接留言或扫下方二维码来知识星球找我,我们会尽力为你解答。

阅读剩余
THE END