如何验证 Apple 登录的 Jwt 令牌(后端验证)。如何从 Java 中的模数和指数 (n,e) 生成 RSA 公钥
Posted
技术标签:
【中文标题】如何验证 Apple 登录的 Jwt 令牌(后端验证)。如何从 Java 中的模数和指数 (n,e) 生成 RSA 公钥【英文标题】:How To validate Jwt Token for Apple Login (Backend validation). How to generate RSA Public key from modulus and exponent (n,e) in Java 【发布时间】:2020-05-24 02:24:32 【问题描述】:我正在寻找一种验证苹果登录令牌的方法。 验证必须在后端完成,所以我确信我可以安全地添加一个新帐户。 另一个问题是我需要将 xml 格式的密钥 https://appleid.apple.com/auth/keys 转换为公钥 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-----");
【问题讨论】:
【参考方案1】:与 Jose4 lib 相同的解决方案,
此 HttpsJwksVerificationKeyResolver 将根据列表中的密钥 ID 选择公钥。所以我们不必处理它。
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
将抛出适当的异常,只需捕获并采取相应的行动。
希望这可以保持简单,并使其他开发人员更具可读性。
【讨论】:
这绝对是最好的答案。我能够扔掉大量不工作的代码,并用这个简单、优雅的解决方案替换它。非常感谢!【参考方案2】:这是验证苹果登录令牌的一种可能解决方案。
该实现使用发布于 --> https://appleid.apple.com/auth/keys
的 Apple 公钥密钥从 XML 格式 (https://appleid.apple.com/auth/keys) 转换为 PEM 格式,然后验证令牌。
部分代码可用于将字符串格式的模数和指数转换为PEM格式的RSA公钥
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);
检索苹果键:
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;
【讨论】:
以上是关于如何验证 Apple 登录的 Jwt 令牌(后端验证)。如何从 Java 中的模数和指数 (n,e) 生成 RSA 公钥的主要内容,如果未能解决你的问题,请参考以下文章
OpenID Connect JWT 令牌验证和后端 api 的使用策略 - jwks 还是会话?