由于Apple政策, Apple ID登录的用户注销时需要进行Revoke Token. 顺手记下来.
官方文档
Revoke tokens
代码参考文档:
Generate the Client Secret
Create a Private Key for Client Authentication
Sign in with Apple(苹果授权登陆)
maven
io.jsonwebtoken jjwt 0.9.1
工具类
package apple;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.codec.binary.Base64;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class AppleRestApiUtil {
private final String APPLE_DOMAIN = "https://appleid.apple.com";
private String client_id;
private String team_id;
private String key_id;
private String key_value;
private AppleRestApiUtil(String client_id, String team_id, String key_id, String key_value) {
this.client_id = client_id;
this.team_id = team_id;
this.key_id = key_id;
this.key_value = key_value;
}
public static AppleRestApiUtil init(String client_id, String team_id, String key_id, String key_value) {
return new AppleRestApiUtil(client_id, team_id, key_id, key_value);
}
private String getUrl(String path) {
return APPLE_DOMAIN + path;
}
public String generateClientSecret() {
String client_secret = null;
try {
client_secret = generateClientSecret(client_id, team_id, key_id, key_value);
} catch (Exception e) {
e.printStackTrace();
}
return client_secret;
}
public String generateClientSecret(String client_id, String team_id, String key_id, String key_value) throws Exception{
Map header = new HashMap();
Map claims = new HashMap();
long now = new Date().getTime() / 1000;
header.put("kid", key_id); // 参考后台配置
claims.put("iss", team_id); // 参考后台配置 team id
claims.put("iat", now);
claims.put("exp", now + 60 * 60 * 24 * 30); // 最长半年,单位秒
claims.put("aud", APPLE_DOMAIN); // 默认值
claims.put("sub", client_id);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(readKey(key_value));
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
String client_secret = Jwts.builder().setHeader(header).setClaims(claims).signWith(SignatureAlgorithm.ES256, privateKey).compact();
return client_secret;
}
public byte[] readKey(String key_value) throws Exception {
return Base64.decodeBase64(key_value);
}
public HttpResponse revokeToken(String accessToken) {
String url = getUrl("/auth/revoke");
Map form = new HashMap();
form.put("client_id", client_id);
form.put("client_secret", generateClientSecret());
form.put("token_type_hint","access_token");
form.put("token", accessToken);
HttpResponse execute = HttpRequest.post(url).header("Content-Type", "application/x-www-form-urlencoded").form(form).execute();
return execute;
}
public HttpResponse refreshToken(String refreshToken) {
String url = getUrl("/auth/token");
Map form = new HashMap();
form.put("client_id", client_id);
form.put("client_secret", generateClientSecret());
form.put("grant_type","refresh_token");
form.put("refresh_token", refreshToken);
HttpResponse execute = HttpRequest.post(url).header("Content-Type", "application/x-www-form-urlencoded").form(form).execute();
return execute;
}
public HttpResponse generateToken(String appleCode) {
String url = getUrl("/auth/token");
Map form = new HashMap();
form.put("client_id", client_id);
form.put("client_secret", generateClientSecret());
form.put("grant_type","authorization_code");
form.put("code", appleCode);
HttpResponse execute = HttpRequest.post(url).header("Content-Type", "application/x-www-form-urlencoded").form(form).execute();
return execute;
}
}
测试类
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.JSONObject;
import org.junit.Test;
public class AppleRestApiTest {
private AppleRestApiUtil init() {
String client_id = "";
String team_id = "";
String key_id = "";
String key_value = "";
return AppleRestApiUtil.init(client_id, team_id, key_id, key_value);
}
@Test
private void testGenerateClientSecret() {
String client_secret = init().generateClientSecret();
System.out.println("client_secret: " + client_secret);
}
@Test
private void testGenerateToken() {
String appleCode = ""; // IOS端提供, 5分钟过期
HttpResponse httpResponse = init().generateToken(appleCode);
if(httpResponse.getStatus() == HttpStatus.HTTP_OK) {
String body = httpResponse.body();
JSONObject jsonObject = JSONObject.parseObject(body);
System.out.println("body: " + body);
System.out.println("access_token: " + jsonObject.getString("access_token")); // 3600s过期
System.out.println("refresh_token: " + jsonObject.getString("refresh_token"));
}
}
@Test
private void testRefreshToken() {
String refreshToken = ""; // generateToken 方法中获取
HttpResponse httpResponse = init().refreshToken(refreshToken);
if(httpResponse.getStatus() == HttpStatus.HTTP_OK) {
String body = httpResponse.body();
System.out.println("body: " + body);
}
}
@Test
private void testRevokeToken() {
String access_token = ""; // generateToken 或者 refreshToken 方法获取
HttpResponse httpResponse = init().revokeToken(access_token);
if(httpResponse.getStatus() == HttpStatus.HTTP_OK) {
String body = httpResponse.body();
System.out.println("body: " + body);
}
}
}
业务流程
- 用户登录, 通过IOS传入的code, 调用generateToken方法生成一个refresh_token, 把这个值存入和用户相关的表中
- 用户注销, 因为苹果的政策需要撤销令牌, 也就是调用revokeToken方法
2.1 根据用户的refresh_token调用refreshToken方法生成一个access_token(有效期: 3600s)
2.2 然后根据access_token去调用revokeToken方法 - 调用苹果Sign in with Apple REST API这部分的API都需要一个client_secret, 可以每次生成也可以生成一个循环使用(最多有效期6个月)
3.1 参考文档: Generate the Client Secret
3.2 需要 client_id, team_id, key_id, key_value(私钥内容). 密钥如何生成参考: Create a Private Key for Client Authentication
- 以前没使用过Apple的API. 总感觉每次都用refresh_token去重新生成access_token不太好. 又没找到其他的解决方案
- 我把client_secret存入缓存, 如果key_value发生改变. 就会存在client_secret无法使用的情况. 不知道有没有办法避免这种情况.
- 若文中有错误或者有更好的方案,欢迎指教
- END



