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.
复制
@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.
复制
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.
复制
@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.
复制
@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.
复制
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