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 仓库 获取。


原始标题:New in Spring Security OAuth2 - Verify Claims

« 上一篇: MBassador 介绍
» 下一篇: 检测链表中的环