背景:
项目中app登录有第三方账号授权登录,在今年6月30号以后,苹果对发布的app中若有第三方账号授权,必须得接入苹果账号的授权登录的硬性要求。不然在发版时,会不通过审核
苹果授权流图
代码实现
maven的依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
通过RestTemplate发起请求获取苹果的公钥信息:
public PublicKey getPublicKey(String kid) {
logger.debug("get public key start,kid={}", kid);
try {
HeaderMap<String, Object> headerMap = new HeaderMap<>();
headerMap.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_UTF8_VALUE);
headerMap.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_UTF8_VALUE);
AppleKeyResponse appleKeyResponse = get(GET_KEY_URL, headerMap, new ResponseType<AppleKeyResponse>() {
});
logger.debug("get public key success,appleKeyResponse={}", appleKeyResponse);
if (null == appleKeyResponse || CollectionUtils.isEmpty(appleKeyResponse.getKeys())){
return null;
}
List<AppleKeyVo> appleKeyVos = appleKeyResponse.getKeys();
for (AppleKeyVo appleKeyVo : appleKeyVos) {
if (kid.equals(appleKeyVo.getKid())) {
BigInteger modulus = new BigInteger(1, Base64.decodeBase64(appleKeyVo.getN()));
BigInteger publicExponent = new BigInteger(1, Base64.decodeBase64(appleKeyVo.getE()));
RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
}
} catch (Exception e) {
logger.error("get public key fail,e={}", e);
}
return null;
}
注意
1、GET_KEY_URL =https://appleid.apple.com/auth/keys
2、get(GET_KEY_URL, headerMap, new ResponseType() {});这里的get方法,是公司封装RestTemplate的get请求方法。
3、获取到苹果公钥是一个数组,需要根据从前端传过来的identityToken中解析对应的kid(密钥id),如何解析,参考下面的解析identityToken代码
AppleKeyResponse 类
public class AppleKeyResponse {
private List<AppleKeyVo> keys;
public List<AppleKeyVo> getKeys() {
return keys;
}
public void setKeys(List<AppleKeyVo> keys) {
this.keys = keys;
}
@Override
public String toString() {
return "AppleKeyResponse{" +
"keys=" + keys +
'}';
}
AppleKeyVo 类
public class AppleKeyVo {
/**
* 加密算法
*/
private String kty;
/**
* 密钥id
*/
private String kid;
/**
* 用处
*/
private String use;
/**
* 算法
*/
private String alg;
/**
* 公钥参数
*/
private String n;
/**
* 公钥参数
*/
private String e;
public String getKty() {
return kty;
}
public void setKty(String kty) {
this.kty = kty;
}
public String getKid() {
return kid;
}
public void setKid(String kid) {
this.kid = kid;
}
public String getUse() {
return use;
}
public void setUse(String use) {
this.use = use;
}
public String getAlg() {
return alg;
}
public void setAlg(String alg) {
this.alg = alg;
}
public String getN() {
return n;
}
public void setN(String n) {
this.n = n;
}
public String getE() {
return e;
}
public void setE(String e) {
this.e = e;
}
@Override
public String toString() {
return "AppleKeyVo{" +
"kty='" + kty + '\'' +
", kid='" + kid + '\'' +
", use='" + use + '\'' +
", alg='" + alg + '\'' +
", n='" + n + '\'' +
", e='" + e + '\'' +
'}';
}
解析identityToken(是苹果账号生成的授权码)
/**
* 苹果工具类
*
* @author :
* @version : 1.0.0
* @date : 2020/7/13 17:10
*/
public class AppleUtil {
private static Logger logger = LoggerFactory.getLogger(AppleUtil.class);
private static final String AUTH_TIME = "auth_time";
private static final String APPLE_SERVER_ULR = "https://appleid.apple.com";
/*public static void main(String[] args) {
AppleTokenVo appleTokenVo = decodeIdentityToken("eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmNoYW5nZGFvLnR0c2Nob29sIiwiZXhwIjoxNTg5MjcwMzI3LCJpYXQiOjE1ODkyNjk3MjcsInN1YiI6IjAwMTk0MC43YTExNDFhYTAwMWM0NjllYTE1NjNjNmJhZTk5YzM3ZC4wMzA3IiwiY19oYXNoIjoienNIUW9xbTdjcDZOcmxrUHFhTmpGQSIsImVtYWlsIjoiYXEzMmsydnpjd0Bwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSIsImF1dGhfdGltZSI6MTU4OTI2OTcyNywibm9uY2Vfc3VwcG9ydGVkIjp0cnVlfQ.q5unOzswOjpRYmrVKVm3FRb_Th6kkhgEvoFfTEAIETwgTXZ7bYcQM8J8tCjkGGqtt2z74Z-wTW7Q3ia209xhmwrVDIup0jcQgNTvsCEMkfo9evPIDrNRNQw2Dzw2EBKma8004NL6THYlySoDnPRoW_VQCHP_m0HnjYuIc-wtREEClf-_tOFDPpTsvUFoETHNfhpsLhqj24-zm6MSOocYY3WbUaYJQVEFCz-x6AGko1XkMtms_-JU1xakNtjMZTIVj2XyUI5MO7_eo-D9i_c7Hj-OE9HNBEvFnPxOesDzXvEoYdb7uByXEfa-H1syJMecBMRa3tL76W_CYKsONRkU9Q");
System.out.println(appleTokenVo);
}*/
/**
* 获取publicKey 的算法id
*
* @param identityToken 苹果token的第一部分
* @return String
*/
public static String getKid(String identityToken) {
String kid = null;
try {
String str1 = new String(Base64.decodeBase64(identityToken), StandardCharsets.UTF_8);
Map<String, Object> data1 = JSONUtil.readValue(str1, new TypeReference<Map<String, Object>>() {
});
kid = (String) data1.get("kid");
} catch (Exception e) {
logger.error("get kid fail,e={}", e);
}
return kid;
}
/**
* 解密个人信息
*
* @param identityToken APP获取的identityToken的第二部分
* @return 解密参数:失败返回null sub就是用户id,用户昵称需要前端传过来
*/
public static AppleTokenVo getAppleUserInfo(String identityToken) {
AppleTokenVo appleTokenVo = null;
try {
String str2 = new String(Base64Utils.decodeFromString(identityToken), StandardCharsets.UTF_8);
appleTokenVo = JSONUtil.readValue(str2, AppleTokenVo.class);
} catch (Exception e) {
logger.info("get apple user information fail,e={} ", e);
}
return appleTokenVo;
}
/**
* 验证
*
* @param identityToken APP获取的identityToken
* @param aud 您在您的Apple Developer帐户中的client_id
* @param sub 用户的唯一标识符对应APP获取到的:user
* @return true/false
*/
public static boolean verifyIdentityToken(PublicKey publicKey, String identityToken, String aud, String sub) {
try {
JwtParser jwtParser = Jwts.parser().setSigningKey(publicKey);
jwtParser.requireIssuer(APPLE_SERVER_ULR);
jwtParser.requireAudience(aud);
jwtParser.requireSubject(sub);
Jws<Claims> claim = jwtParser.parseClaimsJws(identityToken);
if (claim != null && claim.getBody().containsKey(AUTH_TIME)) {
return true;
}
} catch (ExpiredJwtException e1) {
logger.error("apple token verify fail,identityToken is expired!");
} catch (Exception e2) {
logger.error("apple token verify fail,error={}", e2);
}
return false;
}
}
AppleTokenVo类(identityToken解析出来的信息):
/**
* 苹果用户token中信息
*
* @version : 1.0.0
* @date : 2020/7/17 15:03
*/
public class AppleTokenVo {
/**
* 签发机构网址
*/
private String iss;
/**
* bundle id
*/
private String aud;
/**
* 过期时间戳
*/
private Long exp;
/**
* 签发时间
*/
private Long iat;
/**
* user id
*/
private String sub;
/**
* 客户端发出请求时携带的随机串,用于对照
*/
private String nonce;
/**
* 邮箱
*/
private String email;
public String getIss() {
return iss;
}
public void setIss(String iss) {
this.iss = iss;
}
public String getAud() {
return aud;
}
public void setAud(String aud) {
this.aud = aud;
}
public Long getExp() {
return exp;
}
public void setExp(Long exp) {
this.exp = exp;
}
public Long getIat() {
return iat;
}
public void setIat(Long iat) {
this.iat = iat;
}
public String getSub() {
return sub;
}
public void setSub(String sub) {
this.sub = sub;
}
public String getNonce() {
return nonce;
}
public void setNonce(String nonce) {
this.nonce = nonce;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "AppleTokenVo{" +
"iss='" + iss + '\'' +
", aud='" + aud + '\'' +
", exp=" + exp +
", iat=" + iat +
", sub='" + sub + '\'' +
", nonce='" + nonce + '\'' +
", email='" + email + '\'' +
'}';
}
}
总结:
苹果授权登录,代码大概就以上这些,除了公司封装RestTemplate,其他都有了。