我正在遵循本指南以了解客户端凭证流程和本指南以了解所需的 JWT 令牌。最终目标是通过 Microsoft Graph API 访问日历信息。对 Graph API 的调用需要使用从 Microsoft 令牌端点获取的访问令牌进行身份验证。
但是调用令牌端点本身需要某种形式的身份验证,可以通过共享密钥或证书签名的 JWT 令牌进行。共享秘密方法有据可查,似乎工作正常,除了我试图调用的 Graph 服务拒绝访问令牌不够安全 - 似乎需要证书签名的 JWT 令牌。我无法找到第二种方法的任何 Java 示例,并且到目前为止我按照上述指南实施的内容不起作用。
使用 JJWT,生成令牌的代码如下所示:
PrivateKey key = loadPrivateKey();
String jwt = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "RS256")
.setHeaderParam("x5t", "A7...89")
.setSubject(clientId)
.setExpiration(new Date(System.currentTimeMillis() + 200000))
.setIssuer(clientId)
.setNotBefore(new Date())
.setAudience("https://login.microsoftonline.com/" + tenantId + "/oauth2/token")
.setId(UUID.randomUUID().toString())
.signWith(
SignatureAlgorithm.RS256,
key)
.compact();
loadPrivateKey() 使用 BouncyCastle 类:
KeyFactory factory = KeyFactory.getInstance("RSA");
PemObject pemObject;
PemReader pemReader = new PemReader(new StringReader(pemFileContent));
try {
pemObject = pemReader.readPemObject();
} finally {
pemReader.close();
}
byte[] content = pemObject.getContent();
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
return factory.generatePrivate(privKeySpec);
我在 apps.dev.microsoft.com 控制台中创建了一个密钥对,下载了私钥并使用 openssl 将其转换为 PEM 格式,然后将 PEM 内容粘贴到上面使用的 pemFileContent 变量中。openssl 为 PEM 版本计算与 MS 应用程序控制台相同的指纹。
我在调用 Retrofit 服务时使用 JWT 令牌:
default Call<APIToken> getAccessToken(String tenantId, String clientId, String clientAssertion)
{
return this.getAccessToken(
tenantId,
clientId,
clientAssertion,
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"https://graph.microsoft.com/.default",
"client_credentials");
}
@FormUrlEncoded
@Headers({
"Host: login.microsoftonline.com",
"Content-Type: application/x-www-form-urlencoded"
})
@POST("/{tenant_id}/oauth2/token")
Call<APIToken> getAccessToken(
@Path("tenant_id") String tenantId,
@Field("client_id") String clientId,
@Field("client_assertion") String clientAssertion,
@Field("client_assertion_type") String clientAssertionType,
@Field("scope") String scope,
@Field("grant_type") String grantType);
调试输出中的一切看起来都不错:
header={typ=JWT, alg=RS256, x5t=A7...89},body={sub=b0a0fd13-1e86-4ef3-a003-c53eaf21daa4, exp=1517656715, iss=b0a0fd13-1e86-4ef3-a003-c53eaf21daa4, nbf=1517656515, aud=https://login.microsoftonline.com/87ad3067-2703-49ea-8cd2-a094fc3ee413/oauth2/token, jti=4fa5db4f-76b7-4122-9c5a-092ac74fd0fa},signature=....342 characters....
但是响应是 401 Unauthorized,错误报告:
{"error":"invalid_client","error_description":"AADSTS70002: Error validating credentials. AADSTS50012: Client assertion contains an invalid signature. [Reason - The key was not found., Thumbprint of key used by client: '03B....3D', Configured keys: [Key0:Start=02/03/2018, End=12/31/2099, Thumbprint=lQ...M4;]]\r\nTrace ID: b4775394-51cc-4a4a-b927-50bd05421100\r\nCorrelation ID: f9264339-9b85-4942-9404-af941aa0331c\r\nTimestamp: 2018-02-03 11:15:18Z","error_codes":[70002,50012],"timestamp":"2018-02-03 11:15:18Z","trace_id":"b4775394-51cc-4a4a-b927-50bd05421100","correlation_id":"f9264339-9b85-4942-9404-af941aa0331c"}
我不知道在哪里寻找问题。错误中提到的指纹值均不对应于私钥的指纹。其他人在使用错误的签名算法时报告了此错误消息,但 RS256 是要使用的记录算法。我正在寻找一个指向我出错的指针,或者一个使用签名的 JWT 令牌的客户端凭据流的 Java 中的工作示例(我对使用哪些库并不挑剔)(我找到了使用的示例一个共享的秘密,它们工作正常,但 MS Graph API 拒绝该令牌,因为它不够安全)。