1.背景
公司开发的app上线许久,支持三方登录(微信、qq)。由于不久前苹果官方新政策,所以要也要接入appleId登录。
这块由我来负责Sign in with Apple 服务端校验(服务端由java开发)。所以本人也是从零开始在网上先从苹果官方网站上找一下接入流程,不过很遗憾苹果官方的文档看不出个所以然来,连个例子都没有。就只能查找国内有过相关经验的小伙伴写的,关于apple 验证登录的技术文章。通过国内小伙伴的技术分享,本人也顺利完成该功能,下面总结一下以便能帮助到其他小伙伴。
2.App端授权登陆方式
针对App端授权登陆,提供两种后端验证方式:
1.基于JWT的算法验证。
2.是基于授权码的验证。
本文主要讲JWT这种验证方式。
3.校验流程图
以下流程图直接引用以下技术文章的流程图
imagehttps://www.cnblogs.com/0xa6a/p/12330069.html?utm_source=tuicool
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 集成苹果登录
网友评论