JwtAccessTokenConverter問題整理


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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM