栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA集成apple授权认证登录【后端认证授权】

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA集成apple授权认证登录【后端认证授权】

1、需求描述

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 --- 注册苹果账户的邮箱

        业务上没有要求做授权时间的控制,所以没有使用授权认证中的 过期时间

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/271431.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号