1. 概述
本文将介绍 Spring Security OAuth2 中新增的 JWT Claims 验证功能。我们将重点学习如何使用 Spring Security OAuth 2.2.0.RELEASE 引入的 JwtClaimsSetVerifier
来验证 JWT 令牌中的声明(Claims)。
2. Maven 配置
首先在 pom.xml
中添加最新版本的 spring-security-oauth2
依赖:
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
3. Token Store 配置
接下来在资源服务器中配置 TokenStore
:
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
converter.setJwtClaimsSetVerifier(jwtClaimsSetVerifier());
return converter;
}
注意我们在 JwtAccessTokenConverter
中添加了新的验证器。关于 JwtTokenStore
的详细配置,可参考 Spring Security OAuth 中使用 JWT 的指南。
后续章节将讨论不同类型的声明验证器及其组合使用方式。
4. IssuerClaimVerifier 验证器
先从简单的开始 - 使用 IssuerClaimVerifier
验证发行者("iss")声明:
@Bean
public JwtClaimsSetVerifier issuerClaimVerifier() {
try {
return new IssuerClaimVerifier(new URL("http://localhost:8081"));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
这个配置添加了一个简单的发行者验证器:
- ✅ 当 JWT 令牌包含正确的发行者值时,验证通过
- ❌ 如果发行者值不匹配,抛出
InvalidTokenException
- ⚠️ 如果令牌不包含发行者声明,验证器不会触发(视为有效)
5. 自定义声明验证器
更灵活的是可以构建自定义声明验证器:
@Bean
public JwtClaimsSetVerifier customJwtClaimVerifier() {
return new CustomClaimVerifier();
}
以下是检查 JWT 中是否存在 user_name
声明的实现示例:
public class CustomClaimVerifier implements JwtClaimsSetVerifier {
@Override
public void verify(Map<String, Object> claims) throws InvalidTokenException {
String username = (String) claims.get("user_name");
if ((username == null) || (username.length() == 0)) {
throw new InvalidTokenException("user_name claim is empty");
}
}
}
通过实现 JwtClaimsSetVerifier
接口,我们可以完全自定义验证逻辑,实现任何需要的检查规则。
6. 组合多个验证器
使用 DelegatingJwtClaimsSetVerifier
可以组合多个验证器:
@Bean
public JwtClaimsSetVerifier jwtClaimsSetVerifier() {
return new DelegatingJwtClaimsSetVerifier(Arrays.asList(
issuerClaimVerifier(), customJwtClaimVerifier()));
}
DelegatingJwtClaimsSetVerifier
接收一个 JwtClaimsSetVerifier
列表,将验证过程委托给这些验证器:
- 所有验证器按顺序执行
- 任一验证器失败即抛出异常
- 所有验证器通过才视为有效令牌
7. 简单集成测试
实现完成后,通过集成测试验证声明验证器:
@RunWith(SpringRunner.class)
@SpringBootTest(
classes = ResourceServerApplication.class,
webEnvironment = WebEnvironment.RANDOM_PORT)
public class JwtClaimsVerifierIntegrationTest {
@Autowired
private JwtTokenStore tokenStore;
...
}
测试场景
场景1:令牌不含发行者声明(但含用户名)
@Test
public void whenTokenDontContainIssuer_thenSuccess() {
String tokenValue = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....";
OAuth2Authentication auth = tokenStore.readAuthentication(tokenValue);
assertTrue(auth.isAuthenticated());
}
✅ 验证通过:发行者验证器仅在声明存在时触发
场景2:令牌含有效发行者和用户名
@Test
public void whenTokenContainValidIssuer_thenSuccess() {
String tokenValue = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9....";
OAuth2Authentication auth = tokenStore.readAuthentication(tokenValue);
assertTrue(auth.isAuthenticated());
}
✅ 验证通过:所有声明均有效
场景3:令牌含无效发行者
@Test(expected = InvalidTokenException.class)
public void whenTokenContainInvalidIssuer_thenException() {
String tokenValue = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9....";
OAuth2Authentication auth = tokenStore.readAuthentication(tokenValue);
assertTrue(auth.isAuthenticated());
}
❌ 验证失败:发行者不匹配(如 http://localhost:8082
)
场景4:令牌不含用户名声明
@Test(expected = InvalidTokenException.class)
public void whenTokenDontContainUsername_thenException() {
String tokenValue = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9....";
OAuth2Authentication auth = tokenStore.readAuthentication(tokenValue);
assertTrue(auth.isAuthenticated());
}
❌ 验证失败:缺少必需的 user_name
声明
场景5:令牌含空用户名声明
@Test(expected = InvalidTokenException.class)
public void whenTokenContainEmptyUsername_thenException() {
String tokenValue = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9....";
OAuth2Authentication auth = tokenStore.readAuthentication(tokenValue);
assertTrue(auth.isAuthenticated());
}
❌ 验证失败:user_name
声明值为空
8. 总结
本文介绍了 Spring Security OAuth 中新增的 JWT 声明验证功能。通过组合使用内置验证器和自定义验证器,可以灵活实现各种 JWT 令牌验证需求。实际开发中,建议根据业务场景合理配置验证规则,避免踩坑。
完整源代码可在 GitHub 仓库 获取。