問題描述
使用中國區的Azure,在擷取Token時候,參考了 adal4j的代碼,在官方文檔中,發現了如下的片段代碼:
ExecutorService service = Executors.newFixedThreadPool(1);
AuthenticationContext context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken(
"https://graph.windows.net", YOUR_TENANT_ID, username, password,
null);
AuthenticationResult result = future.get();
System.out.println("Access Token - " + result.getAccessToken());
System.out.println("Refresh Token - " + result.getRefreshToken());
System.out.println("ID Token - " + result.getIdToken());
以上代碼中,有一些參數很不明确:
1)AUTHORITY, 是什麼意思呢?
2)acquireTokne方法中的 https://graph.windows.net 是指向global azure的資源,如果是中國區azure的資源,那麼resource url是多少呢?
3)YOUR_TENANT_ID,它的值是什麼呢?
問題解答
第一個問題:AUTHORITY, 是什麼意思,它的值是什麼呢?
AUTHORITY,表示認證的主體,它是一個URL,表示可以從該主體中擷取到認證Token。 它的格式為:https://<authority host>/<tenant id> ,是以在使用Azure的過程中,根據Azure環境的不同,Host 有以下四個值。
- AzureChina :The host of the Azure Active Directory authority for tenants in the Azure China Cloud. AZURE_CHINA = "login.chinacloudapi.cn"
- AzureGermany: The host of the Azure Active Directory authority for tenants in the Azure German Cloud. AZURE_GERMANY = "login.microsoftonline.de"
- AzureGovernment: The host of the Azure Active Directory authority for tenants in the Azure US Government Cloud. AZURE_GOVERNMENT = "login.microsoftonline.us"
- AzurePublicCloud: The host of the Azure Active Directory authority for tenants in the Azure Public Cloud. AZURE_PUBLIC_CLOUD = "login.microsoftonline.com"
是以,這裡我們需要使用的值為:String AUTHORITY = "https://login.chinacloudapi.cn/<tenant id >";
那麼如何來擷取Tenant ID呢?
登入到Azure門戶 --> 進入AAD中,在Overview頁面檢視Tenant ID (https://portal.azure.cn/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Overview)

第二個問題:acquireTokne方法中的 https://graph.windows.net 是指向global azure的資源,如果是中國區azure的資源,那麼resource url是多少呢?
根據中國區Azure的開發文檔,并沒有查找到對應于 graph.windows.net的中國區Graph 終結點。但是,中國區Graph 的終結點為:microsoftgraph.chinacloudapi.cn,是以,以上示例中應該使用的值應是:
https://microsoftgraph.chinacloudapi.cn/
(Source: https://docs.microsoft.com/en-us/azure/china/resources-developer-guide#check-endpoints-in-azure)
第三個問題:YOUR_TENANT_ID,它的值是什麼呢?
在對比了adal4j的源代碼後,在acquireToken方法定義中,發現YOUR_TENANT_ID所對應的值應該是 clientId ()。是以,官網參考文檔中的YOUR_TENANT_ID存在誤導情景。需要修改為YOUR_CLIENT_ID。
ADAL4J中acquireToken源碼(acquireToken有多個重載,但此處隻列舉出代碼中使用的這個重載)
/**
* Acquires a security token from the authority using a Refresh Token
* previously received.
*
* @param clientId
* Name or ID of the client requesting the token.
* @param resource
* Identifier of the target resource that is the recipient of the
* requested token. If null, token is requested for the same
* resource refresh token was originally issued for. If passed,
* resource should match the original resource used to acquire
* refresh token unless token service supports refresh token for
* multiple resources.
* @param username
* Username of the managed or federated user.
* @param password
* Password of the managed or federated user.
* @param callback
* optional callback object for non-blocking execution.
* @return A {@link Future} object representing the
* {@link AuthenticationResult} of the call. It contains Access
* Token, Refresh Token and the Access Token's expiration time.
*/
public Future<AuthenticationResult> acquireToken(final String resource,
final String clientId, final String username,
final String password, final AuthenticationCallback callback) {
if (StringHelper.isBlank(resource)) {
throw new IllegalArgumentException("resource is null or empty");
}
if (StringHelper.isBlank(clientId)) {
throw new IllegalArgumentException("clientId is null or empty");
}
if (StringHelper.isBlank(username)) {
throw new IllegalArgumentException("username is null or empty");
}
if (StringHelper.isBlank(password)) {
throw new IllegalArgumentException("password is null or empty");
}
return this.acquireToken(new AdalAuthorizatonGrant(
new ResourceOwnerPasswordCredentialsGrant(username, new Secret(
password)), resource), new ClientAuthenticationPost(
ClientAuthenticationMethod.NONE, new ClientID(clientId)),
callback);
}
是以,這裡指定的Client ID 其實是,AAD中所注冊的一個應用(服務主體),而這個主體可以根據需求授予不同的權限,acquireToken就是根據使用者驗證成功後,生成這個主題所擁有的權限JWT令牌(Token),擷取到Token後,就擁有了通路Azure中資源API的授權.
如何來擷取這個Client ID呢?
- 進入AAD, 選擇注冊應用( App Registrations:https://portal.azure.cn/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps)
- 并在Onwed Applications 中選擇,進入詳細頁面或就是目前頁面,擷取Application(Client) ID
特别注意:這個App必須開啟 “Allow public client flows“ 才能成功擷取到 Token。 預設情況下,這裡選擇的是No。 如果不開啟這一步,将會收到錯誤消息:"error_description":"AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'.
開啟方式為:點選這個App的名稱,進入詳細頁面,選擇Authentication,滑動到最底部,選擇“Allow public client flows”。
完成參考執行個體代碼
1:在POM.XML檔案中添加adal4j依賴
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>adal4j</artifactId>
<version>1.2.0</version>
</dependency>
2:示例代碼
package com.example;
import java.net.MalformedURLException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationResult;
/**
* Hello world!
*
*/
public class App {
public static void main(String[] args) throws InterruptedException, ExecutionException, MalformedURLException {
System.out.println("Hello World!");
ExecutorService service = Executors.newFixedThreadPool(1);
String AUTHORITY = "https://login.chinacloudapi.cn/<tenant id >"; // AzureAuthority
String YOUR_Client_ID="7b61c392-xxxx-xxxx-xxxx-xxxxxxxxxxx";
String username = "[email protected]";
String password = "xxxxxxxxxxx";
AuthenticationContext context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken("https://microsoftgraph.chinacloudapi.cn/", YOUR_Client_ID,
username, password, null);
AuthenticationResult result = future.get();
System.out.println("Access Token - " + result.getAccessToken());
System.out.println("Refresh Token - " + result.getRefreshToken());
System.out.println("ID Token - " + result.getIdToken());
}
}
(PS: 使用的 username, password就是登入Azure的使用者名和密碼)
測試結果:
擷取Token成功。
可以通過一個公用網站 jwt.io 來解析Token: https://jwt.io/, 它可以解析出Token内容,讓我們可讀。
參考資料
Azure China developer guide:https://docs.microsoft.com/en-us/azure/china/resources-developer-guide#check-endpoints-in-azure
Authority: https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-client-application-configuration#authority
Authority Value: https://azuresdkdocs.blob.core.windows.net/$web/python/azure-identity/1.4.0/_modules/azure/identity/_constants.html#AzureAuthorityHosts
Azure Active Directory libraries for Java: https://docs.microsoft.com/en-us/java/api/overview/azure/activedirectory?view=azure-java-stable#client-library
當在複雜的環境中面臨問題,格物之道需:濁而靜之徐清,安以動之徐生。 雲中,恰是如此!