苹果授权登录,后端校验(Sign in with Apple)

接入苹果第三方登录流程记录

在网上找到一个中文版的流程图:
在这里插入图片描述

我的思路

  1. app端传入identityToken,解析获取header和paylod

identityToken 是一个 Json Web Token (JWT)。它由点号 (“.”) 分割为三部分:header、payload、signature。前两部分是两个 Json 字符串经过 base64Url 编码的结果。第三部分是前面二者加密后再做 base64Url 编码得到的。

  1. 获取苹果的公钥,链接: https://appleid.apple.com/auth/keys,会得到有个jsonArray数组,如图:
    一般返回3个数据
  2. 使用第一步从header中解析出来的"kid"去匹配苹果公钥
  3. 通过苹果公钥中的"n"和"e"生成解析identityToken的签名秘钥
  4. 解析成功说明鉴权成功,返回解析出来的苹果唯一id; 反之则不然

此处我在解析时,从解析出来的结果中的"aud:bundle id苹果打包名"做了一次匹配,也可以不这么做

代码实例

我将鉴权方法做成一个工具类,可以直接复制代码调用,此处代码需要maven依赖 jjwthutool工具类 ,不知道的可以到网上去找下依赖
下面代码中出现的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一个技术论坛,但是有时候会习惯性的使用,但使用体验越来越糟.改善社区环境人人有责,哈哈

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值