苹果apple账号授权登录第三方APP 您所在的位置:网站首页 旅游用品有限公司 苹果apple账号授权登录第三方APP

苹果apple账号授权登录第三方APP

2023-10-18 06:21| 来源: 网络整理| 查看: 265

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

一:获取苹果公钥

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

二:验证JWT 引入JWT所需Maven包 com.auth0 java-jwt 3.8.3 com.auth0 jwks-rsa 0.12.0 io.jsonwebtoken jjwt 0.9.0 @Autowired protected RestTemplate restTemplate; /** * identityToken授权判断 * 这里代码只展示验证整个过程,具体其他业务得结合自己的公司去实现 */ public Response appleLoginVerify(String fullName,String identityToken) throws Exception { log.info("登录接受参数----->fullName:" + fullName);//这里要求传fullName是因为苹果在第一次登陆的时候才会返回fullName,而且在验证通过后还获取不到fullName,邮箱的话在验证通过后可以获取到,所以不需要App传邮箱 if (!verify(identityToken)) {//验证identityToken return 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 arr = getAuthKeys(); if (arr == null) { return false; } try { //先取jwt中的header来匹配苹果公钥的kid String header = new String(Base64.decodeBase64(jwt.split("\\.")[0])); ObjectMapper objectMapper = new ObjectMapper(); ApplePublicKeyVo applePublicKeyVo = objectMapper.readValue(header, ApplePublicKeyVo.class); Optional 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 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 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() { }); } 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; } @Override public 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); } @Override public int hashCode() { return Objects.hash(kty, kid, use, alg, n, e); } }

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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有