使用苹果登录时出现 invalid_client 错误。

5
我将为您翻译以下内容:

我想要实现的目标:

迄今为止我的进展:

进行苹果验证调用:

        restTemplate = new RestTemplate();

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("client_id", clientId); // app_id like com.app.id
        String token = generateJWT();   // generated jwt
        map.add("client_secret", token); 
        map.add("grant_type", "authorization_code");
        map.add("code", authorizationCode);  // JWT code we got from iOS
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

        final String appleAuthURL = "https://appleid.apple.com/auth/token";
        String response = restTemplate.postForObject(appleAuthURL, request, String.class);

令牌生成:

        final PrivateKey privateKey = getPrivateKey();
        final int expiration = 1000 * 60 * 5;

        String token = Jwts.builder()
                .setHeaderParam(JwsHeader.KEY_ID, keyId) // key id I got from Apple 
                .setIssuer(teamId)  
                .setAudience("https://appleid.apple.com")
                .setSubject(clientId) // app id com.app.id
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .signWith(SignatureAlgorithm.ES256, privateKey) // ECDSA using P-256 and SHA-256
                .compact();

        return token;

从文件中获取我的私钥:

        final Reader pemReader = new StringReader(getKeyData());
        final PEMParser pemParser = new PEMParser(pemReader);
        final JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        final PrivateKeyInfo object = (PrivateKeyInfo) pemParser.readObject();
        final PrivateKey pKey = converter.getPrivateKey(object);

我确认我的JWT包含所有必需的字段:

{
  "kid": "SAME KEY AS MY KEY ID",
  "alg": "ES256"
}

{
  "iss": "Blahblah",
  "aud": "https://appleid.apple.com",
  "sub": "com.app.id",
  "exp": 1578513833,
  "iat": 1578513533
}
2个回答

5
这行文字引起了我的注意:
map.add("code", authorizationCode);  // JWT code we got from iOS

authorizationCode 不是一个 jwt

JSON Web Tokens 由3个部分组成,用点号分隔

但是 authorizationCode 有4个部分,像这样:

text1.text2.0.text3

您可能正在使用 iOS 应用程序中的 identityToken,而不是 authorizationCode

以下是检索它的方法:

let authorizationCode = String(data: appleIDCredential.authorizationCode!, encoding: .utf8)!
print("authorizationCode: \(authorizationCode)")

对于那些遇到相同的invalid_client错误的人,以下内容也会很有帮助:

  1. kid是从developer.apple.com/account/resources/authkeys/list中获取私钥ID

  2. keyFile是从developer.apple.com下载的包含私钥的文件

  3. teamID可以在登录developer.apple.com并点击“账户”后,在右上角找到

  4. aud中的值应为https://appleid.apple.com

  5. app_id是应用程序的Bundle Identifier

如果需要,以下是使用Python创建客户端密钥的工作解决方案:

# $ pip install pyjwt
import jwt
import time

kid = "myKeyId"  
keyFile = "/pathToFile/AuthKey.p8"
key = ""
with open(keyFile, 'r') as myFile:
    key = myFile.read()

print(key)

timeNow = int(round(time.time()))
time3Months = timeNow + 86400*90

claims = {
    'iss': teamID,
    'iat': timeNow,
    'exp': time3Months,
    'aud': 'https://appleid.apple.com',
    'sub': app_id,
}


secret = jwt.encode(claims, key, algorithm='ES256', headers={'kid': kid})
print("secret:")
print(secret)
client_secret = secret.decode("utf-8")
print(client_secret)

同时确保为 https://appleid.apple.com/auth/token 的调用正确设置 Content-Length 头信息。 - Vall0n

0
在使用 Apple ID 登录时,将 clientSecret 和 appleToken 保存到本地数据库中。
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
print("didCompleteWithAuthorization : -\(authorization)")
switch authorization.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
    // Create an account in your system.
    let userIdentifier = appleIDCredential.user
    let fullName = appleIDCredential.fullName?.givenName
    let email = appleIDCredential.email
    
    guard let appleIDToken = appleIDCredential.identityToken else {
        print("Unable to fetch identity token")
        return
    }
    guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
        return
    }
    StorageServices.storeInDefaults(object: idTokenString, key: "appleToken")
    // Add new code below
    if let authorizationCode = appleIDCredential.authorizationCode,
       let codeString = String(data: authorizationCode, encoding: .utf8) {
        StorageServices.storeInDefaults(object: codeString, key: "clientSecret")
    }
   
default:
    break
}

调用苹果令牌撤销API。

func callRevokeTokenAPI() {
    guard let clientSecret =  StorageServices.readFromDefaults(key: "clientSecret") as? String else {return}
    guard let appleToken = StorageServices.readFromDefaults(key: "appleToken") as? String  else {return}
    let parameters = "client_id=com.oxstren.Actofit-Wear&client_secret=\(clientSecret)&token=\(appleToken)&token_type_hint=access_token"
    print(parameters)
    let postData =  parameters.data(using: .utf8)
    var request = URLRequest(url: URL(string: "https://appleid.apple.com/auth/revoke")!,timeoutInterval: Double.infinity)
    request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "POST"
    request.httpBody = postData
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let response = response as? HTTPURLResponse, error == nil else {
            print("error", error ?? URLError(.badServerResponse))
            return
        }
        print(response)
      guard let data = data else {
        print(String(describing: error))
        return
      }
      print(String(data: data, encoding: .utf8)!)
    }
    task.resume()
} //end function body.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接