web-dev-qa-db-fra.com

Comment obtenir un jeton d'accès après la connexion de l'utilisateur à partir de Gmail sous Android?

Je suis Google Connectez-vous pour Android . Je peux maintenant obtenir le idToken, mais mon serveur principal que j'ai utilisé plus tôt attend le jeton access car j'utilisais précédemment Google+ Login. Maintenant, je ne veux pas modifier mon côté serveur. Mais quand même, comment puis-je utiliser Google Sign in et obtenir le jeton d'accès dans mon application Android afin de valider mon utilisateur sur mon serveur principal?.

J'utilisais auparavant GooglePlay Service 7.5.0 et j'utilise maintenant le dernier service GooglePlay Service 8.3.0.

23
aman.nepid

Pour vos besoins, vous pouvez utiliser le code suivant:

Tout d’abord, assurez-vous d’avoir un ID client {WEB} OAuth 2.0 valide:

<!-- Server Client ID.  This should be a valid Web OAuth 2.0 Client ID obtained
         from https://console.developers.google.com/ -->
    <string name="server_client_id">...e4p8.apps.googleusercontent.com</string>

Puis à l'intérieur de la classe d'activité:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ...

    // For sample only: make sure there is a valid server client ID.
    validateServerClientID();

    // [START configure_signin]
    // Configure sign-in to request offline access to the user's ID, basic
    // profile, and Google Drive. The first time you request a code you will
    // be able to exchange it for an access token and refresh token, which
    // you should store. In subsequent calls, the code will only result in
    // an access token. By asking for profile access (through
    // DEFAULT_SIGN_IN) you will also get an ID Token as a result of the
    // code exchange.
    String serverClientId = getString(R.string.server_client_id);
    GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestScopes(new Scope(Scopes.DRIVE_APPFOLDER))
            .requestServerAuthCode(serverClientId)
            .requestEmail()
            .build();
    // [END configure_signin]

    // Build GoogleAPIClient with the Google Sign-In API and the above options.
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
            .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
            .build();
}

private void getAuthCode() {
    // Start the retrieval process for a server auth code.  If requested, ask for a refresh
    // token.  Otherwise, only get an access token if a refresh token has been previously
    // retrieved.  Getting a new access token for an existing grant does not require
    // user consent.
    Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
    startActivityForResult(signInIntent, RC_GET_AUTH_CODE);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == RC_GET_AUTH_CODE) {
        GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
        Log.d(TAG, "onActivityResult:GET_AUTH_CODE:success:" + result.getStatus().isSuccess());

        if (result.isSuccess()) {
            // [START get_auth_code]
            GoogleSignInAccount acct = result.getSignInAccount();
            String authCode = acct.getServerAuthCode();

            // Show signed-in UI.
            mAuthCodeTextView.setText(getString(R.string.auth_code_fmt, authCode));
            updateUI(true);

            // TODO(user): send code to server and exchange for access/refresh/ID tokens.
            // [END get_auth_code]
        } else {
            // Show signed-out UI.
            updateUI(false);
        }
    }
}

Vous pouvez voir le code entier à l'adresse suivante: ServerAuthCodeActivity.Java

Le résultat, si vous utilisez cet exemple, ressemble à la capture d'écran suivante:

 BNK's screenshot

Ensuite, vous pouvez suivre les étapes mentionnées dans la documentation Google ci-dessous (à partir de l'étape # 3. Envoyez le code d'autorisation au backend de votre application à l'aide de HTTPS POST):

Connexion Google pour Android - Activation de l'accès côté serveur


UPDATE: à partir des commentaires, si vous souhaitez obtenir un jeton d'accès directement depuis l'application client Android, veuillez utiliser l'exemple de code suivant (remplacé par votre client_id, client_secret et le code d'authentification). 

OkHttpClient client = new OkHttpClient();
    RequestBody requestBody = new FormEncodingBuilder()
            .add("grant_type", "authorization_code")
            .add("client_id", "812741506391-h38jh0j4fv0ce1krdkiq0hfvt6n5amrf.apps.googleusercontent.com")
            .add("client_secret", "{clientSecret}")
            .add("redirect_uri","")
            .add("code", "4/4-GMMhmHCXhWEzkobqIHGG_EnNYYsAkukHspeYUk9E8")
            .build();
    final Request request = new Request.Builder()
            .url("https://www.googleapis.com/oauth2/v4/token")
            .post(requestBody)
            .build();
    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(final Request request, final IOException e) {
            Log.e(LOG_TAG, e.toString());                
        }

        @Override
        public void onResponse(Response response) throws IOException {
            try {
                JSONObject jsonObject = new JSONObject(response.body().string());
                final String message = jsonObject.toString(5);
                Log.i(LOG_TAG, message);                    
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    });

Veuillez utiliser compile 'com.squareup.okhttp:okhttp:2.6.0' (la version 3-RC1 aura différentes classes)

Avec une réponse réussie, vous aurez les informations suivantes dans logcat:

I/onResponse: {
              "expires_in": 3600,
              "token_type": "Bearer",
              "refresh_token": "1\/xz1eb0XU3....nxoALEVQ",
              "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjQxMWY1Ym......yWVsUA",
              "access_token": "ya29.bQKKYah-........_tkt980_qAGIo9yeWEG4"
         }
40
BNK

BNK a la plupart du temps sur place. La classe d'activité est identique à celle des BNK qui répondent uniquement en ajoutant la partie OkHttp une fois que vous avez obtenu la variable GoogleSignInAccount dans la méthode onActivityResult().

Mais je continuais à avoir des erreurs avec la partie requête OkHttp. Finalement, après quelques essais (et un peu de chance) dans Postman, j'ai découvert que le paramètre id_token me manquait. Il manquait un paramètre à la demande OkHttp, à savoir id_token. Utilisez le jeton d'identification que vous obtenez du compte GoogleSignInAccount, par exemple.

GoogleSignInAccount acct = result.getSignInAccount();
String idTokenString = acct.getIdToken();

Maintenant, utilisez cette idTokenString avec tous les paramètres de la partie OkHttp de la réponse de BNK un peu comme ceci

...

RequestBody requestBody = new FormEncodingBuilder()
            .add("grant_type", "authorization_code")
            .add("client_id", "812741506391-h38jh0j4fv0ce1krdkiq0hfvt6n5amrf.apps.googleusercontent.com")
            .add("client_secret", "{clientSecret}")
            .add("redirect_uri","")
            .add("code", "4/4-GMMhmHCXhWEzkobqIHGG_EnNYYsAkukHspeYUk9E8")
            .add("id_token", idTokenString) // Added this extra parameter here
            .build();

...

La réponse obtenue est la même que celle de BNK

{
  "access_token": "ya29.CjBgA_I58IabCJ...remainingAccessTokenHere",
  "token_type": "Bearer",
  "expires_in": 3577,
  "id_token": "eyJhbGciOiJS...veryLongStringHere"
}

Envoyez maintenant ce code d’accès à votre serveur principal pour qu’il s’authentifie comme vous le faisiez à l’époque de GoogleAuthUtil et de PlusAPI.

J'espère que cela aide :) Merci à BNK!

3
Narayan Acharya

Si quelqu'un d'autre rencontre des problèmes lors de la dernière demande de récupération du jeton d'accès de Google. ci-dessous est une approche testée et qui fonctionne à partir de 11-01-2018. Utilisation de retrofit2. 

Tout d'abord, voici un lien vers Google Doc sur le point de terminaison de l'échange de jetons: https://developers.google.com/identity/protocols/OAuth2WebServer#exchange-authorization-code

public interface GoogleService {

@POST("token")
@FormUrlEncoded
@Headers("Content-Type:application/x-www-form-urlencoded")
Call<GoogleAuthData> getToken(
        @Field("grant_type") String grantType,
        @Field("client_id") String clientId,
        @Field("client_secret") String clientSecret,
        @Field("redirect_uri") String redirectUri,
        @Field("code") String code);
}

Puis appelle ça comme ça:

Call<GoogleAuthData> call = RetroClient.getGoogleService().getToken(
            "authorization_code", context.getString(R.string.server_client_id),
            context.getString(R.string.server_client_secret), "", authCode);
1

J'ai trouvé un moyen d'obtenir un jeton d'accès sans idToken, code, secret ou toute demande (comme poster à " https://www.googleapis.com/oauth2/v4/token ") . Tout ce dont vous avez besoin est uniquement "ID client" . Suivez ces étapes:

  1. Utilisez "GoogleSignIn" pour vous connecter et obtenir l'objet "Compte".

    GoogleSignIn.getClient(
            ctx,
            GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                    .requestEmail()
                    .requestProfile()
                    .requestIdToken(KEY.GOOGLE_CLIENT_ID)
                    .requestServerAuthCode(KEY.GOOGLE_CLIENT_ID, true)
                    .build())
            .let { client ->
                client.signOut()
                    .let { task ->
                        Observable.create<GoogleSignInClient> { ob ->
                            task.addOnCompleteListener { ob.onNext(client) }
                        }
                    }
            }
            .flatMap {
                ctx.startActivityForResult(it.signInIntent, RC_SIGN_IN)
                ctx.activityResultObservable
            }
            .filter { it.requestCode == RC_SIGN_IN }
            .map {
                GoogleSignIn
                        .getSignedInAccountFromIntent(it.data)
                        .getResult(ApiException::class.Java)
            }
    

Ici, j'utilise RxJava pour écrire le code, vous pouvez écrire votre code sans celui-ci.

  1. Dans l'objet "Compte", vous pouvez obtenir le jeton d'accès en utilisant "GoogleAuthUtil".

            .flatMap { result ->
                Observable.create<AuthData> {
                    val scope = "oauth2:https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/userinfo.profile"
                    val accessToken = GoogleAuthUtil.getToken(context, result.account, scope)
                    // now you can use this token
                    it.onNext(accessToken)
                }
            }
    

La fonction "GoogleAuthUtil :: getToken" fait une demande, vous ne pouvez donc pas l'exécuter dans le thread de l'interface utilisateur. Vous pouvez maintenant envoyer ce jeton sur votre serveur. ????

0
yuriel

Grâce à @BNK, il a fourni la solution de travail. Et voici un guide officiel pour obtenir un "jeton d'accès" à partir de "code d'autorisation": https://developers.google.com/identity/protocols/OAuth2WebServer#exchange-authorization-code

Ici, je souhaite fournir à ma solution des classes de SDK Android pur. Si vous ne souhaitez pas ajouter de bibliothèque sophistiquée uniquement à cette fin:

private String mAccessToken;
private long mTokenExpired;

private String requestAccessToken(GoogleSignInAccount googleAccount) {
    if (mAccessToken != null && SystemClock.elapsedRealtime() < mTokenExpired) return mAccessToken;
    mTokenExpired = 0;
    mAccessToken = null;

    HttpURLConnection conn = null;
    OutputStream os = null;
    InputStream is = null;
    InputStreamReader isr = null;
    BufferedReader br = null;

    try {
        final URL url = new URL("https://www.googleapis.com/oauth2/v4/token");
        conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setUseCaches(false);
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setConnectTimeout(3000);
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

        final StringBuilder b = new StringBuilder();
        b.append("code=").append(googleAccount.getServerAuthCode()).append('&')
         .append("client_id=").append(getString(R.string.default_web_client_id)).append('&')
         .append("client_secret=").append(getString(R.string.client_secret)).append('&')
         .append("redirect_uri=").append("").append('&')
         .append("grant_type=").append("authorization_code");

        final byte[] postData = b.toString().getBytes("UTF-8");

        os = conn.getOutputStream();
        os.write(postData);

        final int responseCode = conn.getResponseCode();
        if (200 <= responseCode && responseCode <= 299) {
            is = conn.getInputStream();
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);
        } else {
            Log.d("Error:", conn.getResponseMessage());
            return null;
        }

        b.setLength(0);
        String output;
        while ((output = br.readLine()) != null) {
            b.append(output);
        }

        final JSONObject jsonResponse = new JSONObject(b.toString());
        mAccessToken = jsonResponse.getString("access_token");
        mTokenExpired = SystemClock.elapsedRealtime() + jsonResponse.getLong("expires_in") * 1000;
        return mAccessToken;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
            }
        }
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
            }
        }
        if (isr != null) {
            try {
                isr.close();
            } catch (IOException e) {
            }
        }
        if (br != null) {
            try {
                br.close();
            } catch (IOException e) {
            }
        }
        if (conn != null) {
            conn.disconnect();
        }
    }
    return null;
}

Exécutez cette méthode sur le thread d'arrière-plan. De plus, vous devez obtenir client_id et client_secret à partir de la console des API Google.

 Google APIs console id and secret

0
Oleksandr Albul