Ошибка: недействительный клиент - войдите в Apple с помощью Spring Boot

Я получаю сообщение об ошибке 400 : [{"error":"invalid_client"}] при попытке обменять код авторизации на токен обновления. Я все перепробовал и перепроверил много раз. Я использую эту библиотеку для создания секрета клиента. Ниже я использовал образец ключа для демонстрации проблемы. Что могло быть не так?

Генератор секретов клиентов 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"
}

person Maximilian Trauboth    schedule 14.04.2020    source источник


Ответы (2)


Время выпуска и истечения срока должно быть в секундах, а не в миллисекундах.

person Axel    schedule 17.04.2020
comment
Хороший намек! Однако я все еще получаю эту ошибку. Я обновил код выше. - person Maximilian Trauboth; 26.04.2020
comment
не могли бы вы показать секретную строку вашего клиента? попробуйте вставить его в jwt.io и посмотрите, верен ли декодированный секрет клиента - person Axel; 26.04.2020
comment
Вау, после вставки в jwt.io оказалось, что iat и exp имеют одинаковое значение 1587928. Как это может быть? - person Maximilian Trauboth; 26.04.2020
comment
Хм, я думаю, что сейчас нет необходимости делить на 1000, и попробуйте добавить * 1000 для времени exp, то есть exp = now + 5 * 1000 - person Axel; 26.04.2020
comment
comment
Но это было то, что у меня было раньше. Разве значение iat не должно быть намного больше, чем 1587928? Это число кажется довольно маленьким в течение нескольких секунд после Эпохи, в UTC ... - person Maximilian Trauboth; 26.04.2020
comment
О, да, у него должно быть еще три цифры, я сам не использую Java, поэтому я не уверен, как процесс генерации даты и jwt в java - person Axel; 26.04.2020
comment
Хорошо, теперь, после того как снова вернули старую версию, секунды iat и exp выглядят хорошо. Однако я все равно получаю сообщение об ошибке. Я обновил приведенный выше пример, чтобы показать сгенерированный секрет клиента. - person Maximilian Trauboth; 26.04.2020
comment
Я думаю, что 4 секунды может быть слишком коротким, попробуйте добавить 200 секунд (5 минут) для опыта - person Axel; 26.04.2020
comment
Нет, по-прежнему ошибка: / Кстати, на jwt.io написано invalid signature. Значит, с подписью что-то не так. Сейчас проверяю .. - person Maximilian Trauboth; 26.04.2020
comment
У вас есть файл закрытого ключа .p8? Вы можете сгенерировать для него открытый ключ с помощью этой команды в терминале: openssl ec -in AuthKey_123ABC456.p8 -pubout -out AuthKey_123ABC456_public.p8, затем вы можете открыть файл открытого ключа, скопировать и вставить его содержимое в отладчик jwt для проверки подписи - person Axel; 26.04.2020
comment
Я создал открытый ключ и вставил его вместе с закрытым ключом в отладчике, но ничего не изменилось. Проверяет ли подпись автоматически при предоставлении двух ключей? - person Maximilian Trauboth; 26.04.2020
comment
Вам нужно только вставить открытый ключ в отладчик, и он проверит подпись. Я не уверен, верен ли ваш формат закрытого ключа в коде, поскольку исходный файл (.p8) имеет символ новой строки (\ n) после каждых 64 символов. - person Axel; 26.04.2020
comment
Хорошо, я вставил открытый ключ с разрывами строки и без разрыва строки и -----BEGIN PUBLIC KEY----- и -----END PUBLIC KEY-----. Тем не менее он говорит о недействительной подписи. Мой файл закрытого ключа - это именно то, как я получил его от Apple. Я его не менял. - person Maximilian Trauboth; 26.04.2020
comment
Наверное, что-то не так с генерацией подписи, я думаю, извините, здесь не могу помочь, так как я плохо разбираюсь в Java - person Axel; 26.04.2020
comment
Возможно, требуется разрыв строки для строки закрытого ключа внутри кода? - person Maximilian Trauboth; 26.04.2020
comment
может быть, вы можете попробовать добавить разрыв строки после каждых 64 символов для вашей строки закрытого ключа - person Axel; 26.04.2020

Я не нашел причины проблемы. Однако я решил проблему, выполнив точные шаги, указанные здесь.

person Maximilian Trauboth    schedule 02.05.2020