Как проверить токен Jwt для входа в Apple (проверка бэкенда). Как сгенерировать ключ RSA Publi c по модулю и показателю степени (n, e) в Java - PullRequest
1 голос
/ 08 февраля 2020

Я ищу способ проверки токена Apple.
Проверка должна быть выполнена на стороне сервера, поэтому я уверен, что могу безопасно добавить новую учетную запись.

Другая проблема заключается в том, что мне нужно преобразовать ключ https://appleid.apple.com/auth/keys в формате xml в формате Publi c Key pem.
Я нашел возможное решение, которое я опубликую ниже. Код реализован в Java

public static void main(String...args) throws Exception {
        String jwtAppleToken = ""; //copy here the token from apple

        //copied from https://appleid.apple.com/auth/keys
        final String base64UrlEncodedModulus = "lxrwmuYSAsTfn-lUu4goZSXBD9ackM9OJuwUVQHmbZo6GW4Fu_auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY-RNwCWdjNfEaY_esUPY3OVMrNDI15Ns13xspWS3q-13kdGv9jHI28P87RvMpjz_JCpQ5IM44oSyRnYtVJO-320SB8E2Bw92pmrenbp67KRUzTEVfGU4-obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd_JhmqX5CAaT9Pgi0J8lU_pcl215oANqjy7Ob-VMhug9eGyxAWVfu_1u6QJKePlE-w"; 
        final String base64UrlEncodedExp = "AQAB";

        String publicKey = getPemPublicKeyFromBase64UrlEncodedXMLRSAKey(base64UrlEncodedModulus, base64UrlEncodedExp);

        System.out.println(verify(jwtAppleToken, publicKey));

        System.out.println("-----BEGIN PUBLIC KEY-----");
        System.out.println(publicKey);
        System.out.println("-----END PUBLIC KEY-----");

    }

Ответы [ 2 ]

1 голос
/ 08 февраля 2020

Это возможное решение для проверки токена Apple.

В реализации используется ключ Apple publi c, опубликованный на -> https://appleid.apple.com/auth/keys

Ключи преобразуются в формат PEM из формата XML (https://appleid.apple.com/auth/keys) и чем токен проверен.

Часть кода может использоваться для преобразования модуля и экспоненты в строковом формате в ключ RSA Publi c в формате PEM

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;

public class VerifyAppleToken {

    public static void main(String...args) throws Exception {
        String jwtAppleToken = ""; //copy here the token from apple
        System.out.println("THE TOKEN IS VERIFIED FOR ONE OF APPLE KEYS:"+verify(jwtAppleToken));

        //copied from https://appleid.apple.com/auth/keys
        final String base64UrlEncodedModulus = "lxrwmuYSAsTfn-lUu4goZSXBD9ackM9OJuwUVQHmbZo6GW4Fu_auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY-RNwCWdjNfEaY_esUPY3OVMrNDI15Ns13xspWS3q-13kdGv9jHI28P87RvMpjz_JCpQ5IM44oSyRnYtVJO-320SB8E2Bw92pmrenbp67KRUzTEVfGU4-obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd_JhmqX5CAaT9Pgi0J8lU_pcl215oANqjy7Ob-VMhug9eGyxAWVfu_1u6QJKePlE-w"; 
        final String base64UrlEncodedExp = "AQAB";

        String publicKey = getPemPublicKeyFromBase64UrlEncodedXMLRSAKey(base64UrlEncodedModulus, base64UrlEncodedExp);

        System.out.println(verify(jwtAppleToken, publicKey));

       //copied from and converted to base64 from base64UrlEncoded https://appleid.apple.com/auth/keys on 
       // 07/02/2020
        final String base64EncodedModulus = "lxrwmuYSAsTfn+lUu4goZSXBD9ackM9OJuwUVQHmbZo6GW4Fu/auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY+RNwCWdjNfEaY/esUPY3OVMrNDI15Ns13xspWS3q+13kdGv9jHI28P87RvMpjz/JCpQ5IM44oSyRnYtVJO+320SB8E2Bw92pmrenbp67KRUzTEVfGU4+obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd/JhmqX5CAaT9Pgi0J8lU/pcl215oANqjy7Ob+VMhug9eGyxAWVfu/1u6QJKePlE+w=="; 

        final String base64EncodedExp = "AQAB";

        System.out.println("-----BEGIN PUBLIC KEY-----");
        System.out.println(getPemPublicKeyFromBase64XMLRSAKey(base64EncodedModulus, base64EncodedExp));
        System.out.println("-----END PUBLIC KEY-----");

    }

private static boolean verify(String jwtAppleToken) throws NoSuchAlgorithmException, InvalidKeySpecException {
        AppleKeysRetrieverService retriver = new AppleKeysRetrieverService();       
        AppleKeysResponse res = retriver.sendRetriveRequest("https://appleid.apple.com/auth/keys");

        List<AppleKeyDTO> appleKeys = res.getKeys();

        for (AppleKeyDTO appleKeyDTO : appleKeys) {
            final String base64UrlEncodedModulus  = appleKeyDTO.getN();
            final String base64UrlEncodedExp = appleKeyDTO.getE();
            String publicKey1 = getPemPublicKeyFromBase64UrlEncodedXMLRSAKey(base64UrlEncodedModulus, base64UrlEncodedExp);

            if(verify(jwtAppleToken, publicKey1)) {
                return true;
            }
        }

        return false;
    }

     public static boolean verify(String jwtToken, String publicKey) {
            try {
                JwtHelper.decodeAndVerify(jwtToken, new RsaVerifier(getRSAPublicKey(publicKey)));
            } catch (Exception e) {
                return false;
            }
            return true;
        }


     private static RSAPublicKey getRSAPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
            KeyFactory keyFactory = java.security.KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(java.util.Base64.getDecoder().decode(publicKey));
            return (RSAPublicKey) keyFactory.generatePublic(keySpec);
     }


    private static String getPemPublicKeyFromBase64UrlEncodedXMLRSAKey(String urlBase64Modulus, String urlBase64Exp) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] e = Base64.getUrlDecoder().decode(urlBase64Exp);
        byte[] n = Base64.getUrlDecoder().decode(urlBase64Modulus);

        BigInteger exponent = new BigInteger(1, e);
        BigInteger modulus = new BigInteger(1, n);

        return getPemPublicKey(modulus, exponent);
    }

    private static String getPemPublicKeyFromBase64XMLRSAKey(String base64Modulus, String base64Exp) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] e = Base64.getDecoder().decode(base64Exp);
        byte[] n = Base64.getDecoder().decode(base64Modulus);

        BigInteger exponent = new BigInteger(1, e);
        BigInteger modulus = (new BigInteger(1, n));

        return getPemPublicKey(modulus, exponent);
    }

    private static String getPemPublicKey(BigInteger modulus, BigInteger exponent) throws NoSuchAlgorithmException, InvalidKeySpecException {
        RSAPublicKeySpec publicKeySpec = new java.security.spec.RSAPublicKeySpec(modulus, exponent);

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey myPublicKey = keyFactory.generatePublic(publicKeySpec);

        byte[] park = Base64.getEncoder().encode(myPublicKey.getEncoded());


        return new String(park);
    }

}

Ключи от Apple:


public class AppleKeysRetrieverService {

    public AppleKeysResponse sendRetriveRequest(String retriveAppleKeysUrl) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters()
            .add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));

        String appleKeysResponse = restTemplate
                .getForObject(retriveAppleKeysUrl, String.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(
                DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        AppleKeysResponse res = null;

        try {
            res = objectMapper.readValue(appleKeysResponse, AppleKeysResponse.class);

            return res;
        }catch(Exception e) {

            return null;
        }
    }
}



public class AppleKeyDTO {

    public String kty;
    public String kid;
    public String sig;
    public String alg;
    public String n;
    public String e;

    public String getKty() {
        return kty;
    }
    public void setKty(String kty) {
        this.kty = kty;
    }

    public String getKid() {
        return kid;
    }
    public void setKid(String kid) {
        this.kid = kid;
    }

    public String getSig() {
        return sig;
    }
    public void setSig(String sig) {
        this.sig = sig;
    }

    public String getAlg() {
        return alg;
    }
    public void setAlg(String alg) {
        this.alg = alg;
    }

    public String getN() {
        return n;
    }
    public void setN(String n) {
        this.n = n;
    }

    public String getE() {
        return e;
    }
    public void setE(String e) {
        this.e = e;
    }

}




public class AppleKeysResponse {

    private List<AppleKeyDTO> keys;

    public List<AppleKeyDTO> getKeys() {
        return keys;
    }

    public void setKeys(List<AppleKeyDTO> keys) {
        this.keys = keys;
    }


}









0 голосов
/ 17 марта 2020

То же решение с Jose4 lib,

Этот HttpsJwksVerificationKeyResolver выберет ключ publi c на основе идентификатора ключа из списка. таким образом, нам не нужно иметь с этим дело.

import org.jose4j.jwk.HttpsJwks;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver;


 HttpsJwks httpsJkws = new HttpsJwks("https://appleid.apple.com/auth/keys");

 HttpsJwksVerificationKeyResolver httpsJwksKeyResolver = new HttpsJwksVerificationKeyResolver(httpsJkws);

 JwtConsumer jwtConsumer = new JwtConsumerBuilder()
          .setVerificationKeyResolver(httpsJwksKeyResolver)
          .setExpectedIssuer("https://appleid.apple.com")
          .setExpectedAudience(<clientId>)
          .build();

 JwtClaims jwtClaims = jwtConsumer.processToClaims(<idToken>);

processToClaims будет выдавать соответствующие исключения, просто ловить и действовать соответствующим образом.

Надеюсь, что это остается простым и делает более читабельным для других Разработчики.

...