Sign in with Apple(基于JWT的算法验证)

苹果第三方登录时序图.jpg

下面是应用服务器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认证失败的时候发起一次公钥请求,更新公钥,解决这个问题。
发布了8 篇原创文章 · 获赞 0 · 访问量 3302

猜你喜欢

转载自blog.csdn.net/u012886301/article/details/104824353