服务端验证Sign in with Apple身份令牌时如何获取audience和subject参数
Sign in with Apple 服务端Token校验参数获取说明
你代码中verify方法需要的audience和subject两个参数获取逻辑如下:
- audience:这是固定配置值,对应你接入Sign in with Apple时在苹果后台配置的应用标识:
- 原生iOS App接入场景,传你应用的Bundle ID
- Web/跨平台应用接入场景,传你配置的Services ID
你示例中解码出来的Token payload里aud字段值为com.skyming.devicemonitor,这个就是你当前应用对应的audience值,直接写到服务端配置里读取即可,不需要从前端请求参数中动态获取。
- subject:这是苹果为每个用户在你的应用下生成的全局唯一用户ID,对应Token payload里的
sub字段,不能提前写死传固定值:- 首次用户登录时,你本地数据库没有存储该用户对应的sub值,此时不能开启subject强校验,否则所有首次登录的请求都会校验失败。你需要先完成签名、签发方、audience、过期时间的基础校验,校验通过后从解析出的Claims中取出sub值,和你本地新建的用户账号做绑定存储。
- 后续老用户登录时,你可以根据请求关联的本地用户信息,取出之前存储的该用户绑定的apple sub值,此时再传入做subject强校验,避免Token被冒用到其他账号上。
注意:你当前获取苹果公钥的逻辑存在缺陷,不要直接取JWKSet返回的第一个公钥。需要先解析identityToken的请求头,拿到头里的
kid参数,再去JWKSet的公钥列表里匹配和kid值一致的公钥做校验,否则苹果轮换公钥、返回多个公钥时会出现校验失败的问题。
修正后的代码示例
首先调整verify方法,支持subject可选校验:
public void verify(PublicKey key, String jwt, String audience, String subject) { JwtParserBuilder parserBuilder = Jwts.parserBuilder() .setSigningKey(key) .requireIssuer("https://appleid.apple.com") .requireAudience(audience); // 仅当传入有效subject时才开启sub校验 if (subject != null && !subject.trim().isEmpty()) { parserBuilder.requireSubject(subject); } JwtParser jwtParser = parserBuilder.build(); try { Jws<Claims> claim = jwtParser.parseClaimsJws(jwt); if (claim != null && claim.getBody().containsKey("auth_time")) { String userSub = claim.getBody().getSubject(); // 后续业务逻辑:拿userSub做用户匹配/绑定 } } catch (ExpiredJwtException e) { log.error("apple identityToken expired", e); } catch (Exception e) { log.error("apple identityToken illegal", e); } }
登录接口调用逻辑调整:
public Response<LoginResponse> loginWithJwt(FortuneAppleUserJwtLoginRequest request) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { String identityToken = request.getIdentityToken(); // 1. 先解析token header拿到kid,再匹配对应苹果公钥(原逻辑取第一个公钥的问题记得修正) AppleUserRestClient client = RetrofitUtil.getAppleIdRetrofitInstance().create(AppleUserRestClient.class); Call<JWKSet> call = client.getPublicKey(); JWKSet jwkSet = call.execute().body(); // 省略根据kid匹配公钥的逻辑,自行补充即可 PublicKey publicKey = getPublicKey(n,e); // 2. 从服务端配置读取固定的audience值 String APP_AUDIENCE = "com.skyming.devicemonitor"; // 3. 如果是老用户,这里查库拿到该用户绑定的sub再传入,首次登录传null即可 verify(publicKey, identityToken, APP_AUDIENCE, null); return new Response<>(); }
内容的提问来源于stack exchange,提问作者Dolphin
相关产品推荐
相关产品推荐

