java版本:
POM文件:
4.0.0
org.example
AppleLogin
1.0-SNAPSHOT
nexus-aliyun
Nexus aliyun
http://maven.aliyun.com/nexus/content/groups/public
com.alibaba
fastjson
1.2.51
org.jodd
jodd-http
4.3.2
com.auth0
java-jwt
3.4.1
org.projectlombok
lombok
1.18.8
ch.qos.logback
logback-classic
1.2.3
苹果公钥地址返回结果映射实体类:
import lombok.Data;
import java.util.List;
@Data
public class AppleAuthKeys {
private List- keys;
@Data
public static class Item{
private String kty;
private String kid;
private String use;
private String alg;
private String n;
private String e;
}
}
校验工具类:
import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import jodd.http.HttpRequest;
import lombok.extern.slf4j.Slf4j;
import sun.security.rsa.RSAPublicKeyImpl;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.util.List;
@Slf4j
public class JWTUtil {
private static final String ISSUER = "https://appleid.apple.com";
private static final String AUDIENCE = "这里填写,app bundle id";
private static final String APPLE_AUTH_KEYS_URL = "https://appleid.apple.com/auth/keys";
public static Boolean verifyAppleLoginToken(String token,String subject) throws IOException {
//先从token中解析出HEADER部分的kid,然后从苹果提供的公钥获取url中获取生成rsa公钥所需的n和e
DecodedJWT decodedJWT = JWT.decode(token);
String kid = decodedJWT.getHeaderClaim("kid").asString();
String response = HttpRequest.get(APPLE_AUTH_KEYS_URL).send().body();
List keyList = JSON.parseObject(response, AppleAuthKeys.class).getKeys();
String n = null, e = null;
for (AppleAuthKeys.Item item : keyList) {
if (item.getKid().equals(kid)) {
n = item.getN();
e = item.getE();
}
}
if (null == n || null == e) {
log.error("根据token解析出来的kid获取苹果公钥参数n和e失败,token:{}", token);
return false;
}
//各个版本的base64解码实现不太一样,目前发现只有apache的base64可以解析成功
BigInteger bigIntModulus = new BigInteger(1, org.apache.commons.codec.binary.Base64.decodeBase64(n));
BigInteger bigIntPrivateExponent = new BigInteger(1, org.apache.commons.codec.binary.Base64.decodeBase64(e));
try {
//使用生成的RSA公钥验证token的SIGNATURE部分是否合法,(同时验证ISSUER,SUBJECT,AUDIENCE是否合法)
RSAPublicKeyImpl rsaPublicKey = new RSAPublicKeyImpl(bigIntModulus, bigIntPrivateExponent);
Algorithm algorithm = Algorithm.RSA256(rsaPublicKey, null);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.withSubject(subject)
.withAudience(AUDIENCE)
.build();
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException ex1) {
log.error("苹果token检验失败,失败原因:{},token:{}", ex1.getMessage(), token);
return false;
} catch (InvalidKeyException ex2) {
log.error("苹果token检验失败,公钥生成失败,失败原因:{},token:{}", ex2.getMessage(), token);
return false;
}
log.info("苹果登录token校验成功,n:{},e:{},token:{}", n, e, token);
return true;
}
public static void main(String[] args) throws IOException {
verifyAppleLoginToken("待验证的苹果登录token","待验证的用户唯一ID");
}
}
nodejs版本
const NodeRSA = require("node-rsa");
const axios = require("axios");
const jwt = require("jsonwebtoken");
async function getApplePublicKey(kid) {
let res = await axios.request({
method: "GET",
url: "https://appleid.apple.com/auth/keys",
headers: {
"Content-Type": "application/json"
}
});
let key = res.data.keys.filter(item => item.kid == kid)[0];
console.log(key);
const pubKey = new NodeRSA();
pubKey.importKey(
{ n: Buffer.from(key.n, "base64"), e: Buffer.from(key.e, "base64") },
"components-public"
);
return pubKey.exportKey(["public"]);
}
const audience = "这里填写 app bundle id";
async function verifyIdToken(token,subject) {
const kid = jwt.decode(token, { complete: true }).header.kid;
const applePublicKey = await getApplePublicKey(kid);
console.log(applePublicKey);
const jwtClaims = jwt.verify(token, applePublicKey, {
algorithms: "RS256",
issuer: "https://appleid.apple.com",
audience: audience,
subject: subject
});
jwt.verify(token, applePublicKey, { algorithms: "RS256" }, (err, decode) => {
if (err) {
console.log("token验证失败:", err.message);
} else if (decode) {
console.log("token验证成功:", decode);
}
});
}
verifyIdToken("待验证的token","用户唯一ID");