一、apple配置
1、注册开发者
在apple开发者官网( https://developer.apple.com/)注册成开发者
2、创建应用
- 点击左边菜单的Certificates, Identifiers & Profiles
- 点击左边的菜单的Identifiers 创建应用(记得开通苹果登录)
- 记下创建应用的包名(apple clientId)
3、获取apple teamId
- 右上角的账号名后面的编号即apple TeamId
4、获取密钥id和jwt密钥
- 在2步骤中的页面点击 keys
- 创建新的密钥
- 注册密钥成功后即可获得apple kid 和apple jwt key(只能下载一次,文件为.p8格式)
5、记录apple clientId、teamId、kid、jwtKey 后续开发需要使用到
二、apple授权登录时序图
三、apple授权登录相关官方文档
1、官方文档地址
https://developer.apple.com/documentation/sign_in_with_apple
2、获取公钥地址
- 获取公钥地址的文档地址https://developer.apple.com/documentation/sign_in_with_apple/fetch_apple_s_public_key_for_verifying_token_signature
- 接口地址
https://appleid.apple.com/auth/keys - 注意事项
接口返回的密钥值是固定的,可以适当缓存使用
3、校验token地址
- 校验token的文档地址https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
- 接口地址
https://appleid.apple.com/auth/token
四、注意事项
apple登录只有在第一次授权的时候可以获取到用户信息
五、参考代码
- 获取apple公钥列表
String GET_PUBLIC_KEY = "https://appleid.apple.com/auth/keys";
ResponseEntity<String> getApplePublicKeyResp = restTemplate.getForEntity(GET_PUBLIC_KEY, String.class);
String body = getApplePublicKeyResp.getBody();
JSONObject resultNode =JSONObject.parseObject(body);
JSONArray keys = resultNode.getJSONArray("keys");
Map<String, String> applePublicKeyMap = new HashMap<>(4);
for (int i=0;i<keys.size();i++) {
JSONObject jsonObject = keys.getJSONObject(i);
String kid = jsonObject.getString("kid");
applePublicKeyMap.put(kid , jsonObject.toString);
}
- 生成客户端密钥
/**
* 生成客户端密钥,这个密钥有效期为半年
*
* @return 密钥
*/
@SneakyThrows
private String getClientSecret() {
String kid="apple kid";
String teamId ="apple teamId";
String clientId= "apple clientId";
String clientSecret = Jwts.builder()
.setHeaderParam(JwsHeader.ALGORITHM, "ES256")
.setHeaderParam(JwsHeader.KEY_ID, kid)
.setIssuer(teamId)
.setAudience("https://appleid.apple.com")
.setSubject(clientId)
.setExpiration(new Date(System.currentTimeMillis()+180*24*60*60*1000L))
.setIssuedAt(new Date(System.currentTimeMillis()))
.signWith(SignatureAlgorithm.ES256, getJwtEncryptKey())
.compact();
return clientSecret;
}
```java
/**
* 生成jwt密钥
*
* @return jwt密钥
*/
@SneakyThrows
private PrivateKey getJwtEncryptKey() {
String jwtEncryptKey ="apple jwtKey";
PEMParser pemParser = new PEMParser(new StringReader(jwtEncryptKey));
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
PrivateKeyInfo object = (PrivateKeyInfo) pemParser.readObject();
return converter.getPrivateKey(object);
}
- 校验token
String CHECK_TOKEN_URL = "https://appleid.apple.com/auth/token";
HttpHeaders reqHeaders = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/x-www-form-urlencoded");
reqHeaders.setContentType(type);
MultiValueMap<String, Object> requestMap = new LinkedMultiValueMap<>(5);
requestMap.add("client_id", clientId);
requestMap.add("client_secret", getClientSecret());
requestMap.add("code", token);
requestMap.add("grant_type", "authorization_code");
requestMap.add("redirect_uri", null);
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(requestMap, reqHeaders);
ResponseEntity<String> checkResponseEntity = restTemplate.postForEntity(CHECK_TOKEN_URL, request, String.class);
String body = checkResponseEntity.getBody();
JSONObject resultNode =JSONObject.parseObject(body);
Optional<String> idToken = Optional.ofNullable(resultNode ).map(it -> it.getString("id_token"));
Claims claims =getClaims(idToken.get());
String sub = claims.getSubject();
if (!unionId.equals(sub)) {
log.error("用户传递的唯一标识非法,unionId is {},request userId is {}", unionId, sub);
}
- 解析校验token中的值
private Claims getClaims (String idToken){
String jwtHeaderEncoded = idToken.split("\\.")[0];
String jwtHeader = new String(Base64.decodeBase64(jwtHeaderEncoded));
JSONObject jwtHeaderNode =JSONObject.parseObject(jwtHeader);
ApplePublicKey applePublicKey = applePublicKeyMap(jwtHeaderNode.getString("kid"));
PublicKey publicKey = generatePublicKey(applePublicKey.getN(), applePublicKey.getE());
return Jwts.parser().setSigningKey(publicKey)
.parseClaimsJws(idToken)
.getBody();
}
private PublicKey generatePublicKey(String n, String e) {
BigInteger modulus = new BigInteger(1, Base64.decodeBase64(n));
BigInteger publicExponent = new BigInteger(1, Base64.decodeBase64(e));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
return keyFactory.generatePublic(spec);
}