美文网首页
Sign in with Apple (Java后端校验总结)

Sign in with Apple (Java后端校验总结)

作者: 小左_108c | 来源:发表于2020-06-18 10:56 被阅读0次

1.背景

公司开发的app上线许久,支持三方登录(微信、qq)。由于不久前苹果官方新政策,所以要也要接入appleId登录。
这块由我来负责Sign in with Apple 服务端校验(服务端由java开发)。所以本人也是从零开始在网上先从苹果官方网站上找一下接入流程,不过很遗憾苹果官方的文档看不出个所以然来,连个例子都没有。就只能查找国内有过相关经验的小伙伴写的,关于apple 验证登录的技术文章。通过国内小伙伴的技术分享,本人也顺利完成该功能,下面总结一下以便能帮助到其他小伙伴。

2.App端授权登陆方式

针对App端授权登陆,提供两种后端验证方式:

     1.基于JWT的算法验证。

      2.是基于授权码的验证。

 本文主要讲JWT这种验证方式。

3.校验流程图

以下流程图直接引用以下技术文章的流程图

https://www.cnblogs.com/0xa6a/p/12330069.html?utm_source=tuicool

image

4.相关说明

请查看该文章下的相关说明里面有一些概念 请自己弄明白

https://www.cnblogs.com/0xa6a/p/12330069.html?utm_source=tuicool

5.代码实现

下面的实现流程完全按照图图3.1实现的

5.1 maven pom引用

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

5.2 java代码实现

方法入口

/**
 * 苹果登录校验
 * 
 * @param identityToken
 * @return
 */
public boolean verify(String identityToken) {
    try {
        if (identityToken.split("\\.").length > 1) {
            String firstDate = new String(Base64.decodeBase64(identityToken.split("\\.")[0]), "UTF-8");
            String claim = new String(Base64.decodeBase64(identityToken.split("\\.")[1]), "UTF-8");
            String kid = JSONObject.parseObject(firstDate).get("kid").toString();
            String aud = JSONObject.parseObject(claim).get("aud").toString();
            String sub = JSONObject.parseObject(claim).get("sub").toString();
            PublicKey publicKey = getPublicKey(kid);
            if (publicKey == null) {
                return false;
            }
            boolean reuslt = verify(publicKey, identityToken, aud, sub);
            if (reuslt) {
                logger.info("苹果登录授权成功!");
                return true;
            }
        }
    } catch (Exception e) {
        logger.error("苹果登录授权异常!  {}", e.getMessage());
        e.printStackTrace();
    }
    return false;
}

校验方法

其中的ISS为 https://appleid.apple.com 可以通过
String iss= JSONObject.parseObject(claim).get("iss").toString() 获得。

private boolean verify(PublicKey key, String jwt, String audience, String subject) throws Exception {
    boolean result = false;
    JwtParser jwtParser = Jwts.parser().setSigningKey(key);
    jwtParser.requireIssuer(ISS);
    jwtParser.requireAudience(audience);
    jwtParser.requireSubject(subject);
    try {
        Jws<Claims> claim = jwtParser.parseClaimsJws(jwt);
        if (claim != null && claim.getBody().containsKey("auth_time")) {
            return true;
        }
    } catch (ExpiredJwtException e) {
        throw new BusinessException("苹果identityToken过期", e.getMessage());
    } catch (SignatureException e) {
        throw new BusinessException("苹果identityToken非法", e.getMessage());
    }
    return result;
}

获取PublicKey方法

其中APPLE_AUTH_URL为https://appleid.apple.com/auth/keys 里面的kid是很关键的一个参数
因为通过https://appleid.apple.com/auth/keys获取到的keys 是一个数组有多个key,
必须得通过header解密出来的kid 来筛选到对应的key。
入口方法中的
String firstDate = new String(Base64.decodeBase64(identityToken.split("\.")[0]), "UTF-8");
String kid = JSONObject.parseObject(firstDate).get("kid").toString();
就是来获取kid的 这样来获取PublicKey 能避免
o.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted 问题

/**
     * 该获取PublicKey方法解决   io.jsonwebtoken.SignatureException: JWT signature does not
     * match locally computed signature. JWT validity cannot be asserted and should
     * not be trusted 问题
     * 
     * @param kid
     * @return
     */
    private PublicKey getPublicKey(String kid) {
        try {
            String str = HTTPWeb.get(APPLE_AUTH_URL, new HashMap<String, String>());
            JSONObject data = JSONObject.parseObject(str);
            JSONArray jsonArray = data.getJSONArray("keys");
            if (jsonArray.isEmpty()) {
                return null;
            }
            for (Object object : jsonArray) {
                JSONObject json = ((JSONObject) object);
                if (json.getString("kid").equals(kid)) {
                    String n = json.getString("n");
                    String e = json.getString("e");
                    BigInteger modulus = new BigInteger(1, Base64.decodeBase64(n));
                    BigInteger publicExponent = new BigInteger(1, Base64.decodeBase64(e));
                    RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
                    KeyFactory kf = KeyFactory.getInstance("RSA");
                    return kf.generatePublic(spec);
                }
            }
        } catch (Exception e) {
            logger.error("getPublicKey异常!  {}", e.getMessage());
            e.printStackTrace();
        }
        return null;

    }

HTTPWeb中的get方法

public static String get(String url , Map<String, ? extends Object> data){
        logger.info("请求地址为:"+url);
        CloseableHttpClient client = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet();
        try {
            URIBuilder uriBuilder = new URIBuilder(url);
            if(data != null){
                for (String key : data.keySet()) {
                    uriBuilder.setParameter(key, data.get(key).toString());
                }
            }
            RequestConfig config = RequestConfig.custom()
                    .setConnectTimeout(1000)
                    .setConnectionRequestTimeout(2000)
                    .build();
            httpGet.setConfig(config);
            httpGet.setURI(uriBuilder.build());
            CloseableHttpResponse response = client.execute(httpGet);
            HttpEntity entity = response.getEntity();
            if(entity == null) {
                return null;
            }
            BufferedReader reader = new BufferedReader((new InputStreamReader(entity.getContent(),"utf-8")));
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ( (str = reader.readLine()) != null ) {
                buffer.append(str);
            }
            response.close();
            return buffer.toString();
        } catch (URISyntaxException e) {
            logger.warn("..URL:"+url, e);
        } catch (ClientProtocolException e) {
            logger.warn("..URL:"+url, e);
        } catch (IOException e) {
            logger.warn("..URL:"+url, e);
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                logger.warn("",e);
            }
        }
        return null;
    }

6.总结及鸣谢

本人按照上述总结流程,已顺利完成,Sign in with Apple Java后端校验。

本人能力有限,上面的总结可能有漏洞,如有问题请指正。

另外感谢分享Sign in with Apple 相关技术文章的小伙伴

下面几篇文章对我很有参考意义:

Sign in with Apple 流程总结 - 0xa6a - 博客园

Java后端校验Sign in With Apple (苹果APP授权登录)_xunkoo的博客-CSDN博客_java 集成苹果登录

Sign in with Apple(苹果授权登陆)水不可追-CSDN博客苹果登录

相关文章

网友评论

      本文标题:Sign in with Apple (Java后端校验总结)

      本文链接:https://www.haomeiwen.com/subject/wvwyxktx.html