12

Apple の新機能「Apple でサインイン」のアプリ側を実装しましたが、バックエンドで AuthorizationCode を使用して検証できません。私のバックエンドは Java で書かれており、JWT を生成して Apple サーバーと通信することができません。

4

2 に答える 2

22

最初に developer.apple.com -> Certificates, Identifiers & Profiles -> Keys に移動します。Apple サインイン用のキーを生成し、このキーをダウンロードします。このキーを再度ダウンロードすることはできないため、安全な場所に保管し、他のユーザーと共有しないでください。また、ここに表示されているキー ID もこれに注意してください。これは後で必要になります。チームIDも必要です。ご存じない場合は、ページの右上に YOURNAME - XX0XX00XXX のように書かれています。

基本的にこれらの手順に従います。

1. キーから JWT を生成する

2.トークンとともに認証コードを送信する

3.デコード応答

Webとモバイルの両方を使用するためのUPDATE

Apple Login for Web を使用する場合は、さらにいくつかの手順に従う必要があります。

4. Web 用の新しい識別子を追加する

developer.apple.com -> Certificates, Identifiers & Profiles -> Identifiers に移動します。プラスボタンをクリックして新しい識別子を登録します。[サービス ID] を選択して続行します。説明と識別子を指定します。識別子は一意で、バンドル ID とは異なる必要があります。(たとえば、com.your.bundle.id.web を使用できます)。続行をクリックして登録をクリックします。次に、このサービス ID を構成する必要があります。サービス ID (右上の検索アイコンの近くにあります) を選択し、新しく作成したサービス ID をクリックして [
Apple でサインイン] チェックボックスを有効にします。次に、ドメインを構成する必要があります。ドメインとリターン URL を提供します。

有効なredirect_urlを渡すのを忘れた場合、または同じ authentication_code を複数回使用しようとすると、invalid_grant エラーが発生する可能があるWeb の重要なポイント。


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 もあります。

 <!-- 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>

知りたい場合は、これらのクラスへの応答も解析しました。

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
}
于 2019-10-05T20:30:07.233 に答える