最近新开发的ios平台的app在提审的时候,被拒了,原因是app上如果有接第三方登陆(比如,微信,微博,facebook等),那就必须要接apple id登陆,坑爹~苹果霸权啊!然而没办法,靠他吃饭,他是爸爸,唯有顺从。下面我来说一下对接苹果登陆的后端验证模块,目前这一块网上资料比较少,而且说得不够完整。至于app端的对接,网上一搜,一大堆,很完善。

  这里先说一下apple id登陆的主要流程和涉及到的一些知识点。首先apple登陆的时序图如下:

  先是app和苹果服务器通信获得identitytoken,然后把identitytoken交给业务后台验证,验证通过就可以了。其中appServer涉及到的验证,就是identitytoken,其实identitytoken就是一个jws(关于jws的只是可以参考https://www.jianshu.com/p/50ade6f2e4fd),至于校验jws,其实是有现成的jar包可以实现,验证jws的签名,保证数据没有被篡改之后,还要校验从identitytokendecode出来的nonce,iss,aud,exp,主要是iss和exp这两个。下面我直接上代码:

1.通过maven引入一下两个包,主要是用于验证jws,如下:

        <dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.6.4</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

  

2.验证是identitytoken是否有效,其中有两个主要的地方,第一个就是把从appleServer获取到的publicKey字符串转换为PublicKey对象;第二个就是使用函数"jsonWebSignature.verifySignature()"验证jws的signature,代码如下:

public class AppleIdAccountValidationService {
private final static Logger logger = LoggerFactory.getLogger(AppleIdAccountValidationService.class);
private final static int APPLE_ID_PUBLIC_KEY_EXPIRE = 24; //24h @Autowired
private StringRedisUtils stringRedisUtils; public boolean isValid(String accessToken) {
//校验基本信息:nonce,iss,aud,exp
CusJws cusJws = this.getJws(accessToken);
if (cusJws == null) {
return false;
}
//iss
long curTime = System.currentTimeMillis();
if (cusJws.getJwsPayload().getExp() * 1000 < curTime) {
return false;
}
if (!JwsPayload.ISS.equals(cusJws.getJwsPayload().getIss())) {
return false;
}
//校验签名
if (!this.verifySignature(accessToken)) {
return false;
}
return true;
} /**
* verify signature
* @param accessToken
* @return
*/
private boolean verifySignature(String accessToken) {
PublicKey publicKey = this.getAppleIdPublicKey();
JsonWebSignature jsonWebSignature = new JsonWebSignature();
jsonWebSignature.setKey(publicKey);
try {
jsonWebSignature.setCompactSerialization(accessToken);
return jsonWebSignature.verifySignature();
} catch (JoseException e) {
return false;
}
} /**
* publicKey会本地缓存1天
* @return
*/
private PublicKey getAppleIdPublicKey() {
String publicKeyStr = stringRedisUtils.getString(Constants.REDIS_KEY_APPLE_ID_PUBLIC_KEY);
if (publicKeyStr == null) {
publicKeyStr = this.getAppleIdPublicKeyFromRemote();
if (publicKeyStr == null) {
return null;
}
try {
PublicKey publicKey = this.publicKeyAdapter(publicKeyStr);
stringRedisUtils.setString(Constants.REDIS_KEY_APPLE_ID_PUBLIC_KEY, publicKeyStr, APPLE_ID_PUBLIC_KEY_EXPIRE, TimeUnit.HOURS);
return publicKey;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
return this.publicKeyAdapter(publicKeyStr);
} /**
* 将appleServer返回的publicKey转换成PublicKey对象
* @param publicKeyStr
* @return
*/
private PublicKey publicKeyAdapter(String publicKeyStr) {
if (!StringUtils.hasText(publicKeyStr)) {
return null;
}
Map maps = (Map)JSON.parse(publicKeyStr);
List keys = (List<Map>)maps.get("keys");
Map o = (Map) keys.get(0);
Jwk jwa = Jwk.fromValues(o);
try {
PublicKey publicKey = jwa.getPublicKey();
return publicKey;
} catch (InvalidPublicKeyException e) {
e.printStackTrace();
return null;
}
} /**
* 从appleServer获取publicKey
* @return
*/
private String getAppleIdPublicKeyFromRemote() {
ResponseEntity<String> responseEntity = new RestTemplate().getForEntity("https://appleid.apple.com/auth/keys", String.class);
if (responseEntity == null || responseEntity.getStatusCode() != HttpStatus.OK) {
logger.error(String.format("getAppleIdPublicKeyFromRemote [%s] exception, detail:", appleIdPublicKeyUrl));
return null;
}
return responseEntity.getBody();
} private CusJws getJws(String identityToken) {
String[] arrToken = identityToken.split("\\.");
if (arrToken == null || arrToken.length != 3) {
return null;
}
Base64.Decoder decoder = Base64.getDecoder();
JwsHeader jwsHeader = JSON.parseObject(new String(decoder.decode(arrToken[0])), JwsHeader.class);
JwsPayload jwsPayload = JSON.parseObject(new String(decoder.decode(arrToken[1])), JwsPayload.class);
return new CusJws(jwsHeader, jwsPayload, arrToken[2]);
} class CusJws {
private JwsHeader jwsHeader;
private JwsPayload jwsPayload;
private String signature; public CusJws(JwsHeader jwsHeader, JwsPayload jwsPayload, String signature) {
this.jwsHeader = jwsHeader;
this.jwsPayload = jwsPayload;
this.signature = signature;
} public JwsHeader getJwsHeader() {
return jwsHeader;
} public void setJwsHeader(JwsHeader jwsHeader) {
this.jwsHeader = jwsHeader;
} public JwsPayload getJwsPayload() {
return jwsPayload;
} public void setJwsPayload(JwsPayload jwsPayload) {
this.jwsPayload = jwsPayload;
} public String getSignature() {
return signature;
} public void setSignature(String signature) {
this.signature = signature;
}
} static class JwsHeader {
private String kid;
private String alg; public String getKid() {
return kid;
} public void setKid(String kid) {
this.kid = kid;
} public String getAlg() {
return alg;
} public void setAlg(String alg) {
this.alg = alg;
}
} static class JwsPayload {
private String iss;
private String sub;
private String aud;
private long exp;
private long iat;
private String nonce;
private String email;
private boolean email_verified; public final static String ISS = "https://appleid.apple.com"; public String getIss() {
return iss;
} public void setIss(String iss) {
this.iss = iss;
} public String getSub() {
return sub;
} public void setSub(String sub) {
this.sub = sub;
} public String getAud() {
return aud;
} public void setAud(String aud) {
this.aud = aud;
} public long getExp() {
return exp;
} public void setExp(long exp) {
this.exp = exp;
} public long getIat() {
return iat;
} public void setIat(long iat) {
this.iat = iat;
} public String getNonce() {
return nonce;
} public void setNonce(String nonce) {
this.nonce = nonce;
} public String getEmail() {
return email;
} public void setEmail(String email) {
this.email = email;
} public boolean isEmail_verified() {
return email_verified;
} public void setEmail_verified(boolean email_verified) {
this.email_verified = email_verified;
}
}
}

  

warn:以上是后台的验证方式一,后来发现有问题,更新后的方案以及后端验证的第二种方式,统一在微信公众号“ismallboy”更新。

                       欢迎关注微信公众号“ismallboy”,请扫码并关注以下公众号,并在公众号下面回复“word”,获得本文最新内容。

sign in with apple后端校验(java)的更多相关文章

  1. 关于Sign in with Apple 后台验证的一些记录

    2019年10月9号  IOS端新增Sign in with Apple IOS真是世界上最垃圾的语言,没有之一,苹果是世界上最垃圾的公司,没有之一 关于Sign in with Apple 苹果官方 ...

  2. Apple严控Java太不人性化

    转自:http://www.cdtarena.com/javapx/201307/9115.html Apple为了在系统安全方面得到更好的声誉,对更容易造成系统漏洞的Java进行着严格的控制,并在自 ...

  3. JSR303 后端校验包的使用

    1.首先通过Maven导入JSR303架包. <!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate- ...

  4. JSR303后端校验(一)

    JSR303后端校验(一) (1)在pom文件中添加依赖 <!-- JSR303后端校验 --> <dependency> <groupId>org.hiberna ...

  5. iOS sign in with Apple 苹果ID登录

    http://www.cocoachina.com/articles/109104?filter=ios https://juejin.im/post/5deefc5e518825126416611d ...

  6. Sign in with Apple 流程总结

    流程图 相关说明 UserId 与用户的 Apple Id 一一对应.在同一个开发帐号下的所有 app 里,获取到的值都一样. IdentityToken identityToken 是一个 Json ...

  7. 前后端分离Java后端主流开发环境框架20200622

    开发环境: IDE:IntelliJ IDEA 2017+ DB: mysql5.7.4.PostgreSQL.mongoDB.redis JDK:JDK1.8+ Maven:Maven 3.2.3+ ...

  8. C# Sign In With Apple苹果登陆后端验证

    苹果App授权登录 苹果官方的授权文档: 生成Token:https://developer.apple.com/documentation/sign_in_with_apple/generate_a ...

  9. 学习Spring Boot:(十)使用hibernate validation完成数据后端校验

    前言 后台数据的校验也是开发中比较注重的一点,用来校验数据的正确性,以免一些非法的数据破坏系统,或者进入数据库,造成数据污染,由于数据检验可能应用到很多层面,所以系统对数据校验要求比较严格且追求可变性 ...

随机推荐

  1. 身为一个 CS专业的留学生,你还在为堆积如山的编程assignment而发愁吗?

    每个人都渴望圆梦,当我们看见梦想在别人身上实现时,总在抱怨幸运之神为何不眷顾自己:其实更多的时候,梦想就在你身边的不远处,只要你迈出正确的步伐. 记得刚入大学时,意气风发.我以全班前几名的优异成绩考入 ...

  2. 大家都说好用的 Python 命令行库:click

    作者:HelloGitHub-Prodesire HelloGitHub 的<讲解开源项目>系列,项目地址:https://github.com/HelloGitHub-Team/Arti ...

  3. vue学习之深入响应式原理

    vue的响应式原理 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全 ...

  4. [考试反思]1029csp-s模拟测试92:弱智

    我只能这么评价我自己. 看这个提交时间...我没话可说... T1半个世界都A了还是切不掉.又一次挂细节. T2不会证明的乱搞(虽然可以证明)A了没什么可说的算是水过. T3之前水过的题(打的次正解) ...

  5. [考试反思]1024csp-s模拟测试86:消耗

    %%%两个没素质的和一个萌两小时AK 最近貌似总是可以比较快速的拿下T1,然后T2打到考试结束... T1是套路题没什么好说的. T2是一个比较蠢的博弈题,我花了很长时间干各种乱七八糟的事 什么打表啊 ...

  6. [转载]1.2 UiPath第一个案例Hello World

    1.弹出框Hello World 在弹出的窗口中创建序列 在新建的序列中,在搜索框中输入“Message Box”,Studio自动搜索出结果. 选中“Message Box”,然后拖拽到界面带+号区 ...

  7. Python 基础之 线程与进程

    Python 基础之 线程与进程 在前面已经接触过了,socket编程的基础知识,也通过socketserver 模块实现了并发,也就是多个客户端可以给服务器端发送消息,那接下来还有个问题,如何用多线 ...

  8. Deepin 下 使用 Rider 开发 .NET Core

    Deepin 下 使用 Rider 开发 .NET Core 国产的 Deepin 不错,安利一下. Deepin 用了也有一两年,也只是玩玩,没用在开发上面.后来 Win10 不太清真了,就想着能不 ...

  9. idea 常用功能

      Ctrl + E:打开最近文件   双击 Shift:按文件名查找文件   Ctrl + Shift + F:全局搜索   Alt + ~(数字 1 左边的键):commit.push 代码   ...

  10. C#语音特性 很实用

    1.隐式类型 Var 2.匿名类型 new{} 3.自动属性 [prop TAB]  public string Title { get; set; } 4.初始化器  var myObj1 = ne ...