Sign in with Apple (Java后端校验总结)

1.背景

公司开发的app上线许久,支持三方登录(微信、qq)。由于不久前苹果官方新政策,所以要也要接入appleId登录。
这块由我来负责Sign in with Apple 服务端校验(服务端由java开发)。所以本人也是从零开始在网上先从苹果官方网站上找一下接入流程,不过很遗憾苹果官方的文档看不出个所以然来,连个例子都没有。就只能查找国内有过相关经验的小伙伴写的,关于apple 验证登录的技术文章。通过国内小伙伴的技术分享,本人也顺利完成该功能,下面总结一下以便能帮助到其他小伙伴。

2.App端授权登陆方式

针对App端授权登陆,提供两种后端验证方式:

     1.基于JWT的算法验证。

      2.是基于授权码的验证。

 本文主要讲JWT这种验证方式。

3.校验流程图

以下流程图直接引用以下技术文章的流程图

https://www.cnblogs.com/0xa6a/p/12330069.html?utm_source=tuicool

image

4.相关说明

请查看该文章下的相关说明里面有一些概念 请自己弄明白

https://www.cnblogs.com/0xa6a/p/12330069.html?utm_source=tuicool

5.代码实现

下面的实现流程完全按照图图3.1实现的

5.1 maven pom引用

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

5.2 java代码实现

方法入口

/**
 * 苹果登录校验
 * 
 * @param identityToken
 * @return
 */
public boolean verify(String identityToken) {
    try {
        if (identityToken.split("\\.").length > 1) {
            String firstDate = new String(Base64.decodeBase64(identityToken.split("\\.")[0]), "UTF-8");
            String claim = new String(Base64.decodeBase64(identityToken.split("\\.")[1]), "UTF-8");
            String kid = JSONObject.parseObject(firstDate).get("kid").toString();
            String aud = JSONObject.parseObject(claim).get("aud").toString();
            String sub = JSONObject.parseObject(claim).get("sub").toString();
            PublicKey publicKey = getPublicKey(kid);
            if (publicKey == null) {
                return false;
            }
            boolean reuslt = verify(publicKey, identityToken, aud, sub);
            if (reuslt) {
                logger.info("苹果登录授权成功!");
                return true;
            }
        }
    } catch (Exception e) {
        logger.error("苹果登录授权异常!  {}", e.getMessage());
        e.printStackTrace();
    }
    return false;
}

校验方法

其中的ISS为 https://appleid.apple.com 可以通过
String iss= JSONObject.parseObject(claim).get("iss").toString() 获得。

private boolean verify(PublicKey key, String jwt, String audience, String subject) throws Exception {
    boolean result = false;
    JwtParser jwtParser = Jwts.parser().setSigningKey(key);
    jwtParser.requireIssuer(ISS);
    jwtParser.requireAudience(audience);
    jwtParser.requireSubject(subject);
    try {
        Jws<Claims> claim = jwtParser.parseClaimsJws(jwt);
        if (claim != null && claim.getBody().containsKey("auth_time")) {
            return true;
        }
    } catch (ExpiredJwtException e) {
        throw new BusinessException("苹果identityToken过期", e.getMessage());
    } catch (SignatureException e) {
        throw new BusinessException("苹果identityToken非法", e.getMessage());
    }
    return result;
}

获取PublicKey方法

其中APPLE_AUTH_URL为https://appleid.apple.com/auth/keys 里面的kid是很关键的一个参数
因为通过https://appleid.apple.com/auth/keys获取到的keys 是一个数组有多个key,
必须得通过header解密出来的kid 来筛选到对应的key。
入口方法中的
String firstDate = new String(Base64.decodeBase64(identityToken.split("\.")[0]), "UTF-8");
String kid = JSONObject.parseObject(firstDate).get("kid").toString();
就是来获取kid的 这样来获取PublicKey 能避免
o.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted 问题

/**
     * 该获取PublicKey方法解决   io.jsonwebtoken.SignatureException: JWT signature does not
     * match locally computed signature. JWT validity cannot be asserted and should
     * not be trusted 问题
     * 
     * @param kid
     * @return
     */
    private PublicKey getPublicKey(String kid) {
        try {
            String str = HTTPWeb.get(APPLE_AUTH_URL, new HashMap<String, String>());
            JSONObject data = JSONObject.parseObject(str);
            JSONArray jsonArray = data.getJSONArray("keys");
            if (jsonArray.isEmpty()) {
                return null;
            }
            for (Object object : jsonArray) {
                JSONObject json = ((JSONObject) object);
                if (json.getString("kid").equals(kid)) {
                    String n = json.getString("n");
                    String e = json.getString("e");
                    BigInteger modulus = new BigInteger(1, Base64.decodeBase64(n));
                    BigInteger publicExponent = new BigInteger(1, Base64.decodeBase64(e));
                    RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
                    KeyFactory kf = KeyFactory.getInstance("RSA");
                    return kf.generatePublic(spec);
                }
            }
        } catch (Exception e) {
            logger.error("getPublicKey异常!  {}", e.getMessage());
            e.printStackTrace();
        }
        return null;

    }

HTTPWeb中的get方法

public static String get(String url , Map<String, ? extends Object> data){
        logger.info("请求地址为:"+url);
        CloseableHttpClient client = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet();
        try {
            URIBuilder uriBuilder = new URIBuilder(url);
            if(data != null){
                for (String key : data.keySet()) {
                    uriBuilder.setParameter(key, data.get(key).toString());
                }
            }
            RequestConfig config = RequestConfig.custom()
                    .setConnectTimeout(1000)
                    .setConnectionRequestTimeout(2000)
                    .build();
            httpGet.setConfig(config);
            httpGet.setURI(uriBuilder.build());
            CloseableHttpResponse response = client.execute(httpGet);
            HttpEntity entity = response.getEntity();
            if(entity == null) {
                return null;
            }
            BufferedReader reader = new BufferedReader((new InputStreamReader(entity.getContent(),"utf-8")));
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ( (str = reader.readLine()) != null ) {
                buffer.append(str);
            }
            response.close();
            return buffer.toString();
        } catch (URISyntaxException e) {
            logger.warn("..URL:"+url, e);
        } catch (ClientProtocolException e) {
            logger.warn("..URL:"+url, e);
        } catch (IOException e) {
            logger.warn("..URL:"+url, e);
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                logger.warn("",e);
            }
        }
        return null;
    }

6.总结及鸣谢

本人按照上述总结流程,已顺利完成,Sign in with Apple Java后端校验。

本人能力有限,上面的总结可能有漏洞,如有问题请指正。

另外感谢分享Sign in with Apple 相关技术文章的小伙伴

下面几篇文章对我很有参考意义:

Sign in with Apple 流程总结 - 0xa6a - 博客园

Java后端校验Sign in With Apple (苹果APP授权登录)_xunkoo的博客-CSDN博客_java 集成苹果登录

Sign in with Apple(苹果授权登陆)水不可追-CSDN博客苹果登录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,825评论 6 546
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,814评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,980评论 0 384
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 64,064评论 1 319
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,779评论 6 414
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,109评论 1 330
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,099评论 3 450
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,287评论 0 291
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,799评论 1 338
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,515评论 3 361
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,750评论 1 375
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,221评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,933评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,327评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,667评论 1 296
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,492评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,703评论 2 380