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認證失敗的時候發起一次公鑰請求,更新公鑰,解決這個問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章