Apple官方文档
前言:由于公司最近有个业务需求是要进行Apple账号授权登录,于是我看边看文档边借鉴其他人的写法,发现好多文章都有一个共性,一个是在解析JWT的时候自己设置参数后进行判断,这样做没什么意义,另外一个就是大多数人直接取苹果公钥的第二个值进行验证,这样写法是错的,正常做法是将jwt的hender进行解开,得到kid,然后根据kid对苹果公钥进行匹配得到正确的keys。下面分享我自己的写法。

一:获取苹果公钥

https://appleid.apple.com/auth/keys
公钥建议去请求得到最新的,因为公钥会经常换,当公钥不是最新时,在解析时会因为匹配不上而导致登录不成功,具体实现看文章下面的getAuthKeys方法。

二:验证JWT

引入JWT所需Maven包<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.8.3</version></dependency><dependency><groupId>com.auth0</groupId><artifactId>jwks-rsa</artifactId><version>0.12.0</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version></dependency>
   @Autowiredprotected RestTemplate restTemplate;/*** identityToken授权判断       * 这里代码只展示验证整个过程,具体其他业务得结合自己的公司去实现*/public Response<AppleUserDetailsVo > appleLoginVerify(String fullName,String identityToken) throws Exception {log.info("登录接受参数----->fullName:" + fullName);//这里要求传fullName是因为苹果在第一次登陆的时候才会返回fullName,而且在验证通过后还获取不到fullName,邮箱的话在验证通过后可以获取到,所以不需要App传邮箱if (!verify(identityToken)) {//验证identityTokenreturn Response.failure(100001, "Apple identity token expired.");}//identityToken验证通过之后进行解码,再结合自己所需的参数返回给App,AppleUserDetailsVo appleUserDetailsVo = parserIdentityToken(identityToken);if (null != appleUserDetailsVo) {appleUserDetailsVo.setFullName(fullName);appleUserDetailsVo.setFullEmail(appleUserDetailsVo.getEmail());appleUserDetailsVo.setGender(0);appleUserDetailsVo.setIdentityType(1);appleUserDetailsVo.setSource(2);appleUserDetailsVo.setPicture(Const.PICTURE);} else {return Response.failure(200001, "Apple identity token decoding failed.");}log.info("AppleUserDetailsVo值" + appleUserDetailsVo);return Response.data(appleUserDetailsVo);}/*** 对前端传来的JWT字符串identityToken的Payload(第二段)进行解码* 主要获取其中的aud和sub,aud大概对应ios前端的包名,sub大概对应当前用户的授权的openID*/private AppleUserDetailsVo parserIdentityToken(String identityToken) throws IOException {String[] arr = identityToken.split("\\.");String decode = new String(Base64.decodeBase64(arr[1]));log.info("苹果登录后获取到的值:" + decode);ObjectMapper objectMapper = new ObjectMapper();objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);return objectMapper.readValue(decode, AppleUserDetailsVo.class);}private Boolean verify(String jwt) throws Exception {List<ApplePublicKeyVo> arr = getAuthKeys();if (arr == null) {return false;}try {//先取jwt中的header来匹配苹果公钥的kidString header = new String(Base64.decodeBase64(jwt.split("\\.")[0]));ObjectMapper objectMapper = new ObjectMapper();ApplePublicKeyVo applePublicKeyVo = objectMapper.readValue(header, ApplePublicKeyVo.class);Optional<ApplePublicKeyVo> target;target = arr.stream().filter(apple -> apple.equals(applePublicKeyVo)).findFirst();return verifyExc(jwt, target.get());} catch (Exception e) {return false;}/*** 对前端传来的identityToken进行验证** @param jwt     对应前端传来的 identityToken* @param authKey 苹果的公钥 authKey*/private static Boolean verifyExc(String jwt, ApplePublicKeyVo authKey) throws InvalidPublicKeyException {try {PublicKey publicKey;if (!"RSA".equalsIgnoreCase(authKey.getKty())) {throw new InvalidPublicKeyException("The key is not of type RSA", (Throwable) null);} else {KeyFactory kf = KeyFactory.getInstance("RSA");BigInteger modulus = new BigInteger(1, Base64.decodeBase64(authKey.getN()));BigInteger exponent = new BigInteger(1, Base64.decodeBase64(authKey.getE()));publicKey = kf.generatePublic(new RSAPublicKeySpec(modulus, exponent));}Claims parse = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(jwt).getBody();String iss = parse.getIssuer();//苹果官方链接String audience = parse.getAudience();//app设置的包名int expiration = (int) (parse.getExpiration().getTime() / 1000);//过期时间Integer authTime = (Integer) parse.get("auth_time");//签发时间if (iss.contains(Const.ISS) && audience.equals(Const.AUD) && authTime < expiration) {//按照苹果官方来做,iss要存在https://appleid.apple.com,audience要与App设置的一致,签发时间要小于过期时间return true;} else {return false;}} catch (InvalidKeySpecException var4) {throw new InvalidPublicKeyException("Invalid public key", var4);} catch (NoSuchAlgorithmException var5) {throw new InvalidPublicKeyException("Invalid algorithm to generate key", var5);} catch (ExpiredJwtException e) {log.error("[AppleServiceImpl.verifyExc] [error] [apple identityToken expired]", e);return false;} catch (Exception e) {log.error("[AppleServiceImpl.verifyExc] [error] [apple identityToken illegal]", e);return false;}}/*** 获取苹果的公钥*/private static List<ApplePublicKeyVo> getAuthKeys() throws IOException {String url = "https://appleid.apple.com/auth/keys";//获取苹果公钥JSONObject json;try {json = restTemplate.getForObject(url, JSONObject.class);log.info("苹果公钥" + json);} catch (Exception e) {log.info("AppleLogin获取公钥发生错误,先用本地的公钥" + e);json = JSONObject.fromObject(Const.APPLE_KEYS);}if (json != null && json.optJSONArray("keys") != null) {ObjectMapper objectMapper = new ObjectMapper();return objectMapper.readValue(json.getJSONArray("keys").toString(), new TypeReference<List<ApplePublicKeyVo>>() {});}return null;}@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "授权登录后返回的对象")
public class AppleUserDetailsVo implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "签发者")private String iss;@ApiModelProperty(value = "目标受众")private String aud;@ApiModelProperty(value = "过期时间")private Integer exp;@ApiModelProperty(value = "签发时间")private Integer iat;@ApiModelProperty(value = "苹果userId")private String sub;@ApiModelProperty(value = "哈希数列")private String c_hash;@ApiModelProperty(value = "邮箱")private String email;@ApiModelProperty(value = "邮箱验证")private String email_verified;@ApiModelProperty(value = "是否是私有邮箱")private String is_private_email;@ApiModelProperty(value = "验证时间")private Integer auth_time;private boolean nonce_supported;private Integer real_user_status;@ApiModelProperty(value = "自定义参数:第三方拿到的名称")private String fullName;@ApiModelProperty(value = "自定义参数:第三方拿到的邮箱")private String fullEmail;@ApiModelProperty(value = "自定义参数:第三方拿到的性别")private Integer gender;@ApiModelProperty(value = "自定义参数:头像")private String picture;@ApiModelProperty(value = "自定义参数:身份类型(0邮箱 1是苹果 2是Google 3是 Facebook 4是Twitter)")private Integer identityType;@ApiModelProperty(value = "自定义参数:注册来源 1是安卓 2是IOS")private Integer source;@ApiModelProperty(value = "自定义参数:app标识")private Integer ascription;
}@Accessors(chain = true)
@ApiModel(value = "苹果公钥")
public class ApplePublicKeyVo implements Serializable {private static final long serialVersionUID = 1L;private String kty;private String kid;private String use;private String alg;private String n;private String e;public static long getSerialVersionUID() {return serialVersionUID;}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 getUse() {return use;}public void setUse(String use) {this.use = use;}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;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;ApplePublicKeyVo that = (ApplePublicKeyVo) o;return Objects.equals(kid, that.kid) &&Objects.equals(alg, that.alg);}@Overridepublic int hashCode() {return Objects.hash(kty, kid, use, alg, n, e);}
}

以上就是Apple登陆实现的整个流程。

苹果apple账号授权登录第三方APP相关推荐

  1. 微信授权登录第三方app遇到的问题

    最近公司app要求实现微信授权登录app的功能,我是个应届毕业生,工作经验不是很足,但是,开发任务来了,我也不会拖泥带水,去了微信开放平台注册了公司的app,得到了appid等相关信息,回到程序里面, ...

  2. iOS小课堂: 集成 《阿里百川》教程( 打开商品详情页、 淘宝账号授权登录、 完成交易闭环)

    文章目录 前言 I 打开商品详情页面 II 淘宝账号授权登录 2.2 集成文档 2.3 Cocoapod方式引入百川SDK 上 III 常见错误 3.2 读取身份图片AppKey失败, see als ...

  3. Steam账号无法登录第三方应用 / 网站的问题解决

    Steam想必不用多说,作为国际上最大的游戏平台之一,甚至成为了很多人生活中必不可少的一部分.可是Steam也有一些槽点,例如,游戏太贵啊.错误代码啊(这个更新后好像有所好转,之后也会为大家推荐一些加 ...

  4. 谷歌僵尸账号注销登录破解挑战 FB僵尸账号苹果僵尸账号找回登录破解挑战

    谷歌僵尸账号注销登录破解挑战FB僵尸账号苹果僵尸账号找回登录破解挑战 事情是这样的,我本人有一个 Facebook账号 (https://www.facebook.com/scanqrcodes ,h ...

  5. web网页第三方账号授权登录

    ❤️最细微信小程序版本上传.提交审核.发布[建议收藏]❤️ ❤️2021直击大厂前端开发岗位面试题❤️ ❤️效果图如下,如有需要请自取修改[建议收藏]!❤️ ❤️微信小程序的灰度发布❤️ web网页端 ...

  6. 干货|JustAuth三方账号授权登录免费搭建全流程

    三方登录的方式想必大家都很熟悉,基本健全的网页都会整几个入口,比如日常的微信.QQ,金融的支付宝,音视频的抖音.快手,码农领域的Github.Gitee等. 作为功能测试,我们就随机取一个简单的三方授 ...

  7. 首批 iPhone 13 用户直呼太“坑”:​拍照有马赛克、与 Apple Watch “失联”、第三方 App 还不能用高刷?

    iPhone 13 到底香不香,早在 9 月 15 号的苹果秋季发布会上给了我们答案.对此,自然是仁者见仁智者见智:有人认为 iPhone 13 "加量不加价"挺划算,有人则认为 ...

  8. 三方账号授权登录系统设计思路

    文章目录 背景 关键概念 系统设计思路 总结 背景 借技术总结时间梳理一下三方登录授权的一些技术细节实现,假设saas 店铺和商品管理插件中心是两个独立的账号体系(内部分别对应 shopId 和 se ...

  9. 苹果iOS系统下检查第三方APP是否安装及跳转启动

    2019独角兽企业重金招聘Python工程师标准>>> 在iOS系统,使用Url Scheme框架在APP间互相跳转和传递数据,本文只介绍如果检测和跳转. Url Scheme框架 ...

最新文章

  1. Python学习教程:Python爬虫抓取技术的门道
  2. 【Android 高性能音频】Oboe 开发流程 ( Oboe 完整代码示例 )
  3. IQmath中文手册
  4. Pytorch学习 - Task6 PyTorch常见的损失函数和优化器使用
  5. LINUX下源码包安装mysql
  6. 单片机低功耗设计杂谈
  7. C# error CS1729: 'XXClass' does not contain a constructor that takes 0 arguments的解决方案
  8. PPT转换PDF格式怎么转换?后悔现在才知道
  9. linux内核分支,新闻|Linux 内核分支 2.4 版结束生命周期
  10. python JEP安装
  11. matlab人脸识别论文
  12. 研发数据安全解决方案
  13. 什么软件可以测试睡眠质量心率,2020测睡眠质量的app排行榜-推荐10款有趣又有效的睡眠APP...
  14. 一套简单的基本生活财富自由方案
  15. 7.3万字肝爆Java8新特性,我不信你能看完!(建议收藏)
  16. 晴空物语与服务器连接中断,晴空物语刷星光币教学 要耐得住寂寞
  17. wps图表横纵坐标怎么设置_用WPS的excel插入图表怎样快速设置横纵坐标轴
  18. gii无法访问 yii2_Gii的CURD生成无法访问?
  19. 祝福丨TF中文社区成立一周年
  20. 软考信息安全工程师+2021-01-30 1.网络信息安全概述+重点

热门文章

  1. 马克思 第一章 世界的物质性及其发展规律
  2. 嵌入式系统课堂总结1
  3. 推荐十本值得一读的AI书籍(留言送书)
  4. 2022年熔化焊接与热切割试题及答案
  5. 20090726选股
  6. 库存系统难破题?且看京东到家如何破
  7. pythontrun什么意思_python新手笔记一
  8. python读取lst文件
  9. 自媒体创作没思路?这4款内容创作爆文神器助你摆脱内容瓶颈!
  10. 用System中System.setOut()方法修改输出方式