使用 Apple Java 用户验证登录
我已经实现了新的苹果功能使用 Apple 登录"的应用程序端,但我无法在我的后端使用授权码进行验证.我的后端是用 java 编写的,我无法生成 JWT 并与 Apple 服务器通信.
I've implemented the app side of the new apple feature "Sign in with Apple" but i'm unable to verificate with authorizationCode in my backend. My backend is written in java and i'm unable to generate JWT and communicate with Apple servers.
推荐答案
先去 developer.apple.com ->证书、标识符和配置文件 ->钥匙.为 Apple 登录生成密钥并下载此密钥.您无法再次下载此密钥,因此请将其保存在安全的地方,不要与他人共享.此外,您在此处显示的密钥 ID 请注意这一点,稍后您将需要它.您还需要团队 ID.如果你不知道,它会写在页面的右上角,比如 YOURNAME - XX0XX00XXX.
First go developer.apple.com -> Certificates, Identifiers & Profiles -> Keys. Generate a key for Apple Sign in and download this key. You can not download this key again so keep it in a safe place and don't share with others. Also your Key ID shown here note this, you'll need this later. You'll also need team id. If you don't know it, it's written top right of the page like YOURNAME - XX0XX00XXX.
您将基本上遵循这些步骤.
You will basicly follow these steps.
1.从您的密钥生成 JWT
1.Generate JWT from your key
2.使用您的令牌发送验证码
2.Send auth code with your token
3.解码响应
同时使用网络和移动设备的更新
如果您想在网页上使用 Apple 登录,则需要执行更多步骤.
If you would like to use apple login for web there are few more steps you need to follow.
4.添加新的网络标识符
去 developer.apple.com ->证书、标识符和配置文件 ->身份标识.单击加号按钮注册新标识符.选择服务 ID 并继续.提供描述和标识符.标识符必须是唯一的,并且与您的捆绑包 ID 不同.(例如,您可以使用 com.your.bundle.id.web).点击继续点击注册.然后你需要配置这个服务ID.选择服务 ID(它位于搜索图标附近的右上角)您在下面列出的新创建的服务 ID 单击它并
启用使用 Apple 登录复选框.然后你需要配置你的域.提供您的域名并返回网址.
go developer.apple.com -> Certificates, Identifiers & Profiles -> Identifiers. Register a new identifier with clicking plus button. Select Service IDs and continue. Provide a description and identifier. Identifier must be unique and different from your bundle id. (for example you can use com.your.bundle.id.web). Click continue click register. Then you need to configure this service id. Select Service IDs (It's placed at top right near search icon) your newly created services id listed below click it and
enable Sign In with Apple tick box. Then you need to configure your domain. provide your domain and return url.
如果您忘记传递 valid redirect_url 或尝试多次使用相同的authorization_code.
Some important points for web you can get invalid_grant error if you forgot to pass a valid redirect_url or try to use same authorization_code more than once.
public class AppleLoginUtil {
private static String APPLE_AUTH_URL = "https://appleid.apple.com/auth/token";
private static String KEY_ID = "**********";
private static String TEAM_ID = "**********";
private static String CLIENT_ID = "com.your.bundle.id";
private static String WEB_CLIENT_ID = "com.your.bundle.id.web";
private static String WEB_REDIRECT_URL = "https://bundle.your.com/";
private static PrivateKey pKey;
private static PrivateKey getPrivateKey() throws Exception {
//read your key
String path = new ClassPathResource("apple/AuthKey.p8").getFile().getAbsolutePath();
final PEMParser pemParser = new PEMParser(new FileReader(path));
final JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
final PrivateKeyInfo object = (PrivateKeyInfo) pemParser.readObject();
final PrivateKey pKey = converter.getPrivateKey(object);
return pKey;
}
private static String generateJWT() throws Exception {
if (pKey == null) {
pKey = getPrivateKey();
}
String token = Jwts.builder()
.setHeaderParam(JwsHeader.KEY_ID, KEY_ID)
.setIssuer(TEAM_ID)
.setAudience("https://appleid.apple.com")
.setSubject(CLIENT_ID)
.setExpiration(new Date(System.currentTimeMillis() + (1000 * 60 * 5)))
.setIssuedAt(new Date(System.currentTimeMillis()))
.signWith(pKey, SignatureAlgorithm.ES256)
.compact();
return token;
}
private static String generateWebJWT() throws Exception {
String token = Jwts.builder()
.setHeaderParam(JwsHeader.KEY_ID, KEY_ID)
.setIssuer(TEAM_ID)
.setAudience("https://appleid.apple.com")
.setSubject(WEB_CLIENT_ID)
.setExpiration(new Date(System.currentTimeMillis() + (1000 * 60 * 5)))
.setIssuedAt(new Date(System.currentTimeMillis()))
.signWith(getPrivateKey(), SignatureAlgorithm.ES256)
.compact();
return token;
}
/*
* Returns unique user id from apple
* */
public static String appleAuth(String authorizationCode, boolean forWeb) throws Exception {
HttpResponse<String> response = Unirest.post(APPLE_AUTH_URL)
.header("Content-Type", "application/x-www-form-urlencoded")
.field("client_id", forWeb ? WEB_CLIENT_ID : CLIENT_ID)
.field("client_secret", forWeb ? generateWebJWT() : generateJWT())
.field("grant_type", "authorization_code")
.field("code", authorizationCode)
.field("redirect_uri", forWeb ? WEB_REDIRECT_URL : null)
.asString();
TokenResponse tokenResponse=new Gson().fromJson(response.getBody(),TokenResponse.class);
String idToken = tokenResponse.getId_token();
String payload = idToken.split("\.")[1];//0 is header we ignore it for now
String decoded = new String(Decoders.BASE64.decode(payload));
IdTokenPayload idTokenPayload = new Gson().fromJson(decoded,IdTokenPayload.class);
return idTokenPayload.getSub();
}
}
我使用 BouncyCastle jjwt 来生成令牌.还有用于休息呼叫的 unirest 和 gson.
I've used BouncyCastle jjwt for generating token. And also unirest and gson for rest calls.
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.63</version>
</dependency>
<!--JJWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.7</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.7</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.7</version>
<scope>runtime</scope>
</dependency>
<!--UNIREST-->
<dependency>
<groupId>com.mashape.unirest</groupId>
<artifactId>unirest-java</artifactId>
<version>1.4.9</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.3.6</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20140107</version>
</dependency>
如果你想知道的话,我还解析了对这些类的响应.
I've also parsed the responses to these classes if you wanted to know.
public class TokenResponse {
private String access_token;
private String token_type;
private Long expires_in;
private String refresh_token;
private String id_token;
..getters and setters
}
public class IdTokenPayload {
private String iss;
private String aud;
private Long exp;
private Long iat;
private String sub;//users unique id
private String at_hash;
private Long auth_time;
private Boolean nonce_supported;
private Boolean email_verified;
private String email;
..getters and setters
}
相关文章