[英]How to get access token after user is signed in from Gmail in Android?
我正在關注Android 版 Google 登錄。 現在我可以獲取idToken但我之前使用的后端服務器正在等待訪問令牌,因為我之前使用的是 Google+ 登錄。 現在我不想改變我的服務器端。 但是我仍然如何使用 Google 登錄並在我的 android 應用程序中獲取訪問令牌,以便我可以驗證我的用戶到我的后端服務器。
我以前使用的是 GooglePlay 服務 7.5.0,現在我使用的是最新的 8.3.0 的 GooglePlay 服務。
根據您的要求,您可以使用以下代碼:
首先,確保您擁有有效的Web OAuth 2.0 客戶端 ID:
<!-- 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>
然后在 Activity 類中:
@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);
}
}
}
你可以在下面的ServerAuthCodeActivity.java看到完整的代碼
如果您使用該示例,結果將類似於以下屏幕截圖:
然后,您可以按照以下 Google 文檔中提到的步驟操作(從第 3步開始。使用 HTTPS POST 將身份驗證代碼發送到您的應用程序的后端):
更新:根據評論,如果您想直接從 android 客戶端應用程序獲取訪問令牌,請使用以下示例代碼(替換為您的 client_id、client_secret 和 auth 代碼)
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();
}
}
});
請使用compile 'com.squareup.okhttp:okhttp:2.6.0'
(ver 3-RC1 會有不同的類)
成功響應后,您將在 logcat 中獲得以下信息:
I/onResponse: {
"expires_in": 3600,
"token_type": "Bearer",
"refresh_token": "1\/xz1eb0XU3....nxoALEVQ",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjQxMWY1Ym......yWVsUA",
"access_token": "ya29.bQKKYah-........_tkt980_qAGIo9yeWEG4"
}
BNK 在很大程度上掌握了它。 Activity 類與 BNKs answer 相同,只是在onActivityResult()
方法中獲得GoogleSignInAccount
后添加 OkHttp 部分。
但是我仍然遇到 OkHttp 請求部分的錯誤。 最后在 Postman 中進行了一些測試(和部分運氣)后,我發現我缺少 id_token 參數。 OkHttp 請求缺少一個參數,即 id_token。 使用您從 GoogleSignInAccount 獲得的 ID 令牌,如下所示
GoogleSignInAccount acct = result.getSignInAccount();
String idTokenString = acct.getIdToken();
現在使用這個 idTokenString 以及 BNK 答案的 OkHttp 部分中的所有參數,有點像這樣
...
RequestBody requestBody = new FormEncodingBuilder()
.add("grant_type", "authorization_code")
.add("client_id", "alpha-numeric-string-here.apps.googleusercontent.com")
.add("client_secret", "{clientSecret}")
.add("redirect_uri","")
.add("code", "4/4-alphabetic-string-here")
.add("id_token", idTokenString) // Added this extra parameter here
.build();
...
得到的回應與 BNKs 的回應相同
{
"access_token": "ya29.CjBgA_I58IabCJ...remainingAccessTokenHere",
"token_type": "Bearer",
"expires_in": 3577,
"id_token": "eyJhbGciOiJS...veryLongStringHere"
}
現在將此 access_token 發送到您的后端服務器以進行身份驗證,就像您在 GoogleAuthUtil 和 PlusAPI 時代以前所做的那樣。
希望這會有所幫助:) 特別感謝 BNK!
這是我使用Kotlin 的方法,(這是我在 StackOverflow 上的第一個答案,如果有什么問題、遺漏或我可以做得更好,請告訴我)
關於登錄活動
private fun configureGoogleSignIn() {
mGoogleSignInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestServerAuthCode(getString(R.string.server_client_id_oauth))
.requestEmail()
.build()
mGoogleSignInClient = GoogleSignIn.getClient(this, mGoogleSignInOptions)
}
private fun signInWithGoogle() {
val signInIntent: Intent = mGoogleSignInClient.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
}
確保在 OnCreate 上調用 configureGoogleSignIn() 函數
然后得到結果
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
callbackManager?.onActivityResult(requestCode, resultCode, data)
if (requestCode == RC_SIGN_IN) {
val tag = "onActivityResult RC_SIGN_IN"
val task: Task<GoogleSignInAccount> = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
val account = task.getResult(ApiException::class.java)
firebaseAuthWithGoogle(account!!)
getIdTokenFromFirebaseAuth()
var acct = GoogleSignIn.getLastSignedInAccount(this)
if (acct != null) {
var personName = acct.displayName
firstName = acct.givenName!!
lastName = acct.familyName!!
userEmail = acct.email!!
authCode = acct.serverAuthCode!! //THIS is what you looking for
googleIdToken2 = acct.idToken!!
Log.d(tag, authCode)
Log.d(tag, googleIdToken2)
var personId = acct.id
//todo pegar foto do google e por no cadastro do usuario
var personPhoto = acct.photoUrl
spinner.visibility = View.GONE
getGoogleAccessToken()
}
} catch (e: ApiException) {
spinner.visibility = View.GONE
infoToUserTextView.text = getString(R.string.ops_we_had_a_problem)
}
}
}
然后調用 Google API(我正在使用 Retrofit),使用這個接口 make :
@FormUrlEncoded
@POST
fun getAccessTokenGoogle(
@Url url: String,
@Field("grant_type") grant_type: String,
@Field("client_id") client_id: String,
@Field("client_secret") client_secret: String,
@Field("redirect_uri") redirect_uri: String,
@Field("code") authCode: String,
@Field("id_token") id_token: String
):Call<GoogleSignInAccessTokenDataClass>
這是 GoogleSignInAccessTokenDataClass
data class GoogleSignInAccessTokenDataClass(
val access_token: String,
val expires_in: Int,
val id_token: String,
val token_type: String
)
調用登錄活動
private fun getGoogleAccessToken(){
val call = RetrofitGet().userInfoGson().getAccessTokenGoogle(
grant_type = "authorization_code", client_id = getString(R.string.server_client_id_oauth),
client_secret = getString(R.string.server_client_secret_oauth), redirect_uri = "",
authCode = authCode, id_token =googleIdToken2, url = googleTokenUrl
)
call.enqueue(object : Callback<GoogleSignInAccessTokenDataClass>{
val tag = "getGoogleAccessToken"
override fun onFailure(call: Call<GoogleSignInAccessTokenDataClass>, t: Throwable) {
Log.e(tag, t.toString())
}
override fun onResponse(
call: Call<GoogleSignInAccessTokenDataClass>,
response: Response<GoogleSignInAccessTokenDataClass>
) {
if (response.isSuccessful){
val responseBody = response.body()
googleAccessToken = responseBody!!.access_token
Log.d(tag, googleAccessToken)
}else{
try {
val responseError = response.errorBody()!!.string()
Log.e(tag, responseError)
}catch (e:Exception){Log.e(tag, e.toString())}
}
}
})
}
感謝@BNK,他提供了有效的解決方案。 這是如何從“身份驗證代碼”獲取“訪問令牌”的官方指南: https : //developers.google.com/identity/protocols/OAuth2WebServer#exchange-authorization-code
在這里,我想為我的解決方案提供純 Android SDK 類。 如果您不想為此目的添加花哨的庫:
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;
}
在后台線程上運行此方法。 您還需要從 Google API 控制台獲取client_id
和client_secret
。
萬一其他人在提出從谷歌獲取訪問令牌的最終請求時遇到問題。 以下是截至 11-01-2018 的經過測試且有效的方法。 使用改造2。
首先,這是關於令牌交換端點的谷歌文檔的鏈接: 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);
}
然后像這樣調用它:
Call<GoogleAuthData> call = RetroClient.getGoogleService().getToken(
"authorization_code", context.getString(R.string.server_client_id),
context.getString(R.string.server_client_secret), "", authCode);
我找到了一種無需idToken、代碼、秘密或任何請求即可獲取訪問令牌的方法(例如發布到“ https://www.googleapis.com/oauth2/v4/token ”)。 您只需要“客戶 ID”。 請按照以下步驟操作:
使用“GoogleSignIn”登錄並獲取“Account”對象。
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) }
這里我使用 RxJava 來編寫代碼,你可以不用它來編寫代碼。
在“帳戶”對象中,您可以使用“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) } }
函數“GoogleAuthUtil::getToken”發出請求,因此您無法在 UI 線程中運行它。 現在您可以將此令牌發送到您的服務器。 👍
這是在Android中獲取accessToken的最簡單方法
val httpTransport = AndroidHttp.newCompatibleTransport()
val jsonFactory: JsonFactory = JacksonFactory.getDefaultInstance()
tokenResponse = GoogleAuthorizationCodeTokenRequest(
httpTransport,
jsonFactory,
"https://www.googleapis.com/oauth2/v4/token",
clientId,
clientSecret,
account.serverAuthCode,
"" //optional param (redirect url)
).execute()
在后台線程上運行它
Android 使用這些庫
implementation 'com.google.android.gms:play-services-auth:19.0.0'
implementation('com.google.api-client:google-api-client-android:1.23.0') {
exclude group: 'org.apache.httpcomponents'
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.