下面是應用服務器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認證失敗的時候發起一次公鑰請求,更新公鑰,解決這個問題。