APP需要提交苹果的App Store审核时,因为集成了微信授权登录,导致审核失败了,审核失败的理由是:集成了第三方授权的APP,也需要集成Apple授权认证,不然审核通过不了,强制捆绑销售了呀。没办法,胳膊拧不过大腿,还是老老实实的去加了Apple授权认证了。
2、APP授权认证请参考官方文档:
使用 Apple 登录实现用户身份验证 :
Apple Developer documentation
在您的应用程序中使用 Apple 按钮显示登录 :
Apple Developer documentation
实现的效果:
登录界面展示苹果授权登录的按钮,点击授权登录后,输入密码或者指纹授权后,获取 identityToken ---
一个 JSON 网络令牌 (JWT),可安全地将有关用户的信息传达给应用程序
3、后端实现授权认证登录(基于identityToken的方式)认证的设计流图:
引入maven依赖包:
io.jsonwebtoken
jjwt
0.9.1
登录验证参数设置:
import java.io.Serializable;
import javax.validation.constraints.NotEmpty;
import org.apache.commons.lang3.builder.ToStringBuilder;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel("apple授权登录DTO")
public class AppleLoginDTO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "apple授权登录码", required = true)
@NotEmpty(message = "apple授权登录码不能为空")
private String identityToken;
public String getIdentityToken() {
return identityToken;
}
public void setIdentityToken(String identityToken) {
this.identityToken = identityToken;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
参数配置:
#apple登录配置
apple:
auth:
url: https://appleid.apple.com/auth/keys
iss:
url: https://appleid.apple.com
后端实现的授权认证逻辑:
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.Objects;
import org.apache.commons.codec.binary.base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.sisensing.cgm.common.core.domain.TokenUserInfo;
import com.sisensing.cgm.common.core.web.domain.ResponseResult;
import com.sisensing.cgm.common.dto.user.po.AppUserTokenPO;
import com.sisensing.cgm.user.api.domain.dto.AppleLoginDTO;
import com.sisensing.cgm.user.service.IAppleService;
import io.jsonwebtoken.*;
@Service
public class AppleServiceImpl implements IAppleService {
private static final Logger LOGGER = LoggerFactory.getLogger(AppleServiceImpl.class);
@Value("${apple.auth.url}")
private String appleAuthUrl;
@Value("${apple.iss.url}")
private String appleIssUrl;
@Override
public ResponseResult appleLogin(AppleLoginDTO appleLoginDTO) {
String identityToken = appleLoginDTO.getIdentityToken();
System.out.println("in ");
// 获取秘钥的返回信息
String firstDate = null;
String claim = null;
try {
firstDate = new String(base64.decodebase64(identityToken.split("\.")[0]), "UTF-8");
claim = new String(base64.decodebase64(identityToken.split("\.")[1]), "UTF-8");
} catch (UnsupportedEncodingException e) {
LOGGER.error("apple授权码异常,identityToken[%s], %s", identityToken, e);
}
// 开发者帐户中获取的 10 个字符的标识符密钥
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 = this.getPublicKey(kid);
if (Objects.isNull(publicKey)) {
throw new RuntimeException("apple授权登录的数据异常");
}
boolean reuslt = this.verifyAppleLoginCode(publicKey, identityToken, aud, sub);
if (reuslt) {
}
return null;
}
private boolean verifyAppleLoginCode(PublicKey publicKey, String identityToken, String aud, String sub) {
boolean result = false;
JwtParser jwtParser = Jwts.parser().setSigningKey(publicKey);
jwtParser.requireIssuer(appleIssUrl);
jwtParser.requireAudience(aud);
jwtParser.requireSubject(sub);
try {
Jws claim = jwtParser.parseClaimsJws(identityToken);
if (claim != null && claim.getBody().containsKey("auth_time")) {
result = true;
}
} catch (ExpiredJwtException e) {
throw new RuntimeException(String.format("apple登录授权identityToken过期.", e));
} catch (SignatureException e) {
throw new RuntimeException(String.format("apple登录授权identityToken非法.", e));
}
return result;
}
private PublicKey getPublicKey(String kid) {
try {
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
URIBuilder uriBuilder = new URIBuilder(appleAuthUrl);
HttpGet httpGet = new HttpGet(uriBuilder.build());
HttpResponse response = httpclient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
HttpEntity responseEntity = response.getEntity();
String result = EntityUtils.toString(responseEntity, "UTF-8");
if (statusCode != HttpStatus.SC_OK) { // code = 200
throw new RuntimeException(String.format("接口请求失败,url[%s], result[%s]", appleAuthUrl, result));
}
// 请求成功
JSonObject content = JSONObject.parseObject(result);
String keys = content.getString("keys");
JSonArray keysArray = JSONObject.parseArray(keys);
if (keysArray.isEmpty()) {
return null;
}
for (Object key : keysArray) {
JSonObject keyJsonObject = (JSONObject)key;
if (kid.equals(keyJsonObject.getString("kid"))) {
String n = keyJsonObject.getString("n");
String e = keyJsonObject.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 ex) {
LOGGER.error("获取PublicKey异常.", ex);
}
return null;
}
}
ps: 认证授权的代码省略了,具体实现不复杂,认证授权通过后:
if (claim != null && claim.getBody().containsKey("auth_time")) {
result = true;
}
就可以去实现系统的业务授权流程,比如首次注册用户的判断呀,或者已经注册的用户,直接返回登录的token等等
4、断点验证截图 5、总结Apple授权认证的调试过程中,遇到了不少问题,苹果的官方文档对接口的返回字段解释的不太清楚,比如:sub字段,官方文档翻译成中文是:受众注册声明标识了身份令牌所针对的接收者,没有详细说明该值的作用,以及是否会改变,所以暂时当成保留字段来使用;
App获取的 let userIdentifier = appleIDCredential.user 为空,所以后端认证通过后,若是Apple首次授权登录,还需要将appleId返回给App开发,appleID --- 注册苹果账户的邮箱
业务上没有要求做授权时间的控制,所以没有使用授权认证中的 过期时间



