Java对接苹果账号授权登录

背景:

项目中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,其他都有了。

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值