题目
设计高安全性的OAuth2授权服务器并实现自定义JWT令牌增强
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
OAuth2授权服务器配置,JWT令牌定制,Spring Security扩展点,安全最佳实践
快速回答
实现自定义JWT令牌增强的关键步骤:
- 配置
@EnableAuthorizationServer定义授权服务器 - 实现
TokenEnhancer接口添加自定义声明 - 使用
TokenEnhancerChain组合自定义增强器和JWT转换器 - 在资源服务器配置匹配的
JwtAccessTokenConverter - 遵循安全最佳实践:密钥管理、声明最小化、令牌有效期控制
原理说明
Spring Security OAuth2的令牌增强机制允许在JWT生成过程中插入自定义逻辑。核心组件:
- TokenEnhancer:接口用于修改访问令牌属性
- JwtAccessTokenConverter:负责JWT编码/解码
- TokenEnhancerChain:组合多个增强器按顺序执行
- AuthorizationServerConfigurerAdapter:授权服务器配置入口
代码示例
1. 自定义令牌增强器
public class CustomJwtEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
// 添加自定义声明
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("tenant_id", getTenantId(authentication));
additionalInfo.put("user_role", getCustomRoles(authentication));
// 敏感信息加密示例
additionalInfo.put("user_license",
encryptLicense(authentication.getName()));
((DefaultOAuth2AccessToken) accessToken)
.setAdditionalInformation(additionalInfo);
return accessToken;
}
private String encryptLicense(String username) {
// 使用AES-GCM加密敏感数据
return EncryptionUtil.encrypt(username, ENCRYPTION_KEY);
}
}2. 授权服务器配置
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.tokenServices(tokenServices());
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 使用RSA非对称加密
KeyStoreKeyFactory keyFactory = new KeyStoreKeyFactory(
new ClassPathResource("keystore.jks"), "keystorePass".toCharArray());
converter.setKeyPair(keyFactory.getKeyPair("oauth"));
return converter;
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
services.setTokenStore(tokenStore());
services.setSupportRefreshToken(true);
// 关键:配置增强器链
TokenEnhancerChain chain = new TokenEnhancerChain();
chain.setTokenEnhancers(Arrays.asList(
new CustomJwtEnhancer(),
jwtAccessTokenConverter()
));
services.setTokenEnhancer(chain);
// 设置令牌有效期
services.setAccessTokenValiditySeconds(3600);
services.setRefreshTokenValiditySeconds(2592000);
return services;
}
}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();
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 必须与授权服务器使用相同密钥
converter.setVerifierKey(getPublicKeyAsString());
// 自定义声明转换
converter.setAccessTokenConverter(new CustomAccessTokenConverter());
return converter;
}
static class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
OAuth2Authentication auth = super.extractAuthentication(claims);
// 将自定义声明注入Authentication
((DefaultOAuth2AuthenticationDetails) auth.getDetails())
.setDecodedDetails(claims);
return auth;
}
}
}最佳实践
- 密钥管理:使用RSA非对称加密,私钥仅授权服务器持有
- 声明最小化:JWT只包含必要信息,避免存储敏感数据
- 令牌有效期:访问令牌设置较短有效期(1-2小时),刷新令牌较长(30天)
- 令牌撤销:实现令牌黑名单机制应对提前失效需求
- 加密敏感声明:对必要敏感字段进行AES-GCM加密
常见错误
- 密钥不一致:授权/资源服务器使用不同密钥导致令牌验证失败
- 声明冲突:自定义声明与标准声明(sub, exp等)命名冲突
- 未验证声明:资源服务器未校验关键声明(如tenant_id)
- 敏感数据泄露:在JWT中存储密码、API密钥等未加密数据
- 未设置有效期:令牌永久有效导致安全风险
扩展知识
- JWT声明类型:
- 注册声明(Registered Claims):预定义标准字段如iss, exp
- 公共声明(Public Claims):可自定义但需注册的字段
- 私有声明(Private Claims):完全自定义的字段
- 令牌注入攻击防护:验证iss(签发者)和aud(受众)声明
- 动态权限控制:在自定义声明中包含实时权限信息,结合Spring EL实现方法级安全:
@PreAuthorize("hasPermission(#id, 'TENANT_ADMIN')") - 密钥轮换策略:使用JWK Set端点实现无缝密钥轮换
- 令牌绑定:将令牌与客户端特征绑定防止令牌劫持