Невозможно обменять код авторизации на токен доступа и обновить токен в Cross Client google oauth2.0
У меня возникли проблемы с реализацией входа в сервисы Google Play в моем приложении для Android и передачей кода авторизации на внутренний сервер, поэтому сервер будет обмениваться кодом для токена доступа и обновления токена.
Сначала позвольте мне написать несколько строк о том, что уже было опробовано / прочитано:
на code.google.com/apis/console я создал новый проект, в котором два клиента (веб-клиент и установленный клиент Android) читают статьи на https://developers.google.com/+/mobile/android/sign-in и http://android-developers.blogspot.com/2013/01/verifying-back-end-calls-from-android.html
Вот мой код для клиентской части, который получает код авторизации и IdToken:
package com.google.drive.samples.crossclientoauth2;
import java.util.Arrays;
import java.util.List;
import android.accounts.AccountManager;
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.EditText;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
public class MainActivity extends Activity {
final private String CLIENT_ID = MY WEB SERVER'S CLIENT ID;
final private List<String> SCOPES = Arrays.asList(new String[]{
"https://www.googleapis.com/auth/plus.login",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/youtube",
"https://www.googleapis.com/auth/youtube.readonly"
});
// I have modified the above line of code.
private GoogleAccountCredential mCredential;
private EditText mExchangeCodeEditText;
private EditText mIdTokenEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mExchangeCodeEditText = (EditText) findViewById(R.id.editTextExchangeCode);
mIdTokenEditText = (EditText) findViewById(R.id.editTextIdToken);
// initiate a credential object with drive and plus.login scopes
// cross identity is only available for tokens retrieved with plus.login
mCredential = GoogleAccountCredential.usingOAuth2(this, null);
// user needs to select an account, start account picker
startActivityForResult(
mCredential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}
/**
* Handles the callbacks from result returning
* account picker and permission requester activities.
*/
@Override
protected void onActivityResult(
final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
// user has returned back from the account picker,
// initiate the rest of the flow with the account he/she has chosen.
case REQUEST_ACCOUNT_PICKER:
String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
mCredential.setSelectedAccountName(accountName);
new RetrieveExchangeCodeAsyncTask().execute();
new RetrieveJwtAsyncTask().execute();
}
break;
// user has returned back from the permissions screen,
// if he/she has given enough permissions, retry the the request.
case REQUEST_AUTHORIZATION:
if (resultCode == Activity.RESULT_OK) {
// replay the same operations
new RetrieveExchangeCodeAsyncTask().execute();
new RetrieveJwtAsyncTask().execute();
}
break;
}
}
/**
* Retrieves the exchange code to be sent to the
* server-side component of the app.
*/
public class RetrieveExchangeCodeAsyncTask
extends AsyncTask<Void, Boolean, String> {
@Override
protected String doInBackground(Void... params) {
String scope = String.format("oauth2:server:client_id:%s:api_scope:%s",
CLIENT_ID, TextUtils.join(" ", SCOPES));
try {
return GoogleAuthUtil.getToken(
MainActivity.this, mCredential.getSelectedAccountName(), scope);
} catch (UserRecoverableAuthException e) {
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
} catch (Exception e) {
e.printStackTrace(); // TODO: handle the exception
}
return null;
}
@Override
protected void onPostExecute(String code) {
// exchange code with server-side to retrieve an additional
// access token on the server-side.
Log.v("first One ","code 1 is: "+ code);
mExchangeCodeEditText.setText(code);
}
}
/**
* Retrieves a JWT to identify the user without the
* regular client-side authorization flow. The jwt payload needs to be
* sent to the server-side component.
*/
public class RetrieveJwtAsyncTask
extends AsyncTask<Void, Boolean, String> {
@Override
protected String doInBackground(Void... params) {
String scope = "audience:server:client_id:" + CLIENT_ID;
try {
return GoogleAuthUtil.getToken(
MainActivity.this, mCredential.getSelectedAccountName(), scope);
} catch(UserRecoverableAuthIOException e) {
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
} catch (Exception e) {
e.printStackTrace(); // TODO: handle the exception
}
return null;
}
@Override
protected void onPostExecute(String idToken) {
// exchange encrypted idToken with server-side to identify the user
Log.v("Second One","2222"+ idToken);
mIdTokenEditText.setText(idToken);
}
}
private static final int REQUEST_ACCOUNT_PICKER = 100;
private static final int REQUEST_AUTHORIZATION = 200;
}
Приведенный выше код дает мне два кода: 1. Один возвращается RetrieveExchangeCodeAsyncTask - именованный код. 2. Вторая секунда, возвращаемая классом RetrieveJwtAsyncTask с именем IdToken.
Теперь, во-первых, я не понимаю, какой из вышеперечисленных мне нужно отправить на мой веб-сервер, где он будет обменяться. Я попытался использовать первый (тот, который начинается как "4/....") для обмена на моей стороне сервера, но получил исключение нулевого указателя. Также, пожалуйста, укажите, какой URI перенаправления мне нужно использовать.
Вот мой серверный код для обмена:
package com.myAuthSample.tial;
import java.io.IOException;
import com.myAuthSample.tial.MyClass.CodeExchangeException;
import com.myAuthSample.tial.MyClass.NoRefreshTokenException;
public class MyMainDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
MyClass.getCredentials("4/...something...", "state"); //passed the retrieved authorization code
} catch (CodeExchangeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoRefreshTokenException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class MyClass {
// Path to client_secrets.json which should contain a JSON document such as:
// {
// "web": {
// "client_id": "[[YOUR_CLIENT_ID]]",
// "client_secret": "[[YOUR_CLIENT_SECRET]]",
// "auth_uri": "https://accounts.google.com/o/oauth2/auth",
// "token_uri": "https://accounts.google.com/o/oauth2/token"
// }
// }
private static final String CLIENTSECRETS_LOCATION = "/client_secrets_2.json";// client secrets of my android client
// private static final String REDIRECT_URI = "<YOUR_REGISTERED_REDIRECT_URI>";
private static String REDIRECT_URI ="";
private static final List<String> SCOPES = Arrays.asList(
"https://www.googleapis.com/auth/plus.login",
"https://www.googleapis.com/auth/youtube");
private static GoogleAuthorizationCodeFlow flow = null;
/**
* Exception thrown when an error occurred while retrieving credentials.
*/
public static class GetCredentialsException extends Exception {
protected String authorizationUrl;
/**
* Construct a GetCredentialsException.
*
* @param authorizationUrl The authorization URL to redirect the user to.
*/
public GetCredentialsException(String authorizationUrl) {
this.authorizationUrl = authorizationUrl;
}
/**
* Set the authorization URL.
*/
public void setAuthorizationUrl(String authorizationUrl) {
this.authorizationUrl = authorizationUrl;
}
/**
* @return the authorizationUrl
*/
public String getAuthorizationUrl() {
return authorizationUrl;
}
}
/**
* Exception thrown when a code exchange has failed.
*/
public static class CodeExchangeException extends GetCredentialsException {
/**
* Construct a CodeExchangeException.
*
* @param authorizationUrl The authorization URL to redirect the user to.
*/
public CodeExchangeException(String authorizationUrl) {
super(authorizationUrl);
}
}
/**
* Exception thrown when no refresh token has been found.
*/
public static class NoRefreshTokenException extends GetCredentialsException {
/**
* Construct a NoRefreshTokenException.
*
* @param authorizationUrl The authorization URL to redirect the user to.
*/
public NoRefreshTokenException(String authorizationUrl) {
super(authorizationUrl);
}
}
/**
* Exception thrown when no user ID could be retrieved.
*/
private static class NoUserIdException extends Exception {
}
/**
* Retrieved stored credentials for the provided user ID.
*
* @param userId User's ID.
* @return Stored Credential if found, {@code null} otherwise.
*/
static Credential getStoredCredentials(String userId) {
// TODO: Implement this method to work with your database. Instantiate a new
// Credential instance with stored accessToken and refreshToken.
throw new UnsupportedOperationException();
}
/**
* Store OAuth 2.0 credentials in the application's database.
*
* @param userId User's ID.
* @param credentials The OAuth 2.0 credentials to store.
*/
static void storeCredentials(String userId, Credential credentials) {
// TODO: Implement this method to work with your database.
// Store the credentials.getAccessToken() and credentials.getRefreshToken()
// string values in your database.
System.out.println("credentials are : " + credentials.toString());
throw new UnsupportedOperationException();
}
/**
* Build an authorization flow and store it as a static class attribute.
*
* @return GoogleAuthorizationCodeFlow instance.
* @throws IOException Unable to load client_secrets.json.
*/
static GoogleAuthorizationCodeFlow getFlow() throws IOException {
if (flow == null) {
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory(); //...this was the original line....
// JsonFactory jsonFactory = new JacksonFactory();
//my code....
Reader clientSecretReader = new InputStreamReader(MyClass.class.getResourceAsStream(CLIENTSECRETS_LOCATION));
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(jsonFactory,clientSecretReader);
REDIRECT_URI =clientSecrets.getDetails().getRedirectUris().get(0);
// my code ends...
/* GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(jsonFactory,
MyClass.class.getResourceAsStream(CLIENTSECRETS_LOCATION));
*/
flow =
new GoogleAuthorizationCodeFlow.Builder(httpTransport, jsonFactory, clientSecrets, SCOPES)
.setAccessType("offline").setApprovalPrompt("force").build();
}
return flow;
}
/**
* Exchange an authorization code for OAuth 2.0 credentials.
*
* @param authorizationCode Authorization code to exchange for OAuth 2.0
* credentials.
* @return OAuth 2.0 credentials.
* @throws CodeExchangeException An error occurred.
*/
static Credential exchangeCode(String authorizationCode)
throws CodeExchangeException {
try {
GoogleAuthorizationCodeFlow flow = getFlow();
GoogleTokenResponse response =
flow.newTokenRequest(authorizationCode).setRedirectUri(REDIRECT_URI).execute();
return flow.createAndStoreCredential(response, null);
} catch (IOException e) {
System.err.println("An error occurred: " + e);
throw new CodeExchangeException(null);
}
}
/**
* Send a request to the UserInfo API to retrieve the user's information.
*
* @param credentials OAuth 2.0 credentials to authorize the request.
* @return User's information.
* @throws NoUserIdException An error occurred.
*/
static Userinfo getUserInfo(Credential credentials)
throws NoUserIdException {
Oauth2 userInfoService =
new Oauth2.Builder(new NetHttpTransport(), new JacksonFactory(), credentials).build();
Userinfo userInfo = null;
try {
userInfo = userInfoService.userinfo().get().execute();
} catch (IOException e) {
System.err.println("An error occurred: " + e);
}
if (userInfo != null && userInfo.getId() != null) {
return userInfo;
} else {
throw new NoUserIdException();
}
}
/**
* Retrieve the authorization URL.
*
* @param emailAddress User's e-mail address.
* @param state State for the authorization URL.
* @return Authorization URL to redirect the user to.
* @throws IOException Unable to load client_secrets.json.
*/
public static String getAuthorizationUrl(String emailAddress, String state) throws IOException {
GoogleAuthorizationCodeRequestUrl urlBuilder =
getFlow().newAuthorizationUrl().setRedirectUri(REDIRECT_URI).setState(state);
urlBuilder.set("user_id", emailAddress);
return urlBuilder.build();
}
/**
* Retrieve credentials using the provided authorization code.
*
* This function exchanges the authorization code for an access token and
* queries the UserInfo API to retrieve the user's e-mail address. If a
* refresh token has been retrieved along with an access token, it is stored
* in the application database using the user's e-mail address as key. If no
* refresh token has been retrieved, the function checks in the application
* database for one and returns it if found or throws a NoRefreshTokenException
* with the authorization URL to redirect the user to.
*
* @param authorizationCode Authorization code to use to retrieve an access
* token.
* @param state State to set to the authorization URL in case of error.
* @return OAuth 2.0 credentials instance containing an access and refresh
* token.
* @throws NoRefreshTokenException No refresh token could be retrieved from
* the available sources.
* @throws IOException Unable to load client_secrets.json.
*/
public static Credential getCredentials(String authorizationCode, String state)
throws CodeExchangeException, NoRefreshTokenException, IOException {
String emailAddress = "";
try {
Credential credentials = exchangeCode(authorizationCode);
Userinfo userInfo = getUserInfo(credentials);
String userId = userInfo.getId();
emailAddress = userInfo.getEmail();
if (credentials.getRefreshToken() != null) {
storeCredentials(userId, credentials);
return credentials;
} else {
credentials = getStoredCredentials(userId);
if (credentials != null && credentials.getRefreshToken() != null) {
return credentials;
}
}
} catch (CodeExchangeException e) {
e.printStackTrace();
// Drive apps should try to retrieve the user and credentials for the current
// session.
// If none is available, redirect the user to the authorization URL.
e.setAuthorizationUrl(getAuthorizationUrl(emailAddress, state));
throw e;
} catch (NoUserIdException e) {
e.printStackTrace();
}
// No refresh token has been retrieved.
String authorizationUrl = getAuthorizationUrl(emailAddress, state);
throw new NoRefreshTokenException(authorizationUrl);
}
}
Кроме того, я передаю правильный файл client_secret.json в моем коде на стороне сервера (в MyClass)- который является клиентом Android.
Пожалуйста помоги!!! Спасибо заранее.
2 ответа
Используйте из этого:
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
private final val TRANSPORT: HttpTransport = new NetHttpTransport()
private final val JSON_FACTORY: JacksonFactory = new JacksonFactory()
GoogleTokenResponse tokenResponse = new GoogleAuthorizationCodeTokenRequest(TRANSPORT, JSON_FACTORY,
CLIENT_ID, CLIENT_SECRET, code, "postmessage").execute();
GoogleIdToken idToken = tokenResponse.parseIdToken();
String gplusId = idToken.getPayload().getSubject();
Вы должны заменить свои значения client_id,client_secret и кода на вышеуказанные переменные.
дополнительная информация находится в текущей ссылке: https://github.com/googleplus/gplus-quickstart-java/blob/master/src/com/google/plus/samples/quickstart/Signin.java
Также вы можете получить библиотеки API Google по этой ссылке:
Вам нужно обменять code
для токена доступа действительно. id_token
предназначен только для вашего клиента и самодостаточен, поэтому его не нужно обменивать на что-либо еще. redirect_uri
что вам нужно отправить вместе с code
в запросе на обмен токену конечная точка точно такая же redirect_uri
значение, которое вы отправили изначально в запросе авторизации, который отправился в конечную точку авторизации (и который зарегистрирован для вашего клиента в консоли API Google), поэтому, глядя на ваш код, он получен с clientSecrets.getDetails().getRedirectUris().get(0);