下面是应用服务器token认证后端代码
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.Map;
@Component
public class SignInWithAppleHelper {
private static JSONArray keysJsonArray = null;
/**
* 解密个人信息
*
* @param identityToken APP获取的identityToken
* @return 解密参数:失败返回null
*/
public Map<String, Object> verify(String identityToken) {
String sub = "";
boolean result = false;
Map<String, Object> data1 = null;
try {
String[] identityTokens = identityToken.split("\\.");
Map<String, Object> data0 = JSONObject.parseObject(new String(Base64.decodeBase64(identityTokens[0]), "UTF-8"));
data1 = JSONObject.parseObject(new String(Base64.decodeBase64(identityTokens[1]), "UTF-8"));
String aud = (String) data1.get("aud");
sub = (String) data1.get("sub");
String kid = (String) data0.get("kid");
result = verify(identityToken, aud, sub, kid);
} catch (ExpiredJwtException e) {
// throw new PassportException(APIResultStatus.UC_EXPRIED_JWT.getCode(), APIResultStatus.UC_EXPRIED_JWT.getMsg());
} catch (Exception e) {
if (e instanceof SignatureException) {
updateAppleKeys();
}
e.printStackTrace();
// throw new PassportException(APIResultStatus.UC_VERIFY_FAIL.getCode(), APIResultStatus.UC_VERIFY_FAIL.getMsg());
}
if (!result) {
return null;
}
return data1;
}
/**
* 验证
*
* @param identityToken APP获取的identityToken
* @param aud 您在您的Apple Developer帐户中的client_id
* @param sub 用户的唯一标识符对应APP获取到的:user
* @return true/false
*/
public boolean verify(String identityToken, String aud, String sub, String kid) {
PublicKey publicKey = getPublicKey(kid);
JwtParser jwtParser = Jwts.parser().setSigningKey(publicKey);
jwtParser.requireIssuer("https://appleid.apple.com");
jwtParser.requireAudience(aud);
jwtParser.requireSubject(sub);
Jws<Claims> claim = jwtParser.parseClaimsJws(identityToken);
if (claim != null && claim.getBody().containsKey("auth_time")) {
return true;
}
return false;
}
/**
*
* @return 构造好的公钥
*/
public PublicKey getPublicKey(String kid) {
try {
if (keysJsonArray == null || keysJsonArray.size() == 0) {
updateAppleKeys();
}
String n = "";
String e = "";
for (int i = 0; i < keysJsonArray.size(); i++) {
JSONObject jsonObject = keysJsonArray.getJSONObject(i);
if (StringUtils.equals(jsonObject.getString("kid"), kid)) {
n = jsonObject.getString("n");
e = jsonObject.getString("e");
}
}
final BigInteger modulus = new BigInteger(1, Base64.decodeBase64(n));
final BigInteger publicExponent = new BigInteger(1, Base64.decodeBase64(e));
final RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
final KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private void updateAppleKeys() {
RestTemplate restTemplate = new RestTemplate();
String forObject = restTemplate.getForObject("https://appleid.apple.com/auth/keys", String.class);
System.out.println(forObject);
if (StringUtils.isEmpty(forObject)) {
return;
}
JSONObject data = JSONObject.parseObject(forObject);
keysJsonArray = data.getJSONArray("keys");
}
}
需要注意的点:
- token默认的过期时间是五分钟
- 应用服务器请求苹果服务器获取公钥可以做个优化。
因为苹果认证服务器接口不稳定,响应速度时快时慢,所以我们可以在第一次token认证的时候请求获取公钥,然后存到应用服务器内存,不必每次token认证都请求一次,这样可以提高应用服务器接口的响应速度。这个优化有个弊端就是苹果公钥可能变化(不可能变化很快),可以在token认证失败的时候发起一次公钥请求,更新公钥,解决这个问题。