Cannot convert access token to JSON

授权服务颁发token(未进行公私钥加密)后,携带此token请求资源服务,提示此错误。

使用token可以在线解析,跟踪代码后问题出现JwtHelper类decodeAndVerify方法,内容如下:

...
public static Jwt decodeAndVerify(String token, SignatureVerifier verifier) {
Jwt jwt = decode(token);
jwt.verifySignature(verifier);
return jwt;
}
...

其中jwt.verifySignature(verifier)中verifier为空,在下一层JwtImpl类中,方法如下:

...
public void verifySignature(SignatureVerifier verifier) {
verifier.verify(this.signingInput(), this.crypto);
}
...

所以在资源服务中,必须对JwtAccessTokenConverter初始化setVerifier。

...
/**
* Description: 为签名验证和解析提供转换器<br>
* Details: 看起来 {@link org.springframework.security.jwt.crypto.sign.RsaVerifier} 已经被标记为过时了, 究其原因, 似乎 Spring 已经发布了一个新的产品 Spring Authorization Server, 有空再研究.
*
* @see <a href="https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide">OAuth 2.0 Migration Guide</a>
* @see <a href="https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server">Announcing the Spring Authorization Server</a>
* @see JwtAccessTokenConverter
*/
@SuppressWarnings("deprecation")
private JwtAccessTokenConverter jwtAccessTokenConverter() {
final JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setVerifier(new org.springframework.security.jwt.crypto.sign.RsaVerifier(retrievePublicKey()));
return jwtAccessTokenConverter;
} /**
* Description: 获取公钥 (Verifier Key)<br>
* Details: 启动时调用
*
* @return java.lang.String
* @author LiKe
* @date 2020-07-22 11:45:40
*/
private String retrievePublicKey() {
final ClassPathResource classPathResource = new ClassPathResource(AUTHORIZATION_SERVER_PUBLIC_KEY_FILENAME);
try (
// ~ 先从本地取读取名为 authorization-server.pub 的公钥文件, 获取公钥
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(classPathResource.getInputStream()))
) {
log.debug("{} :: 从本地获取公钥 ...", RESOURCE_ID);
return bufferedReader.lines().collect(Collectors.joining("\n"));
} catch (IOException e) {
// ~ 如果本地没有, 则尝试通过授权服务器的 /oauth/token_key 端点获取公钥
log.debug("{} :: 从本地获取公钥失败: {}, 尝试从授权服务器 /oauth/token_key 端点获取 ...", RESOURCE_ID, e.getMessage());
final RestTemplate restTemplate = new RestTemplate();
final String responseValue = restTemplate.getForObject(AUTHORIZATION_SERVER_TOKEN_KEY_ENDPOINT_URL, String.class); log.debug("{} :: 授权服务器返回原始公钥信息: {}", RESOURCE_ID, responseValue);
return JSON.parseObject(JSON.parseObject(responseValue).getString("data")).getString("value");
}
}
...

最终定位出现问题是因为没有正确初始化signingKeyverifierKey

先说说 verifierKey 和 verifier, 默认值是一个 6 位的随机字符串 (new RandomValueStringGenerator().generate()), 和它 “配套” 的 verifier 是持有这个 verifierKey 的 MacSigner (用 HMACSHA256 算法验证 Signature) / RsaVerifier (用 RSA 公钥验证 Signature), 在 JwtAccessTokenConverter 的 decode 方法中作为签名校验器被传入 JwtHelper 的 decodeAndVerify(@NotNull String token, org.springframework.security.jwt.crypto.sign.SignatureVerifier verifier)

下面例子为使用原始MacSigner HMACSHA256 算法例子:

...
private JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("***");
try {
converter.afterPropertiesSet();
} catch (Exception e) {
e.printStackTrace();
}
return converter;
}
...

最后问题完美解决。

而在 JwtHelper 的目标方法中, 首先把 token 的三个部分 (以 . 分隔的) 拆分出来, Base64.urlDecode 解码. 再用我们传入的 verifier 将 “Header.Payload” 编码 (如果是 RSA, 就是公钥.) 并与拆分出来的 Signature 部分比对 (Reference: org.springframework.security.jwt.crypto.sign.RsaVerifier#verify).

对应的, signer 和 signingKey 作为签名 “组件” 存在, (可以看到在默认情况下, JwtAccessTokenConverter 对 JWT 的 Signature 采用的是对称加密, signingKey 和 verifierKey 一致) 在 JwtHelper 的 encode(@NotNull CharSequence content, @NotNull org.springframework.security.jwt.crypto.sign.Signer signer) 方法中, 被用于将 “Header.Payload” 加密 (如果是 RSA, 就是私钥) (Reference: org.springframework.security.jwt.crypto.sign.RsaSigner#sign).

所以算法本质上不是对 JWT 整体进行加解密, 而是对其中的 Signature 部分

参考

JWT在线解析

Java JwtAccessTokenConverter.setVerifierKey方法代碼示例 - 純淨天空 (vimsky.com)

最新文章

  1. js中return,this,arguments,currentStyle和getComputedStyle小析
  2. linux 下 jdk+tomcat+mysql 的 jsp 环境搭建
  3. HMM 自学教程(六)维特比算法
  4. window下appserv组合包配置asp标记风格与简短风格
  5. JS数据类型转换
  6. Learning Web
  7. L2-007. 家庭房产
  8. Sqlla: 数据库操作从未如此简单
  9. 九度OJ题目1105:字符串的反码
  10. logstash分析日志
  11. linux 中的 vim 设置粘贴板
  12. Echart横坐标时间轴滑动
  13. jvm详情——3、JVM基本垃圾回收算法回收策略
  14. POJ 1979 Heavy Transportation (kruskal)
  15. windows 10下sublime text3环境的搭建以及配置python开发环境
  16. 子div撑不开父div
  17. var声明变量
  18. 加入到java后台开发
  19. Judy Array API介绍
  20. 页面刷新 location.reload()

热门文章

  1. php伪协议总结
  2. 《剑指offer》面试题21. 调整数组顺序使奇数位于偶数前面
  3. thanos的日志能不能打到文件里面去?
  4. Cesium入门13 - Extras - 附加内容
  5. .Net Core依赖注入
  6. 一文读懂 HTTP/1HTTP/2HTTP/3
  7. python02day
  8. l线程池抓取lianjia
  9. Vue.js项目的兼容性与部署配置
  10. django之mysqlclient安装