错误:无效的客户端 - 使用 Spring Boot 使用 Apple 登录

Posted

技术标签:

【中文标题】错误:无效的客户端 - 使用 Spring Boot 使用 Apple 登录【英文标题】:Error: Invalid Client - Sign In With Apple with Spring Boot 【发布时间】:2020-07-27 13:22:50 【问题描述】:

我在尝试用我的授权代码交换刷新令牌时收到 400 : ["error":"invalid_client"] 错误。我尝试了所有方法并仔细检查了很多次。 我正在使用this 库来生成客户端密码。下面我使用示例密钥来演示该问题。 有什么问题?

Apple 客户端密钥生成器

public class AppleClientSecretGenerator 

    private String team_id;
    private String sub;
    private String key_id;
    private long expiration_time;
    private String secret_key;
    private String aud;


    public AppleClientSecretGenerator() 
        super();
        this.team_id = "***********";
        this.sub = "com.example.app";
        this.key_id = "**********";
        this.expiration_time = 200000;
        this.secret_key = "MIGTAFKMN23SFF2SFGSM49AgEGCCqGSM49AwEHBHdfDSFFDe09hGVEu5sesMNNF" +
                "pet8GJDZIL0inL4oxgIJNF0i3Q8MYKOgsdCgYIKoZIzj0DAQehRANCAAQhAVyKVrFGWEw+" +
                "gkWyeQNxopjG30iF56DXM0QfqwbffKmsdPkjfe3FKDDFyDYmk+XZM4qj6aIZKLy" +
                "KLM4Nd23";
        this.aud = "https://appleid.apple.com";
    

    public String generate() 
        String jws = "";
        try 
            byte[] keyBytes = Base64.getDecoder().decode(secret_key);
            //byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            PrivateKey key = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.ES256;
            long nowSeconds = System.currentTimeMillis();
            Date now = new Date(nowSeconds);
            long expMillis = nowSeconds + expiration_time;
            Date exp = new Date(expMillis);

            jws = Jwts.builder()
                    .setHeaderParam("kid", key_id)
                    .setIssuedAt(now)
                    .setSubject(sub)
                    .setIssuer(team_id)
                    .setAudience(aud)
                    .setExpiration(exp)
                    .signWith(key, signatureAlgorithm)
                    .compact();
         catch (JwtException e) 
            //don't trust the JWT!
            System.out.println("Error: " + e.getMessage());
         catch (NoSuchAlgorithmException e) 
            // TODO Auto-generated catch block
            System.out.println("Error: " + e.getMessage());
            e.printStackTrace();
         catch (InvalidKeySpecException e) 
            // TODO Auto-generated catch block
            System.out.println("Error: " + e.getMessage());
            e.printStackTrace();
        
        System.out.println("Client Secret: " + jws);
        return jws;

    

授权请求

public class AuthorizationRequest 

    private String client_id;
    private String client_secret;
    private String code;
    private String grant_type;
    private String redirect_uri;


    public AuthorizationRequest(String client_secret, String code) 
        super();
        this.client_id = "com.maxtrauboth.BopdropSwiftUI";
        this.client_secret = client_secret;
        this.code = code;
        this.grant_type = "authorization_code";
        this.redirect_uri = "http://mylocaladdr.test/";
    

// Getters and Setters here

验证用户

@PostMapping("verify")
public UserCredentials verify(@RequestBody UserCredentials credentials) 

        Jwk jwk;
        Algorithm algorithm;

        // Validate identity token
        DecodedJWT jwt = JWT.decode(credentials.getToken());
        JwkProvider provider = new UrlJwkProvider("https://appleid.apple.com/auth/keys");

        try 
            jwk = provider.get(jwt.getKeyId());
            algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
            algorithm.verify(jwt);

            // Check expiration
            if (jwt.getExpiresAt().before(Calendar.getInstance().getTime())) 
              throw new RuntimeException("Expired token!");
            

            // Create client_secret
            AppleClientSecretGenerator generator = new AppleClientSecretGenerator();
            String client_secret = generator.generate();

            // Refreshing the token by sending the authorization code
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            headers.setAccept(Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED));
            headers.set("User-Agent", "Mozilla/5.0 Firefox/26.0");

            System.out.println("Authorization Code: " + credentials.getAuthorization_code());

            AuthorizationRequest request = new AuthorizationRequest(client_secret, credentials.getAuthorization_code());

            HttpEntity<String> entity = new HttpEntity<String>(request.toString(), headers);

            RestTemplate restTemplate = new RestTemplate();

            // send POST request
            TokenResponse response = restTemplate.postForObject("https://appleid.apple.com/auth/token", entity, TokenResponse.class);

            // do some database work here

         catch (JwkException e) 
            // error
         catch (IllegalArgumentException e) 
            // error
        

        // sending the token back to the user 
        return credentials;

生成的客户端密码


  "kid": "*********",
  "alg": "ES256"


  "iss": "*********",
  "iat": 1587930164,
  "exp": 1587930364,
  "aud": "https://appleid.apple.com",
  "sub": "com.example.app"

【问题讨论】:

【参考方案1】:

发布时间和到期时间应该以秒为单位,而不是毫秒。

【讨论】:

好提示!但是,我仍然收到此错误。我更新了上面的代码。 您介意显示您的客户密码字符串吗?尝试将其粘贴到 jwt.io 中,并查看解码后的客户端密码是否正确 哇,将它粘贴到 jwt.io 后,iatexp 具有相同的值 1587928。这怎么可能? 嗯,我认为现在不需要除以 1000,尝试添加 *1000 作为 exp 时间,即 exp = now + 5*1000 参考developer.okta.com/blog/2018/10/31/jwts-with-java【参考方案2】:

我没有找到问题的原因。但是,我按照here 中提到的确切步骤解决了这个问题。

【讨论】:

以上是关于错误:无效的客户端 - 使用 Spring Boot 使用 Apple 登录的主要内容,如果未能解决你的问题,请参考以下文章

spring-boo hello world程序

Spring Security CAS:在 login.jsp 上显示客户端错误

使用 Spring Boot 的 starter web parent 时出现无效的打包错误

spring security reactive - 如何调试“无效凭据”错误?

Spring Boot-无效的访问令牌错误

Spring/GitHub/Docker - 错误:jarfile 无效或损坏