用户从 Android 中的 Gmail 登录后如何获取访问令牌?
Posted
技术标签:
【中文标题】用户从 Android 中的 Gmail 登录后如何获取访问令牌?【英文标题】:How to get access token after user is signed in from Gmail in Android? 【发布时间】:2016-03-04 01:45:16 【问题描述】:我关注Google Sign in for android。现在我可以获得 idToken,但我之前使用的后端服务器期待 access Token,因为我之前使用的是 Google+ 登录。现在我不想改变我的服务器端。但是我仍然如何使用 Google 登录并在我的 android 应用程序中获取访问令牌,以便我可以验证我的用户到我的后端服务器。
我之前使用的是 GooglePlay Service 7.5.0,现在我使用的是最新的 GooglePlay Service 8.3.0。
【问题讨论】:
你试过 GoogleSignInAccount object.getServerAuthCode() 吗? 看起来好像getServerAuthCode()
为您提供了您上传到服务器的一次性验证码。您的服务器使用授权码请求 AccessToken。
您想使用 G+ 登录,登录后您希望 Google auth tocken
【参考方案1】:
这是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'
【讨论】:
【参考方案2】:这是我使用 Kotlin 的方法,(这是我在 *** 上的第一个答案,如果有问题、遗漏或者我可以做得更好,请告诉我)
关于登录活动
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):
@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())
)
【讨论】:
【参考方案3】:BNK 在大多数情况下都能找到它。 Activity 类与 BNK 的回答相同,只是在 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();
...
得到的响应与 BNK 的回答相同
"access_token": "ya29.CjBgA_I58IabCJ...remainingAccessTokenHere",
"token_type": "Bearer",
"expires_in": 3577,
"id_token": "eyJhbGciOiJS...veryLongStringHere"
现在将此 access_token 发送到您的后端服务器以进行身份验证,就像您在使用 GoogleAuthUtil 和 PlusAPI 时所做的那样。
希望这会有所帮助 :) 特别感谢 BNK!
【讨论】:
谢谢 :),我有空会检查文档。自从我发布答案以来,也许现在发生了一些变化。 啊,你的回复没有“refresh_token”。您是否使用与我相同的 API 端点?那是'googleapis.com/oauth2/v4/token' 是的,我确实使用了相同的端点。嗯,想知道为什么我没有得到 refresh_token。之前没注意到:| 呃,更多链接会有不同的情况,请阅读developers.google.com/identity/protocols/OpenIDConnect和developers.google.com/identity/protocols/OAuth2ForDevices和developers.google.com/identity/protocols/OAuth2InstalledApp。下周,我将审查我的代码 谢谢!如果你发现了什么,请告诉我!【参考方案4】:感谢@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;
在后台线程上运行此方法。另外client_id
和client_secret
您需要从 Google API 控制台获取。
【讨论】:
【参考方案5】:我找到了一种获取访问令牌的方法无需 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 写代码,不用它你也可以写代码。
在“Account”对象中,您可以使用“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 线程中运行它。现在您可以将此令牌发送到您的服务器。 ?
【讨论】:
GoogleAuthUtil.getToken - 很久以前就被弃用了。最终将停止工作。 @OleksandrAlbul 官方文档说它不是一个弃用的 API。 developers.google.com/android/reference/com/google/android/gms/… @yuriel 现在可以了。【参考方案6】:以防其他人在发出最终请求以从 google 获取访问令牌时遇到问题。以下是截至 2018 年 11 月 11 日的经过测试和工作的方法。使用改造 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);
【讨论】:
@Suresh 代码是 ServerAuthCode: signInOptions.requestServerAuthCode(getString(R.string.default_web_client_id)); .... onSuccess GoogleSignInAccount.getServerAuthCode(); 【参考方案7】:根据您的要求,您可以使用以下代码:
首先,确保您有一个有效的 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 将身份验证代码发送到您应用的后端):
Google Sign-In for Android - Enabling Server-Side Access
更新:来自 cmets,如果您想直接从 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"
【讨论】:
是的,这就是我所追求的。 100 个代表的赏金是用于工作代码示例。使用来自GoogleSignInOptions
的详细信息并返回GoogleSignInAccount
。谢谢
优秀的答案。即使在官方文档中也找不到任何解决此问题的方法。想知道为什么 android 没有直接公开一个公共 api 来获取访问令牌?
@RobertoFrontado 我在请求中遇到了类似的问题。该请求缺少“id_token”参数。一旦我添加请求成功。在下面查看我的答案以获取详细信息(仅在此答案中添加了一行)!
我提出了同样的要求 - grant_type
、client_id
、client_secret
、code
和 id_token
,但我仍然收到错误消息 - "error":"invalid_client","error_description":"Unauthorized"
。客户端 ID 和密码是 Web 客户端的。有什么想法???
谢谢。获取访问令牌的完美方式。以上是关于用户从 Android 中的 Gmail 登录后如何获取访问令牌?的主要内容,如果未能解决你的问题,请参考以下文章
需要让用户使用多个凭据登录,与使用 Gmail 服务中的其他帐户功能登录相同 - Laravel
在flutter中使用google auth的逻辑(一个用户只能从一个gmail和同一设备登录)?