1. 程式人生 > 其它 >【Azure Developer】如何驗證 Azure AD的JWT Token (JSON Web 令牌)?

【Azure Developer】如何驗證 Azure AD的JWT Token (JSON Web 令牌)?

問題描述

使用微軟Azure AD,對授權進行管理。通過所註冊應用的OAuth API(https://login.chinacloudapi.cn/{TENANT ID}/oauth2/v2.0/token),已經獲取到Token,但是如何在應用端對Token進行驗證呢?

問題場景類似於:一個基於 Java 的API服務,使用Azure AD生產的access_token來做為客戶端訪問API服務的身份驗證。

步驟如下:

  1. 客戶端申請AAD的access_token
  2. 客戶端在header裡新增Authorization引數(值為Bearer <access_token>)訪問API
  3. 服務端在收到header裡的token後,驗證此token是否有效。若有效則進行具體的業務資料處理;若無效,則返回認證失敗

問題是: 在Java程式碼中如何來驗證這個Token是否有效呢?

問題解決

在驗證JWT的關鍵問題中,是需要獲取到生產Token時候的公鑰金鑰。因為 Azure AD 使用一組私鑰簽署JWT Token訪問令牌,並在 JWKS URI 提供相應的公共金鑰。

第一步:通過Azure AD 的 openid-configuration 終結點,可以獲取到 JWKS URI,中國區公用的JWKS URI 為:https://login.partner.microsoftonline.cn/common/discovery/keys ,獲取方式見下圖:

第二步:在程式碼中,直接使用JWKS URI來解析公鑰金鑰,然後生成RSA256Algorithm 物件,以下為程式碼片段:

 URL keysURL = new URL("https://login.partner.microsoftonline.cn/common/discovery/keys");
 JwkProvider provider = new UrlJwkProvider(keysURL);
 Jwk jwk = provider.get(jwt.getKeyId());
 Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
 algorithm.verify(jwt);

全部的Java 程式碼:

package jwttest;

import java.net.MalformedURLException;
import java.net.URL;
import java.security.interfaces.RSAPublicKey;
import java.util.*;
import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.UrlJwkProvider;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Scanner;

public class Main {


    public static void main(String[] args) {

        System.out.println("Start to verify the AAD TOken...");

        // Using Scanner for Getting Input from User
        Scanner in = new Scanner(System.in);

        String stoken = in.nextLine();
        System.out.println("You entered Token is :: " + stoken);

        if (stoken.length() < 50) {
            stoken = "eyJ0eXAiOiJKV1QiLCJhbGciO......................_-dIQ";

            System.out.println("You entered Token is too short, use the default value ::  " + stoken);
        }

        DecodedJWT jwt = JWT.decode(stoken);

        System.out.println("JWT Key ID is : " + jwt.getKeyId());

        JwkProvider provider = null;
        Jwk jwk = null;
        Algorithm algorithm = null;

        try {
            URL keysURL = new URL("https://login.partner.microsoftonline.cn/common/discovery/keys");
            provider = new UrlJwkProvider(keysURL);
            jwk = provider.get(jwt.getKeyId());
            algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
            algorithm.verify(jwt);
            // if the token signature is invalid, the method will throw
            // SignatureVerificationException

            System.out.println("JWT Validation completed.");

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (JwkException e) {
            e.printStackTrace();
        } catch (SignatureVerificationException e) {

            System.out.println(e.getMessage());

        }
    }
}

需要新增的依賴有(pom.xml):

  <dependency> 
      <groupId>com.fasterxml.jackson.core</groupId> 
      <artifactId>jackson-core</artifactId> 
      <version>2.13.0</version> 
    </dependency> 
    <dependency> 
      <groupId>com.fasterxml.jackson.core</groupId> 
      <artifactId>jackson-databind</artifactId> 
      <version>2.13.0</version> 
    </dependency> 
    <dependency> 
      <groupId>com.fasterxml.jackson.core</groupId> 
      <artifactId>jackson-annotations</artifactId> 
      <version>2.13.0</version> 
    </dependency> 
    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>3.16.0</version>
  </dependency>
    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>jwks-rsa</artifactId>
      <version>0.18.0</version>
  </dependency>

程式碼執行結果為:

在上面這段簡單的程式碼中,也先後遇見了啟動異常,主要是新增依賴時候少加了com.fasterxml.jackson.core,並且需要保持版本的一致性。否則,會依次遇見如下錯誤:

錯誤一:java.lang.ClassNotFoundException: com.fasterxml.jackson.core.exc.InputCoercionException

Exception in thread "main" java.lang.NoClassDefFoundError: com/fasterxml/jackson/core/exc/InputCoercionException
        at com.auth0.jwt.impl.JWTParser.addDeserializers(JWTParser.java:58)
        at com.auth0.jwt.impl.JWTParser.<init>(JWTParser.java:24)
        at com.auth0.jwt.impl.JWTParser.<init>(JWTParser.java:20)
        at com.auth0.jwt.JWTDecoder.<init>(JWTDecoder.java:32)   
        at com.auth0.jwt.JWT.decode(JWT.java:45)
        at blob.Main.main(Main.java:36)
Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.core.exc.InputCoercionException   
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)   
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        ... 6 more

錯誤二:java.lang.ClassNotFoundException: com.fasterxml.jackson.core.util.JacksonFeature

Exception in thread "main" java.lang.NoClassDefFoundError: com/fasterxml/jackson/core/util/JacksonFeature
        at com.fasterxml.jackson.databind.ObjectMapper.<init>(ObjectMapper.java:673)
        at com.fasterxml.jackson.databind.ObjectMapper.<init>(ObjectMapper.java:576)
        at com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper(JWTParser.java:64)
        at com.auth0.jwt.impl.JWTParser.<init>(JWTParser.java:20)
        at com.auth0.jwt.JWTDecoder.<init>(JWTDecoder.java:32)
        at com.auth0.jwt.JWT.decode(JWT.java:45)
        at blob.Main.main(Main.java:36)
Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.core.util.JacksonFeature
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        ... 7 more

只要在引入jackson-corejackson-databindjackson-annotations 時保持版本一直即可解決以上問題。如本示例中使用的版本為:2.13.0

Java 應用驗證Azure AD的 Token演示動畫:

參考資料

Azure Active Directory Token Validation in Java Applications :https://sgonzal.com/2020/04/06/jwt-validation.html#:~:text=Set%20up%20the%20clients%20that%20call%20the%20web,tokens%20issued%20by%20AAD%20in%20a%20Java%20application.

How can I validate an Azure AD JWT Token in Java?:https://stackoverflow.com/questions/60884823/how-can-i-validate-an-azure-ad-jwt-token-in-java

當在複雜的環境中面臨問題,格物之道需:濁而靜之徐清,安以動之徐生。 雲中,恰是如此!