|
|
@@ -17,6 +17,7 @@ import org.springframework.util.MultiValueMap;
|
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
|
|
|
import java.io.*;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.security.Key;
|
|
|
import java.security.KeyFactory;
|
|
|
import java.security.PublicKey;
|
|
|
@@ -67,95 +68,91 @@ public class AppleUtil {
|
|
|
public void set_APPLE_BUNDLE_IDENTIFIER(String BUNDLE_IDENTIFIER) {
|
|
|
AppleUtil.APPLE_BUNDLE_IDENTIFIER = BUNDLE_IDENTIFIER;
|
|
|
}
|
|
|
+
|
|
|
/**
|
|
|
- * 获取苹果的公钥
|
|
|
- * @return
|
|
|
- * @throws Exception
|
|
|
+ * 解码identityToken
|
|
|
+ * 对前端传来的JWT字符串identityToken的第二部分进行解码
|
|
|
+ * 主要获取其中的aud和sub,aud大概对应ios前端的包名,sub大概对应当前用户的授权的openID
|
|
|
+ * @param identityToken
|
|
|
+ * @return {"aud":"com.xkj.****","sub":"000***.8da764d3f9e34d2183e8da08a1057***.0***","c_hash":"UsKAuEoI-****","email_verified":"true","auth_time":1574673481,"iss":"https://appleid.apple.com","exp":1574674081,"iat":1574673481,"email":"****@qq.com"}
|
|
|
*/
|
|
|
- private static JSONArray getAuthKeys() throws Exception {
|
|
|
-// String url = "https://appleid.apple.com/auth/keys";
|
|
|
- RestTemplate restTemplate = new RestTemplate();
|
|
|
- JSONObject json = restTemplate.getForObject(APPLE_KEYS_URL,JSONObject.class);
|
|
|
- JSONArray arr = json.getJSONArray("keys");
|
|
|
- return arr;
|
|
|
+ public static JSONObject parserIdentityToken(String identityToken) {
|
|
|
+ String[] arr = identityToken.split("\\.");
|
|
|
+
|
|
|
+ String firstDate = new String(Base64.decodeBase64(arr[0]), StandardCharsets.UTF_8);
|
|
|
+ String decode = new String(Base64.decodeBase64(arr[1]), StandardCharsets.UTF_8);
|
|
|
+ JSONObject claimObj = JSON.parseObject(decode);
|
|
|
+ // 将第一部分获取到的kid放入消息体中,方便后续匹配对应的公钥使用
|
|
|
+ claimObj.put("kid", JSONObject.parseObject(firstDate).get("kid"));
|
|
|
+ return claimObj;
|
|
|
}
|
|
|
|
|
|
- public static Boolean verify(String jwt) throws Exception{
|
|
|
- JSONArray arr = getAuthKeys();
|
|
|
- if(arr == null){
|
|
|
- return false;
|
|
|
- }
|
|
|
- //先取苹果第一个key进行校验
|
|
|
- JSONObject authKey = arr.getJSONObject(0);
|
|
|
- if(verifyExc(jwt, authKey)){
|
|
|
- return true;
|
|
|
- }else{
|
|
|
- //再取第二个key校验
|
|
|
- authKey = arr.getJSONObject(1);
|
|
|
- return verifyExc(jwt, authKey);
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * 根据kid获取对应的苹果公钥
|
|
|
+ * @param kid
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public static PublicKey getPublicKey(String kid) {
|
|
|
+ try {
|
|
|
+ RestTemplate restTemplate = new RestTemplate();
|
|
|
+ JSONObject data = restTemplate.getForObject(APPLE_KEYS_URL, JSONObject.class);
|
|
|
+ assert data != null;
|
|
|
+ JSONArray jsonArray = data.getJSONArray("keys");
|
|
|
+ for (Object obj : jsonArray) {
|
|
|
+ Map json = ((Map) obj);
|
|
|
+ // 获取kid对应的公钥
|
|
|
+ if (json.get("kid").equals(kid)) {
|
|
|
+ Jwk jwa = Jwk.fromValues(json);
|
|
|
+ return jwa.getPublicKey();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
|
+ }
|
|
|
+ return null;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 对前端传来的identityToken进行验证
|
|
|
- * @param jwt 对应前端传来的 identityToken
|
|
|
- * @param authKey 苹果的公钥 authKey
|
|
|
+ *
|
|
|
+ * @param identityToken
|
|
|
+ * @param jsonObject
|
|
|
* @return
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- public static Boolean verifyExc(String jwt, JSONObject authKey) throws Exception {
|
|
|
-
|
|
|
- Jwk jwa = Jwk.fromValues(authKey);
|
|
|
- PublicKey publicKey = jwa.getPublicKey();
|
|
|
+ public static Boolean verifyExc(String identityToken, JSONObject jsonObject) throws Exception {
|
|
|
+ String kid = (String) jsonObject.get("kid");
|
|
|
+ PublicKey publicKey = getPublicKey(kid);
|
|
|
|
|
|
- String aud = "";
|
|
|
- String sub = "";
|
|
|
- if (jwt.split("\\.").length > 1) {
|
|
|
- String claim = new String(Base64.decodeBase64(jwt.split("\\.")[1]));
|
|
|
- aud = JSONObject.parseObject(claim).get("aud").toString();
|
|
|
- sub = JSONObject.parseObject(claim).get("sub").toString();
|
|
|
- }
|
|
|
JwtParser jwtParser = Jwts.parser().setSigningKey(publicKey);
|
|
|
jwtParser.requireIssuer("https://appleid.apple.com");
|
|
|
- jwtParser.requireAudience(aud);
|
|
|
- jwtParser.requireSubject(sub);
|
|
|
-
|
|
|
+ jwtParser.requireAudience((String) jsonObject.get("aud"));
|
|
|
+ jwtParser.requireSubject((String) jsonObject.get("sub"));
|
|
|
try {
|
|
|
- Jws<Claims> claim = jwtParser.parseClaimsJws(jwt);
|
|
|
+ Jws<Claims> claim = jwtParser.parseClaimsJws(identityToken);
|
|
|
if (claim != null && claim.getBody().containsKey("auth_time")) {
|
|
|
- System.out.println(claim);
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
} catch (ExpiredJwtException e) {
|
|
|
-// log.error("apple identityToken expired", e);
|
|
|
return false;
|
|
|
} catch (Exception e) {
|
|
|
-// log.error("apple identityToken illegal", e);
|
|
|
return false;
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
|
|
|
+ public static Boolean verify(String identityToken) throws Exception{
|
|
|
|
|
|
-
|
|
|
- /**
|
|
|
- * 对前端传来的JWT字符串identityToken的第二部分进行解码
|
|
|
- * 主要获取其中的aud和sub,aud大概对应ios前端的包名,sub大概对应当前用户的授权的openID
|
|
|
- * @param identityToken
|
|
|
- * @return {"aud":"com.xkj.****","sub":"000***.8da764d3f9e34d2183e8da08a1057***.0***","c_hash":"UsKAuEoI-****","email_verified":"true","auth_time":1574673481,"iss":"https://appleid.apple.com","exp":1574674081,"iat":1574673481,"email":"****@qq.com"}
|
|
|
- */
|
|
|
- public static JSONObject parserIdentityToken(String identityToken){
|
|
|
- String[] arr = identityToken.split("\\.");
|
|
|
- Base64 base64 = new Base64();
|
|
|
- String decode = new String (base64.decodeBase64(arr[1]));
|
|
|
- String substring = decode.substring(0, decode.indexOf("}")+1);
|
|
|
- JSONObject jsonObject = JSON.parseObject(substring);
|
|
|
- return jsonObject;
|
|
|
+ JSONObject playloadObj = parserIdentityToken(identityToken);
|
|
|
+ Boolean success;
|
|
|
+ try {
|
|
|
+ success = verifyExc(identityToken, playloadObj);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException(e);
|
|
|
+ }
|
|
|
+ return success;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/**
|
|
|
* 获取p8文件中的内容
|
|
|
*
|
|
|
@@ -163,7 +160,8 @@ public class AppleUtil {
|
|
|
*/
|
|
|
private static Key getPrivateKey() {
|
|
|
try {
|
|
|
- ClassPathResource resource = new ClassPathResource("config/AuthKey_8G9994KW4L.p8");
|
|
|
+ //ClassPathResource resource = new ClassPathResource("config/AuthKey_8G9994KW4L.p8");
|
|
|
+ ClassPathResource resource = new ClassPathResource("config/AuthKey_"+AppleUtil.APPLE_KID+".p8");
|
|
|
InputStream inputStream = resource.getInputStream();
|
|
|
Reader reader = new InputStreamReader(inputStream, "utf-8");
|
|
|
BufferedReader br = new BufferedReader(reader);
|
|
|
@@ -181,7 +179,7 @@ public class AppleUtil {
|
|
|
EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64(sb.toString().replaceAll("\\n", "")));
|
|
|
return factory.generatePrivate(keySpec);
|
|
|
} catch (FileNotFoundException e) {
|
|
|
- log.error("not find p8 file !=>{}", e.getMessage());
|
|
|
+ System.out.println("not find p8 file !=>"+e.getMessage());
|
|
|
return null;
|
|
|
} catch (Exception e) {
|
|
|
e.printStackTrace();
|
|
|
@@ -269,11 +267,10 @@ public class AppleUtil {
|
|
|
}
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
- log.error("revoke token error");
|
|
|
+ System.out.println("revoke token error:" + e.getMessage());
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
}
|