接入苹果第三方登录流程记录
在网上找到一个中文版的流程图:
我的思路
- app端传入identityToken,解析获取header和paylod
identityToken 是一个 Json Web Token (JWT)。它由点号 (“.”) 分割为三部分:header、payload、signature。前两部分是两个 Json 字符串经过 base64Url 编码的结果。第三部分是前面二者加密后再做 base64Url 编码得到的。
- 获取苹果的公钥,链接: https://appleid.apple.com/auth/keys,会得到有个jsonArray数组,如图:
- 使用第一步从header中解析出来的"kid"去匹配苹果公钥
- 通过苹果公钥中的"n"和"e"生成解析identityToken的签名秘钥
- 解析成功说明鉴权成功,返回解析出来的苹果唯一id; 反之则不然
此处我在解析时,从解析出来的结果中的"aud:bundle id苹果打包名"做了一次匹配,也可以不这么做
代码实例
我将鉴权方法做成一个工具类,可以直接复制代码调用,此处代码需要maven依赖 jjwt 和 hutool工具类 ,不知道的可以到网上去找下依赖
下面代码中出现的TKServiceException类是自定义的异常类,可以替换
import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xiyakj.xiyakj.common.core.exception.TKServiceException;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import java.math.BigInteger;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;
/**
* 苹果授权工具类
*
* @author xiaowx
* @date 2021/12/30 11:57
**/
@Slf4j
public class AppleAuthUtil {
/**
* 获取苹果公钥url
*/
private static final String APPLE_AUTH_KEYS_URL = "https://appleid.apple.com/auth/keys";
public static String parserIdentityToken(String identityToken) {
//获取token的header和payload,需要根据header中的kid去匹配公钥
String[] arr = identityToken.split("\\.");
String header = new String(Base64.decodeBase64(arr[0]));
String payload = new String(Base64.decodeBase64(arr[1]));
JSONObject headerJson = JSONObject.parseObject(header);
JSONObject payloadJson = JSONObject.parseObject(payload);
//获取苹果公钥
JSONObject keyJson = getAppleAuthKey(headerJson.getString("kid"));
if (null == keyJson) {
throw new TKServiceException("苹果授权异常");
}
//根据苹果返回的公钥生成jwt解析的秘钥
PublicKey jwtSigningKey = getJwtSigningKey(keyJson);
//通过秘钥解析jwt,返回苹果的唯一id
String appleOpenId = verify(jwtSigningKey, identityToken, payloadJson.getString("aud"));
if (StringUtils.isBlank(appleOpenId)) {
throw new TKServiceException("苹果授权解析异常");
}
return appleOpenId;
}
/**
* 通过从header中获取的kid匹配公钥
*
* @param kid: jwt-header中解析
* @return
*/
private static JSONObject getAppleAuthKey(String kid) {
String result = HttpUtil.get(APPLE_AUTH_KEYS_URL);
JSONObject resultJson = JSONObject.parseObject(result);
JSONArray keys = resultJson.getJSONArray("keys");
for (Object key : keys) {
JSONObject keyJson = JSONObject.parseObject(key.toString());
if (keyJson.getString("kid").equals(kid)) {
return keyJson;
}
}
return null;
}
/**
* 获取identityToken签名秘钥
*
* @param keyJson
* @return
*/
private static PublicKey getJwtSigningKey(JSONObject keyJson) {
byte[] nDecode = Base64Decoder.decode(keyJson.getString("n").getBytes());
byte[] eDecode = Base64Decoder.decode(keyJson.getString("e").getBytes());
//通过RSAPublicKeySpec构造器生成对象
RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(new BigInteger(1, nDecode), new BigInteger(1, eDecode));
//传入hutool工具类中生成publicKey
return SecureUtil.generatePublicKey("RSA", rsaPublicKeySpec);
}
/**
* 解析identityToken并返回苹果唯一id
*
* @param key: 解析秘钥
* @param jwt: identityToken
* @param audience: 我此处传的是解析出来的包名
* @return
*/
private static String verify(PublicKey key, String jwt, String audience) {
JwtParser jwtParser = Jwts.parser().setSigningKey(key);
try {
Jws<Claims> claim = jwtParser.parseClaimsJws(jwt);
if (claim != null && claim.getBody().getAudience().equals(audience)) {
return claim.getBody().getSubject();
}
return null;
} catch (Exception e) {
log.error("apple identityToken expired", e);
return null;
}
}
}
后记
此代码实现仅代表我个人的思路,可供参考.
之所以写这篇博客,一是为了记录,二是为了提供一种思路
杂谈
今天上午找"苹果授权登录后台处理"解决方案,在csdn翻阅了10来篇文章,10篇有大概4篇内容重复率很高,但是博主id却不一样,也有些确实提供了帮助,但大多数文章有些杂乱,没有逻辑,有些参数都来的莫名其妙,有些代码自己都没验证就贴了上来,纯属浪费时间.
在此并非厚此薄彼,更有甚者,直接copy他人的博客,标点符号都不改,并且还标为"原创",这些人不仅代码Ctrl c+Ctrl v,博客都得如此,还的署上大名,将copy来的标为原创
刚才跟同事讨论此情况,有个不成才的想法:
希望csdn搞一个清网行动,如果发现copy的博客,并且还标为原创的,举报有奖励,不管是什么奖励,多少有点激励,至少可以改善下当前copy鸠占鹊巢的情形.增加用户体验感
还有并不是只有csdn一个技术论坛,但是有时候会习惯性的使用,但使用体验越来越糟.改善社区环境人人有责,哈哈