|
@@ -5,11 +5,23 @@ import com.alibaba.fastjson.JSONArray;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.auth0.jwk.Jwk;
|
|
import com.auth0.jwk.Jwk;
|
|
|
import io.jsonwebtoken.*;
|
|
import io.jsonwebtoken.*;
|
|
|
|
|
+import io.jsonwebtoken.impl.DefaultJwtBuilder;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.codec.binary.Base64;
|
|
import org.apache.commons.codec.binary.Base64;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
+import org.springframework.core.io.ClassPathResource;
|
|
|
|
|
+import org.springframework.http.*;
|
|
|
|
|
+import org.springframework.util.MultiValueMap;
|
|
|
import org.springframework.web.client.RestTemplate;
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
|
|
|
|
|
|
|
+import java.io.*;
|
|
|
|
|
+import java.security.Key;
|
|
|
|
|
+import java.security.KeyFactory;
|
|
|
import java.security.PublicKey;
|
|
import java.security.PublicKey;
|
|
|
|
|
+import java.security.spec.EncodedKeySpec;
|
|
|
|
|
+import java.security.spec.PKCS8EncodedKeySpec;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* @author:slambb
|
|
* @author:slambb
|
|
@@ -18,6 +30,22 @@ import java.security.PublicKey;
|
|
|
|
|
|
|
|
@Slf4j
|
|
@Slf4j
|
|
|
public class AppleUtil {
|
|
public class AppleUtil {
|
|
|
|
|
+ @Value("${apple.KEYS_URL}")
|
|
|
|
|
+ private static String APPLE_KEYS_URL;
|
|
|
|
|
+ //Revoke tokens
|
|
|
|
|
+ //由于苹果注册后,删除用户需要revoke tokens
|
|
|
|
|
+ public static String privateKeyStr;
|
|
|
|
|
+ @Value("${apple.REVOKE_TOKENS_URL}")
|
|
|
|
|
+ private static String APPLE_REVOKE_TOKENS_URL;
|
|
|
|
|
+ @Value("${apple.AUTH_TOKENS_URL}")
|
|
|
|
|
+ private static String APPLE_AUTH_TOKENS_URL;
|
|
|
|
|
+
|
|
|
|
|
+ @Value("${apple.TEAM_ID}")
|
|
|
|
|
+ private static String APPLE_TEAM_ID;
|
|
|
|
|
+ @Value("${apple.KID}")
|
|
|
|
|
+ private static String APPLE_KID;
|
|
|
|
|
+ @Value("${apple.BUNDLE_IDENTIFIER}")
|
|
|
|
|
+ private static String APPLE_BUNDLE_IDENTIFIER;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 获取苹果的公钥
|
|
* 获取苹果的公钥
|
|
@@ -25,9 +53,9 @@ public class AppleUtil {
|
|
|
* @throws Exception
|
|
* @throws Exception
|
|
|
*/
|
|
*/
|
|
|
private static JSONArray getAuthKeys() throws Exception {
|
|
private static JSONArray getAuthKeys() throws Exception {
|
|
|
- String url = "https://appleid.apple.com/auth/keys";
|
|
|
|
|
|
|
+// String url = "https://appleid.apple.com/auth/keys";
|
|
|
RestTemplate restTemplate = new RestTemplate();
|
|
RestTemplate restTemplate = new RestTemplate();
|
|
|
- JSONObject json = restTemplate.getForObject(url,JSONObject.class);
|
|
|
|
|
|
|
+ JSONObject json = restTemplate.getForObject(APPLE_KEYS_URL,JSONObject.class);
|
|
|
JSONArray arr = json.getJSONArray("keys");
|
|
JSONArray arr = json.getJSONArray("keys");
|
|
|
return arr;
|
|
return arr;
|
|
|
}
|
|
}
|
|
@@ -107,4 +135,125 @@ public class AppleUtil {
|
|
|
return jsonObject;
|
|
return jsonObject;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取p8文件中的内容
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return
|
|
|
|
|
+ */
|
|
|
|
|
+ private static Key getPrivateKey() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ClassPathResource resource = new ClassPathResource("static/config/AuthKey_8G9994KW4L.p8");
|
|
|
|
|
+ InputStream inputStream = resource.getInputStream();
|
|
|
|
|
+ Reader reader = new InputStreamReader(inputStream, "utf-8");
|
|
|
|
|
+ BufferedReader br = new BufferedReader(reader);
|
|
|
|
|
+ String string = null;
|
|
|
|
|
+ StringBuffer sb = new StringBuffer();
|
|
|
|
|
+ while ((string = br.readLine()) != null) {
|
|
|
|
|
+ if (string.startsWith("---")) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ sb.append(string);
|
|
|
|
|
+ }
|
|
|
|
|
+ br.close();
|
|
|
|
|
+
|
|
|
|
|
+ KeyFactory factory = KeyFactory.getInstance("EC");
|
|
|
|
|
+ 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());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 私钥加密后给苹果去验证,构造clientSecret,就是构造一个jwt字符串
|
|
|
|
|
+ * [获取私钥]
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param iss team_id
|
|
|
|
|
+ * @param sub client_id
|
|
|
|
|
+ * @param kid access_token
|
|
|
|
|
+ * @return
|
|
|
|
|
+ */
|
|
|
|
|
+ public static String buildJwt(String iss, String sub, String kid) {
|
|
|
|
|
+ Map<String, Object> header = new HashMap<>();
|
|
|
|
|
+ header.put("alg", SignatureAlgorithm.ES256.getValue()); //SHA256withECDSA
|
|
|
|
|
+ header.put("kid", kid);
|
|
|
|
|
+
|
|
|
|
|
+ long iat = System.currentTimeMillis() / 1000; //以秒为单位
|
|
|
|
|
+ Map<String, Object> claims = new HashMap<>();
|
|
|
|
|
+ claims.put("iss", iss);// apple开发组id 问ios开发要
|
|
|
|
|
+ claims.put("iat", iat);
|
|
|
|
|
+ claims.put("exp", iat + 180 * 3600); //设置过期时间
|
|
|
|
|
+ claims.put("aud", "https://appleid.apple.com"); //固定值
|
|
|
|
|
+ claims.put("sub", sub);// Bundle Identifier
|
|
|
|
|
+ return new DefaultJwtBuilder().setHeader(header).setClaims(claims).signWith(SignatureAlgorithm.ES256, getPrivateKey()).compact();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 用户授权获取
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param code
|
|
|
|
|
+ * @return
|
|
|
|
|
+ */
|
|
|
|
|
+ public static String getAuthToken(String code) {
|
|
|
|
|
+ if (code.isEmpty() ) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+// JSONObject jsonObject = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ privateKeyStr = buildJwt(APPLE_TEAM_ID, APPLE_BUNDLE_IDENTIFIER, APPLE_KID);
|
|
|
|
|
+ Map<String, String> stringStringHashMap = new HashMap<>();
|
|
|
|
|
+ stringStringHashMap.put("client_id", APPLE_BUNDLE_IDENTIFIER);
|
|
|
|
|
+ stringStringHashMap.put("client_secret", privateKeyStr);
|
|
|
|
|
+ stringStringHashMap.put("code", code);
|
|
|
|
|
+ stringStringHashMap.put("grant_type", "authorization_code");
|
|
|
|
|
+
|
|
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
|
|
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
|
|
|
+ HttpEntity request = new HttpEntity<>(stringStringHashMap,headers);
|
|
|
|
|
+ RestTemplate restTemplate = new RestTemplate();
|
|
|
|
|
+ JSONObject jsonObject = restTemplate.postForObject(APPLE_AUTH_TOKENS_URL,request,JSONObject.class);
|
|
|
|
|
+ return jsonObject.get("access_token").toString();
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ throw new RuntimeException(e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Boolean appleRevoke(String code) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String authToken = getAuthToken(code);
|
|
|
|
|
+ Map<String, String> requestMap = new HashMap<>();
|
|
|
|
|
+ requestMap.put("client_id", APPLE_BUNDLE_IDENTIFIER);
|
|
|
|
|
+ requestMap.put("client_secret", privateKeyStr);
|
|
|
|
|
+ requestMap.put("token", authToken);
|
|
|
|
|
+ requestMap.put("token_type_hint", "access_token");
|
|
|
|
|
+ System.out.println(requestMap);
|
|
|
|
|
+
|
|
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
|
|
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
|
|
|
+ HttpEntity request = new HttpEntity<>(requestMap,headers);
|
|
|
|
|
+ RestTemplate restTemplate = new RestTemplate();
|
|
|
|
|
+ ResponseEntity<String> responseEntity = restTemplate.postForEntity(APPLE_REVOKE_TOKENS_URL,request,String.class);
|
|
|
|
|
+ int statusCode = responseEntity.getStatusCodeValue();
|
|
|
|
|
+ if (statusCode != 200) {
|
|
|
|
|
+ JSONObject jsonObject = JSON.parseObject(responseEntity.getBody());
|
|
|
|
|
+ String error = jsonObject.get("error").toString();
|
|
|
|
|
+ System.out.println("error --------- " + error);
|
|
|
|
|
+ if (!error.isEmpty()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("revoke token error");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
}
|
|
}
|