【JAVA】对接苹果授权登录流程
背景
苹果公司要求所有使用第三方登录的 App,都必须接入Sign in with Apple。
接入方式
基于JWT identityToken的算法验证
基于授权码的验证
校验流程
上图为苹果对接官网的流程示意图,大致意思就是在苹果手机需要使用第三方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_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】对接苹果授权登录流程相关推荐
- Apple Sign in with Apple(苹果授权登录PHP)
Apple Sign in with Apple(苹果授权登录PHP) 文章目录 Apple Sign in with Apple(苹果授权登录PHP) 一.登录Apple Developer 二.创 ...
- Sign in With Apple (苹果授权登录)
Sign in With Apple (苹果授权登录) 关于Sign in With Apple (苹果授权登录)的问题,公司app上架appStore被拒原因是使用第三方授权登陆但是却没有使用苹果账 ...
- 苹果授权登录Sign In With Apple亲测通过版[100%成功]
苹果授权登录Sign In With Apple后台代码实现JAVA版本亲测通过版 废话不多说,直接复制把自己的包名写上就可以用了 有个别的小坑,HttpUtil自己写,没附上 Base64一定要用o ...
- iOS 苹果授权登录(Sign in with Apple)
在 iOS13 中,如果 App 提供第三方登录,就必须添加 苹果登录 Sign in with Apple 选项,并要求所有开发者于 2020年4月之前 完成现有应用的更新,否则审核不给通过. iO ...
- 微信授权登录流程以及公众号配置方法(golang后端)
一.准备一个已经认证OK的微信公众号和已经备案的域名,且解析好配置好https证书. 1.如上图 微信公众号 > 基本配置 ,设置开发者密码 2.设置IP白名单,白名单填写提供后端服务的服务器公 ...
- Java版本微信授权登录(测试版)
这篇文章是对微信授权登录的一个测试版本,并不能直接在生产上使用,对于在生产上正式使用将会在下一篇中描述. 一,首先需要以下两个数据 appID和appsecret 如何获取这两个数据,请先登录微信公众 ...
- 提高微信小程序的应用速度的常见方式有哪些? 小程序怎么实现下拉刷新? 简述微信小程序原理? 小程序的发布流程(开发流程)分析下微信小程序的优劣势?小程序授权登录流程? 小程序支付如何实现
小程序部分常见面试题 提高微信小程序的应用速度的常见方式有哪些? 提高页面加载速度 用户行为预测 减少默认data的大小 组件化方案 分包预下载 小程序与原生App相比优缺点? 优点: 基于微信平台开 ...
- Java版本微信授权登录(升级版)
前面写了一遍文章<Java版本微信授权登录(测试版)>,可以当做入门的基础文章,这里继续做一点深入,主要解决的是,如何在本地开发中微信授权以后跳转到本地启动的项目中. 我们知道了微信公众平 ...
- 支付宝app登录授权的infoStr授权登录流程
官网: 服务端sdk:https://docs.open.alipay.com/54/103419/ 客户端如何使用登录:https://docs.open.alipay.com/218/105329 ...
最新文章
- android 防止连点的方法
- FPGA基于双端口RAM的乒乓操作
- 最强的目标检测网络:DetectoRS 54.7 AP
- mySQL:两表更新(用一个表更新另一个表)的SQL语句
- Javascript 盲区和 操作实例 笔记
- Android开源项目整理:个性化空间View篇(看遍论坛千万篇,不看此篇也枉然)
- Project Euler Problem 9-Special Pythagorean triplet
- HTML5/CSS3基础
- android 将布局多次添加,android – 如何在布局xml中添加循环视图
- 面向对象设计原则之2-开放闭合原则
- linux如何进入grub启动菜单,Linux下Grub和NTLoader如何启动菜单DIY?
- linux cat命令追加,linux cat命令
- 二叉树:二叉树的最近公共祖先
- 框架 - SpringMVC框架
- 第1-6课:算法设计常用思想之穷举法
- 牛客MySQL:错题
- 风口的猪(小米实习生招聘)
- 微信小程序销毁某一注册函数_微信小程序注销手册
- 共享内存—shmget参数shmflg详解—IPC_CREAT、IPC_EXCL、0666(对内存的读写执行权限)
- 重温经典(三)-百年思索
热门文章
- 【公众号】如何将公众号给他人开发
- Android仿keep运动轨迹动画,Android仿Keep运动休息倒计时圆形控件
- 03-能看懂代码,就是自己写不出来,怎么办?
- Boost(一)——Boost简介
- html图片左侧留白,HTML+CSS入门 如何解决图片跨域导致的截图空白
- 免费的ipad编辑php软件下载,免费也很香!8 款免费 APP 打造你的学术型 iPad
- 模电:晶振与匹配电容的总结
- 小米5s plus 刷机 国际版
- 传奇怎么设置不显示服务器,如何将传奇服务器未知神殿地图修改为不限制进出...
- 理科生浪漫java表白代码_数学公式表白-2020理科生专属浪漫表白句子大全