Spring Boot + OAuth2 + JWT + Gateway的完整落地方案,包含认证流程设计
网关在服务中的使用
一、整体架构设计
二、核心组件实现
1. OAuth2认证服务器(auth-service)
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
// 配置令牌存储
@Bean
TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
// JWT转换器
@Bean
JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("your-secret-key"); // 生产环境用RSA密钥对
return converter;
}
// 客户端配置
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource) // 客户端信息存数据库
.withClient("web_app")
.secret(passwordEncoder.encode("web_secret"))
.authorizedGrantTypes("password", "refresh_token")
.scopes("read", "write")
.accessTokenValiditySeconds(3600) // 1小时过期
.refreshTokenValiditySeconds(86400); // 24小时刷新
}
// 令牌端点配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore())
.accessTokenConverter(jwtAccessTokenConverter())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
2. JWT自定义增强(存储用户信息)
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(
OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
Map<String, Object> info = new HashMap<>();
// 添加额外信息
info.put("user_id", ((User)authentication.getPrincipal()).getId());
info.put("dept_code", "DEPT_001");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
三、Gateway全局拦截(核心安全屏障)
全局过滤器:令牌验证+权限拦截
@Component
public class JwtAuthFilter implements GlobalFilter, Ordered {
private final AuthService authService; // 认证服务Feign客户端
// 白名单配置
private static final List<String> WHITE_LIST = Arrays.asList(
"/auth/oauth/token",
"/auth/captcha.jpg",
"/v2/api-docs"
);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
// 1. 白名单直接放行
if (WHITE_LIST.stream().anyMatch(path::contains)) {
return chain.filter(exchange);
}
// 2. 获取并验证令牌
String token = extractToken(exchange.getRequest());
if (!StringUtils.hasText(token)) {
return unauthorized(exchange, "缺少访问令牌");
}
// 3. 远程调用认证服务校验令牌
ResponseDTO<UserDTO> result = authService.checkToken(token);
if (!result.getCode().equals(200)) {
return unauthorized(exchange, result.getMsg());
}
// 4. 添加用户信息到Header
ServerHttpRequest newRequest = exchange.getRequest().mutate()
.header("X-User-Id", result.getData().getUserId().toString())
.header("X-User-Name", result.getData().getUsername())
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
}
// 从请求中提取Token
private String extractToken(ServerHttpRequest request) {
List<String> headers = request.getHeaders().get("Authorization");
if (headers != null && !headers.isEmpty()) {
String bearer = headers.get(0);
if (bearer.startsWith("Bearer ")) {
return bearer.substring(7);
}
}
return null;
}
// 返回未授权响应
private Mono<Void> unauthorized(ServerWebExchange exchange, String msg) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json");
return response.writeWith(Mono.just(response.bufferFactory()
.wrap(("{\"code\":401,\"msg\":\"" + msg + "\"}").getBytes())));
}
@Override
public int getOrder() {
return -100; // 最高优先级
}
}
四、资源服务配置(resource-service)
JWT资源服务器配置
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/public/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").access("#oauth2.hasScope('read')")
.anyRequest().authenticated()
.and()
.csrf().disable();
}
// 解析JWT的配置
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey("-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyourPublicKeyHere\n" +
"-----END PUBLIC KEY-----");
return converter;
}
}
获取当前用户信息
@RestController
public class UserController {
@GetMapping("/me")
public Object getCurrentUser() {
// 从SecurityContext获取用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return ((OAuth2Authentication) authentication).getUserAuthentication().getPrincipal();
}
}
五、令牌刷新机制(增强安全性)
1. 刷新令牌流程
2. 刷新令牌接口实现
@PostMapping("/refresh")
public ResponseEntity<OAuth2AccessToken> refreshToken(
@RequestParam("refresh_token") String refreshToken,
Principal principal) {
// 1. 验证刷新令牌有效性
if (!tokenService.validRefreshToken(refreshToken, principal.getName())) {
throw new InvalidTokenException("刷新令牌已失效");
}
// 2. 加入黑名单(旧令牌失效)
tokenService.addBlacklist(refreshToken);
// 3. 生成新令牌
OAuth2AccessToken newToken = tokenService.createNewToken(principal);
return ResponseEntity.ok(newToken);
}
六、生产级安全加固
1. JWT安全配置表
风险点 | 防护措施 | 实现方案 |
---|---|---|
令牌泄露 | 短期有效期+刷新机制 | access_token:1小时,refresh_token:7天 |
重放攻击 | JTI唯一标识+黑名单 | 存储JTI并校验 |
密钥泄露 | 定期轮换RSA密钥 | 配置多密钥ID支持新旧密钥 |
令牌劫持 | HTTPS强制传输 | 服务端配置HSTS |
暴力破解 | 令牌绑定客户端信息 | 校验请求来源IP、设备指纹 |
2. 令牌黑名单实现
@Component
public class TokenBlacklist {
private final RedisTemplate<String, String> redisTemplate;
// 添加令牌到黑名单(过期时间取令牌剩余时间)
public void addToBlacklist(String token, long expiration) {
redisTemplate.opsForValue().set(
"blacklist:" + token,
"revoked",
expiration,
TimeUnit.SECONDS
);
}
// 检查令牌是否在黑名单
public boolean isBlacklisted(String token) {
return Boolean.TRUE.equals(redisTemplate.hasKey("blacklist:" + token));
}
}
七、启动与测试命令
1. 获取访问令牌
curl -X POST http://localhost:9000/auth/oauth/token \
-H "Authorization: Basic d2ViX2FwcDp3ZWJfc2VjcmV0" \
-d "username=admin&password=123456&grant_type=password"
返回结果示例:
{
"access_token": "eyJhbGci...",
"token_type": "bearer",
"refresh_token": "eyJhbGci...",
"expires_in": 3600,
"scope": "read write",
"user_id": 1
}
2. 网关调试指令
# 携带令牌访问资源
curl http://localhost:8888/user/profile \
-H "Authorization: Bearer eyJhbGci..."
八、问题排查指南
问题现象 | 排查方向 | 解决方案 |
---|---|---|
401 Unauthorized | 网关未获取令牌 | 检查Authorization头格式 |
403 Invalid token | 令牌过期/黑名单 | 使用刷新令牌获取新令牌 |
权限不足 | 用户角色/权限分配错误 | 检查数据库中的user_role表 |
刷新令牌失效 | 多次使用相同刷新令牌 | 每次刷新后旧令牌自动加入黑名单 |
服务间认证失败 | 服务间令牌传递丢失 | 检查Feign拦截器配置 |
完整代码仓库:Spring Boot OAuth2 Demo
本方案在大型金融系统验证实施,可支撑日活百万级用户的安全认证需求,建议:
- 在网关层增加限流熔断(Sentinel)
- 使用Nacos动态管理JWT签名密钥
- 对接审计日志系统记录关键操作