背景

苹果公司要求所有使用第三方登录的 App,都必须接入Sign in with Apple。

接入方式

  1. 基于JWT identityToken的算法验证

  2. 基于授权码的验证

校验流程


上图为苹果对接官网的流程示意图,大致意思就是在苹果手机需要使用第三方APP时,服务端会有用户信息,带着用户信息服务端请求苹果服务,验证用户信息,苹果服务端用户信息验证通过后,意味着用户登录苹果账号成功,则允许使用第三方APP进行登录等操作。

使用JWT方式接入步骤

(通过查询资料,大多数成功案例都是通过JWT方式实现的,下面就记录下JWT接入方式的实现)

我们可以先通过下图了解整个流程,之后对应到代码就会有所共鸣。

1. Maven引入JWT相关包
<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>
2. 相关说明
  • UserId:与用户的 Apple Id 一一对应。在同一个开发帐号下的所有 app 里,获取到的值都一样。

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

identityToken 示例:

eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmRpeWl5aW4ub25saW5lNTMiLCJleHAiOjE1OTc2NTAxNzQsImlhdCI6MTU5NzY0OTU3NCwic3ViIjoiMDAxMzc3LmQ0ZDVmMTAwODQ0ZTQzZjdiMWM1OWRiMzUyZWZkZmI4LjAyNTkiLCJjX2hhc2giOiJkTDVRdld2VTNjVHBxczNSazlUTnRBIiwiZW1haWwiOiI0OTk4OTY1MDdAcXEuY29tIiwiZW1haWxfdmVyaWZpZWQiOiJ0cnVlIiwiYXV0aF90aW1lIjoxNTk3NjQ5NTc0LCJub25jZV9zdXBwb3J0ZWQiOnRydWV9.hM9HjNsMJW2PjYP7SfbzF-GqOt0VnMjYGq4BoU68rkQ-K2lPp_ae5ziX6Bbr3WHg6cc3Z8OzGO63OfExvSj9gQTR596CZLvNGXhbI3piTK6597-cYsPCTbY7xHxgdHLuL8XhD-9dXPn9rouVYu4QA18JBQG1Q4sGsRzLEJ5DjOM9x1bkBz4Vu_5LEOefHFHkWN_RPCh_AOJGviDzm81kTkCTWn8jpm0tGdevMR93MOf44f7bjP2T8yezl0Vbv09TrnkdAqG0BsihCD0VN9JV7X2eagyumoxTdFfoRiOflFKAaQqohVzcqy9tHOGm_6w5h8bsRCmtBC4PnqIFqNy_AQ

前两部分解码后结果示例:

  • header
{kid: "86D88Kf",alg: "RS256"
}
  • payload
{"iss":"https://appleid.apple.com","aud":"io.github.0xa6a","exp":1581854624,"iat":1581854024,"sub":"001472.dab04f9ba9d34f03ad641fa82d1c598b.0945","nonce":"a-random -string","c_hash":"cZay7wqnmHuPcG6FhVDqZA","email":"yf7a3ps8hm91@privaterelay.appleid.com","email_verified":"true","is_private_email":"true","auth_time":1581854024
}
字段名 说明
iss 签发机构网址
aud bundle id
exp int 过期时间戳
iat 签发时间
sub user id
nouce 客户端发出请求时携带的随机串,用于对照
c_hash 一段哈希
email email
email_verified email 是否确认了
is_private_email 是否为 private email
auth_time email 授权时间
3. 核心代码
  • 解析前端传的identityToken
 /*** 对前端传来的JWT字符串identityToken的第二部分进行解码* 主要获取其中的aud和sub,aud大概对应ios前端的包名,sub大概对应当前用户的授权的openID** @param identityToken 身份token* @return {"aud":"com.xkj.****","sub":"000***.8da764d3f9e34d2183e8da08a1057***.0***","c_hash":"UsKAuEoI-****","email_verified":"true","auth_time":1574673481,"iss":"https://appleid.apple.com","exp":1574674081,"iat":1574673481,"email":"****@qq.com"}*/private JSONObject parserIdentityToken(String identityToken) {String[] arr = identityToken.split("\\.");String decode = new String(Base64.decodeBase64(arr[1]));String substring = decode.substring(0, decode.indexOf("}") + 1);return JSON.parseObject(substring);}
  • 获取苹果公钥
 /*** 获取苹果的公钥** @return*/private static JSONArray getAuthKeys() {String url = "https://appleid.apple.com/auth/keys";RestTemplate restTemplate = new RestTemplate();JSONObject json = restTemplate.getForObject(url, JSONObject.class);if (json != null) {return json.getJSONArray("keys");}return null;}
  • 将公钥与前端传的identityToken进行校验
/*** 对前端传来的identityToken进行验证** @param jwt     对应前端传来的 identityToken* @param authKey 苹果的公钥 authKey* @return* @throws Exception*/
private static Boolean verifyExc(String jwt, JSONObject authKey) throws Exception {Jwk jwa = Jwk.fromValues(authKey);PublicKey publicKey = jwa.getPublicKey();String aud = "";String sub = "";if (jwt.split("\\.").length > 1) {String claim = new String(Base64.decodeBase64(jwt.split("\\.")[1]));aud = JSONObject.parseObject(claim).get("aud").toString();sub = JSONObject.parseObject(claim).get("sub").toString();}JwtParser jwtParser = Jwts.parser().setSigningKey(publicKey);jwtParser.requireIssuer("https://appleid.apple.com");jwtParser.requireAudience(aud);jwtParser.requireSubject(sub);try {Jws<Claims> claim = jwtParser.parseClaimsJws(jwt);if (claim != null && claim.getBody().containsKey("auth_time")) {System.out.println(claim);return true;}return false;} catch (ExpiredJwtException e) {log.error("apple identityToken expired", e);return false;} catch (Exception e) {log.error("apple identityToken illegal", e);return false;}
}
4. 遇到的问题
  • 解析前端传的identityToken方法出错

这个问题主要是IOS传的identityToken有问题,后端拿到的identityToken一定是由(“.”)号分割成三部分的字符串,而前端刚开始传的是不带点号的,想到后端有Base64解码编码的代码,就拿着字符串用在线工具解码试了下,发现问题是IOS传的是编码后的identityToken,解码后得到的字符串就是没问题的了。

  • 后端identityToken与苹果返回公钥做校验出错,具体错误如下:
io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.

这个问题是将identityToken和苹果返回的公钥做校验不通过,苹果返回的公钥是一个数组,有两个公钥,我们不能只取一个做校验,应该判断第一个校验不通过还需要拿第二个继续做校验。

  • 后端identityToken过期错误,具体错误如下:
io.jsonwebtoken.ExpiredJwtException: JWT expired at 2020-08-17T15:42:54Z. Current time: 2020-08-21T17:14:24Z, a difference of 351090141 milliseconds.  Allowed clock skew: 0 milliseconds.

IOS端给的identityToken是有过期时间的,苹果默认是5分钟,过期后需要前端重新获取一个identityToken.

总结

测试通过的校验代码已上传github:接入苹果授权登录验证。验证可以说是单独的部分,具体登录逻辑还需要结合自身项目去加以改造。

【JAVA】对接苹果授权登录流程相关推荐

  1. Apple Sign in with Apple(苹果授权登录PHP)

    Apple Sign in with Apple(苹果授权登录PHP) 文章目录 Apple Sign in with Apple(苹果授权登录PHP) 一.登录Apple Developer 二.创 ...

  2. Sign in With Apple (苹果授权登录)

    Sign in With Apple (苹果授权登录) 关于Sign in With Apple (苹果授权登录)的问题,公司app上架appStore被拒原因是使用第三方授权登陆但是却没有使用苹果账 ...

  3. 苹果授权登录Sign In With Apple亲测通过版[100%成功]

    苹果授权登录Sign In With Apple后台代码实现JAVA版本亲测通过版 废话不多说,直接复制把自己的包名写上就可以用了 有个别的小坑,HttpUtil自己写,没附上 Base64一定要用o ...

  4. iOS 苹果授权登录(Sign in with Apple)

    在 iOS13 中,如果 App 提供第三方登录,就必须添加 苹果登录 Sign in with Apple 选项,并要求所有开发者于 2020年4月之前 完成现有应用的更新,否则审核不给通过. iO ...

  5. 微信授权登录流程以及公众号配置方法(golang后端)

    一.准备一个已经认证OK的微信公众号和已经备案的域名,且解析好配置好https证书. 1.如上图 微信公众号 > 基本配置 ,设置开发者密码 2.设置IP白名单,白名单填写提供后端服务的服务器公 ...

  6. Java版本微信授权登录(测试版)

    这篇文章是对微信授权登录的一个测试版本,并不能直接在生产上使用,对于在生产上正式使用将会在下一篇中描述. 一,首先需要以下两个数据 appID和appsecret 如何获取这两个数据,请先登录微信公众 ...

  7. 提高微信小程序的应用速度的常见方式有哪些? 小程序怎么实现下拉刷新? 简述微信小程序原理? 小程序的发布流程(开发流程)分析下微信小程序的优劣势?小程序授权登录流程? 小程序支付如何实现

    小程序部分常见面试题 提高微信小程序的应用速度的常见方式有哪些? 提高页面加载速度 用户行为预测 减少默认data的大小 组件化方案 分包预下载 小程序与原生App相比优缺点? 优点: 基于微信平台开 ...

  8. Java版本微信授权登录(升级版)

    前面写了一遍文章<Java版本微信授权登录(测试版)>,可以当做入门的基础文章,这里继续做一点深入,主要解决的是,如何在本地开发中微信授权以后跳转到本地启动的项目中. 我们知道了微信公众平 ...

  9. 支付宝app登录授权的infoStr授权登录流程

    官网: 服务端sdk:https://docs.open.alipay.com/54/103419/ 客户端如何使用登录:https://docs.open.alipay.com/218/105329 ...

最新文章

  1. android 防止连点的方法
  2. FPGA基于双端口RAM的乒乓操作
  3. 最强的目标检测网络:DetectoRS 54.7 AP
  4. mySQL:两表更新(用一个表更新另一个表)的SQL语句
  5. Javascript 盲区和 操作实例 笔记
  6. Android开源项目整理:个性化空间View篇(看遍论坛千万篇,不看此篇也枉然)
  7. Project Euler Problem 9-Special Pythagorean triplet
  8. HTML5/CSS3基础
  9. android 将布局多次添加,android – 如何在布局xml中添加循环视图
  10. 面向对象设计原则之2-开放闭合原则
  11. linux如何进入grub启动菜单,Linux下Grub和NTLoader如何启动菜单DIY?
  12. linux cat命令追加,linux cat命令
  13. 二叉树:二叉树的最近公共祖先
  14. 框架 - SpringMVC框架
  15. 第1-6课:算法设计常用思想之穷举法
  16. 牛客MySQL:错题
  17. 风口的猪(小米实习生招聘)
  18. 微信小程序销毁某一注册函数_微信小程序注销手册
  19. 共享内存—shmget参数shmflg详解—IPC_CREAT、IPC_EXCL、0666(对内存的读写执行权限)
  20. 重温经典(三)-百年思索

热门文章

  1. 【公众号】如何将公众号给他人开发
  2. Android仿keep运动轨迹动画,Android仿Keep运动休息倒计时圆形控件
  3. 03-能看懂代码,就是自己写不出来,怎么办?
  4. Boost(一)——Boost简介
  5. html图片左侧留白,HTML+CSS入门 如何解决图片跨域导致的截图空白
  6. 免费的ipad编辑php软件下载,免费也很香!8 款免费 APP 打造你的学术型 iPad
  7. 模电:晶振与匹配电容的总结
  8. 小米5s plus 刷机 国际版
  9. 传奇怎么设置不显示服务器,如何将传奇服务器未知神殿地图修改为不限制进出...
  10. 理科生浪漫java表白代码_数学公式表白-2020理科生专属浪漫表白句子大全