@Value("${app.apple.login.key-id}")
private String keyId;
@Value("${app.apple.login.private-key}")
private String privateKey;
@Value("${app.apple.login.team-id}")
private String teamId;
@Value("${app.apple.login.client-id}")
private String clientId;
private static final String AuthUrl = "https://appleid.apple.com/auth/token";
@Autowired
private AccountManager accountManager;
@Override
public AccountThirdBO process(LoginRequest req) {
if (StringUtils.isEmpty(req.getCode())){
throw new BusinessException(ResultEnum.PARA_ERR);
}
String provider = AccountConstant.ProviderEnum.apple.toString();
AuthRsp rsp = authCode(req.getCode(), keyId, privateKey, teamId, clientId);
String thirdId = parseThirdId(rsp);
AccountIndex accountIndex = accountManager.getAccountIndex(provider, thirdId);
boolean unregistered = accountIndex == null;
if (!unregistered) {
accountManager.checkPhoneValidation(accountIndex.getUid());
}
List<ThirdInfo> thirdInfoList = new ArrayList<>();
thirdInfoList.add(new ThirdInfo(provider, thirdId, thirdId, ""));
return new AccountThirdBO(thirdInfoList,
accountIndex == null ? null : accountIndex.getUid(),
unregistered,
false,
unregistered);
}
private AuthRsp authCode(String code, String keyId, String privateKey, String teamId, String clientId){
String clientSecret = ClientSecretGenerator.generate(keyId, privateKey, teamId, clientId);
if (StringUtils.isEmpty(clientSecret)){
throw new BusinessException(ResultEnum.ACCOUNT_LOGIN_AUTH_FAILED);
}
Map<String, String> header = new HashMap<>();
header.put("Content-Type", "application/x-www-form-urlencoded");
Map<String, String> req = new HashMap<>();
req.put("client_id", clientId);
req.put("client_secret", clientSecret);
req.put("code", code);
req.put("grant_type", "authorization_code");
String rspBody = HttpInvokeUtil.httpPost(AuthUrl, req, EncodeUtil.UTF8_ENCODE, header);
if (StringUtils.isEmpty(rspBody) || rspBody.contains("error")){
throw new BusinessException(ResultEnum.ACCOUNT_LOGIN_AUTH_FAILED);
}
AuthRsp rsp = JSONObject.parseObject(rspBody, AuthRsp.class);
if (rsp == null || StringUtils.isEmpty(rsp.getIdToken())){
throw new BusinessException(ResultEnum.ACCOUNT_LOGIN_AUTH_FAILED);
}
return rsp;
}
private String parseThirdId(AuthRsp rsp){
String[] idTokens = rsp.getIdToken().split("\\.");
if (idTokens.length < 2){
throw new BusinessException(ResultEnum.ACCOUNT_LOGIN_AUTH_FAILED);
}
// 去掉签名
String unsignedIdToken = idTokens[0] + "." + idTokens[1] + ".";
Jwt<Header, Claims> jwt = Jwts.parserBuilder().build().parseClaimsJwt(unsignedIdToken);
Claims claims = jwt.getBody();
if (claims == null){
throw new BusinessException(ResultEnum.ACCOUNT_LOGIN_AUTH_FAILED);
}
String thirdId = (String)claims.get("sub");
if (StringUtils.isEmpty(thirdId)){
throw new BusinessException(ResultEnum.ACCOUNT_LOGIN_AUTH_FAILED);
}
return thirdId;
}
public static class ClientSecretGenerator{
private ClientSecretGenerator(){}
public static String generate(String keyId, String privateKey, String teamId, String clientId) {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey.getBytes(Charsets.UTF_8)));
try {
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PrivateKey key = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
return Jwts.builder()
.setHeader(genHeader(keyId))
.setClaims(genClaims(teamId, clientId))
.signWith(key, SignatureAlgorithm.ES256)
.compact();
}catch (NoSuchAlgorithmException e){
}catch (InvalidKeySpecException e){
}
return null;
}
private static Map<String, Object> genHeader(String keyId){
Map<String, Object> headerMap = new HashMap<>();
headerMap.put("alg", SignatureAlgorithm.ES256);
headerMap.put("kid", keyId);
return headerMap;
}
private static Map<String, Object> genClaims(String teamId, String clientId){
Date now = new Date();
Date atTime = TimeUtils.plusDays(now, -2);
Date expireTime = TimeUtils.plusDays(now, 2);
Map<String, Object> claimMap = new HashMap<>();
claimMap.put("iss", teamId);
claimMap.put("iat", atTime.getTime() / 1000);
claimMap.put("exp", expireTime.getTime() / 1000);
claimMap.put("aud", "https://appleid.apple.com");
claimMap.put("sub", clientId);
return claimMap;
}
}
public static class AuthRsp{
@JSONField(name = "access_token")
private String accessToken;
@JSONField(name = "expires_in")
private Long expiresIn;
@JSONField(name = "refresh_token")
private String refreshToken;
@JSONField(name = "id_token")
private String idToken;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public Long getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(Long expiresIn) {
this.expiresIn = expiresIn;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getIdToken() {
return idToken;
}
public void setIdToken(String idToken) {
this.idToken = idToken;
}
}