zoukankan      html  css  js  c++  java
  • sign in with apple后端校验(java)

      最近新开发的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”,获得本文最新内容。

                                                              

  • 相关阅读:
    【SDOI2014】数表
    【洛谷P4735】最大异或和
    FFT学习笔记
    【SHOI2008】堵塞的交通
    HDU 1754 I Hate It 线段树
    hdu 1166 敌兵布阵 ( 线段树或者树状数组)
    hdu 5339 Untitled dfs
    The mook jong
    hdu 5363 Key Set 快速幂
    HDU 1983 Kaitou Kid
  • 原文地址:https://www.cnblogs.com/ismallboy/p/11960958.html
Copyright © 2011-2022 走看看