题目
设计高安全性的OAuth2资源服务器,实现JWT验证与自定义令牌增强
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
OAuth2资源服务器配置,JWT自定义声明处理,Spring Security过滤器链集成,令牌增强机制,权限动态验证
快速回答
实现要点:
- 配置
@EnableResourceServer并继承ResourceServerConfigurerAdapter - 使用
JwtAccessTokenConverter自定义JWT声明和签名验证 - 实现
TokenEnhancer接口添加自定义声明(如租户ID) - 重写
DefaultAccessTokenConverter映射权限到Authentication - 在资源服务器创建
@Bean JwtDecoder解析自定义JWT - 使用
@PreAuthorize实现方法级动态权限控制
场景需求
在微服务架构中,资源服务器需要:1) 验证携带自定义声明(如tenant_id)的JWT令牌;2) 根据声明动态控制API访问;3) 实现细粒度权限管理。
核心实现步骤
1. 自定义令牌增强器
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
// 添加租户ID和自定义声明
additionalInfo.put("tenant_id", "TENANT_123");
additionalInfo.put("user_role", authentication.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}2. 配置授权服务器
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain chain = new TokenEnhancerChain();
chain.setTokenEnhancers(Arrays.asList(new CustomTokenEnhancer(),
jwtAccessTokenConverter()));
endpoints.tokenEnhancer(chain);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("secret-key"); // 实际使用密钥库
// 自定义声明转换
DefaultAccessTokenConverter accessTokenConverter =
new DefaultAccessTokenConverter() {
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
OAuth2Authentication auth = super.extractAuthentication(claims);
// 将自定义声明添加到Authentication
auth.setDetails(claims.get("tenant_id"));
return auth;
}
};
converter.setAccessTokenConverter(accessTokenConverter);
return converter;
}
}3. 资源服务器配置
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.decoder(jwtDecoder());
}
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder decoder = NimbusJwtDecoder
.withSecretKey(new SecretKeySpec("secret-key".getBytes(), "HMACSHA256"))
.build();
// 添加自定义声明处理器
decoder.setJwtClaimsConverter(new CustomJwtClaimsConverter());
return decoder;
}
}
// 自定义声明转换器
public class CustomJwtClaimsConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
// 从JWT声明中提取自定义权限
List<String> roles = (List<String>) jwt.getClaims().get("user_role");
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
}
}4. 动态权限控制
@RestController
public class SecureController {
// 基于自定义声明的方法级安全
@PreAuthorize("@tenantSecurity.checkTenant(authentication, #tenantId)")
@GetMapping("/data/{tenantId}")
public String getData(@PathVariable String tenantId) {
return "Tenant-specific data";
}
}
@Component
public class TenantSecurity {
public boolean checkTenant(Authentication auth, String tenantId) {
// 从Authentication中获取租户ID
String tokenTenant = (String) auth.getDetails();
return tenantId.equals(tokenTenant);
}
}关键原理
- 令牌增强链:
TokenEnhancerChain按顺序处理令牌,先添加声明再编码为JWT - JWT声明映射:
DefaultAccessTokenConverter负责将Map声明转换为Authentication对象 - 资源服务器验证流程:1) 提取JWT → 2) 验证签名 → 3) 转换声明为权限 → 4) 构建
SecurityContext
最佳实践
- 使用非对称加密(RS256)替代HS256,私钥签名/公钥验证
- 将租户ID放在
jti(JWT ID)声明中避免冲突 - 通过
JwtClaimsSetVerifier验证令牌有效期和发行方 - 为敏感API启用
@PreAuthorize和@PostFilter组合控制
常见错误
- 未正确配置
TokenEnhancerChain顺序导致声明丢失 - 资源服务器与授权服务器的签名密钥不匹配
- 忽略JWT声明的大小限制(建议不超过4KB)
- 在JWT中存储敏感数据(如密码明文)
扩展知识
- JWT刷新机制:结合
refresh_token实现无感令牌更新 - 密钥轮换:使用
JWK Set URI动态获取公钥 - 令牌内省:当JWT过大时改用Opaque Token配合
/oauth/check_token端点 - 性能优化:使用
NimbusJwtDecoder的缓存机制减少公钥获取开销