前言
關(guān)于使用Google第三方登錄服務(wù)端如何進行登錄驗證。Google登錄流程中,服務(wù)器的主要工作為驗證用戶信息以確保此次登錄為有效登錄,然后讓當前登錄的用戶進行應(yīng)用服務(wù)器的相應(yīng)流程。另一方面,用戶在授權(quán)登錄后,應(yīng)用服務(wù)器可以通過用戶的授權(quán)獲取到access_token,以便后續(xù)訪問Google的其他API。
登錄流程

根據(jù)上面的圖所示:
在用戶點擊"Sign in With Google"的按鈕后,客戶端將會收到一個來自O(shè)Auth服務(wù)器并且只能使用一次的code,客戶端將該code發(fā)送至應(yīng)用服務(wù)器,這里由客戶端處理。
應(yīng)用服務(wù)器用該code向Google API服務(wù)器請求access_token和refresh_token,這里需要注意:
a. 每個code只能使用一次
b. 只要code有效,那么每次請求Google都將返回access_token
c. 在獲取到refresh_token之后,需要將其保存起來用于后續(xù)刷新access_token使用(access_token存在過期時間),因為在后續(xù)的refresh_token交換請求將返回空
請求access_token和refresh_token的API為https://oauth2.googleapis.com/token
請求參數(shù)有:
| 參數(shù)名 | 解釋 |
|---|---|
| code | 用戶授權(quán)后客戶端獲取到的code |
| client_id | 應(yīng)用ID |
| client_secret | 應(yīng)用密鑰 |
| redirect_uri | 在 API控制臺頁面 配置的授權(quán)重定向URI |
| grant_type | 固定值:authorization_code |
Google返回的參數(shù)有:
| 參數(shù)名 | 解釋 |
|---|---|
| access_token | 用于訪問Google API的token |
| expires_in | access_token有效期, 單位: 秒 |
| id_token | Google數(shù)字簽名了的用戶身份信息 |
| scope | access_token授予的訪問范圍,以空格分隔的、區(qū)分大小寫的字符串列表表示。 |
| token_type | token類型,這里總是返回Bearer
|
| refresh_token | 用于刷新access_token的token |
下面以golang為例, 發(fā)送請求:
var (
clientId = "your clientId"
clientSecret = "your clientSecret"
code = "your code"
redirectUri = "your redirectUri"
url = "https://oauth2.googleapis.com/token"
)
reader := strings.NewReader(fmt.Sprintf("client_id=%s&client_secret=%s&redirect_uri=%s&grant_type=authorization_code&code=%s", clientId, clientSecret, redirectUri, code))
response, err := http.Post(url, "application/x-www-form-urlencoded", reader)
handleResponse(response)
handleErr(err)
刷新access_token的API為: https://oauth2.googleapis.com/token
請求參數(shù)有:
| 參數(shù)名 | 解釋 |
|---|---|
| client_id | 應(yīng)用ID |
| client_secret | 應(yīng)用密鑰 |
| refresh_token | access_token值 |
| grant_type | 固定值:refresh_token |
返回的參數(shù)有:
| 參數(shù)名 | 解釋 |
|---|---|
| access_token | 用于訪問Google API的token |
| expires_in | access_token有效期 |
| token_type | token類型,這里總是返回Bearer
|
| scope | access_token授予的訪問范圍,以空格分隔的、區(qū)分大小寫的字符串列表表示。 |
- 服務(wù)器獲取到access_token和refresh_token之后便可以開始驗證用戶信息(idToken)是否有效。
如何驗證用戶身份信息
用戶的身份信息是包含在idToken中的,idToken為 JWT 格式。按照 Google官網(wǎng) 介紹,idToken需要驗證的內(nèi)容有:
- 使用Google公鑰確認
idToken被正確的簽名 -
aud的值應(yīng)該與你應(yīng)用的client_id相同 -
iss的值為accounts.google.com或者https://accounts.google.com - 有效期尚未過
官網(wǎng)建議的驗證方式有兩種:
- 使用Google的驗證庫,包括Java, Node.js, PHP, Python, 當然, Go語言的庫為: Golang, 如果使用Go語言的庫,需要注意如果你的項目中引入了etcd,那么你需要注意關(guān)于etcd GRPC版本所引起的一個老生常談的問題了,我在使用過程中解決辦法是降低了Golang的版本,對應(yīng)go.mod中為:
replace (
google.golang.org/grpc => google.golang.org/grpc v1.26.0
google.golang.org/api => google.golang.org/api v0.14.0
)
關(guān)于驗證idToken的代碼為:
import (
"http"
"github.com/dghubble/oauth1"
"google.golang.org/api/option"
"google.golang.org/api/oauth2/v2"
)
oatuService, err := oauth2.NewService(context.Background(), option.WithHTTPClient(http.DefaultClient))
if err != nil {
log4go.Errorf("%v\n", err)
return nil, err
}
tokenInfoCall := oatuService.Tokeninfo()
tokenInfoCall.IdToken(googleToken)
tokenInfo, err := tokenInfoCall.Do()
//根據(jù)tokenInfo中的字段進行驗證
注意:Google建議生產(chǎn)環(huán)境使用此方法進行驗證
- 調(diào)用Google的API進行驗證, 驗證API為: https://oauth2.googleapis.com/tokeninfo?id_token={idToken}, 如:
https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123
請求方法既可以是POST也可以是GET, 需要注意的是, 在收到API回復(fù)后只有當StatusCode為http.StatusOK時才能進行下一步校驗。
idToken校驗API返回的結(jié)果為JSON鍵值對,如:
{
// 這6個字段是所有idToken都包含的
"iss": "https://accounts.google.com", //token簽發(fā)者,值為https://accounts.google.com或者accounts.google.com
"sub": "110169484474386276334", //用戶在該Google應(yīng)用中的唯一標識,類似于微信的OpenID
"azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com", //具體我也不知道,猜測與aud相同,都是應(yīng)用的client_id
"aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com", //client_id
"iat": "1433978353", //簽發(fā)時間
"exp": "1433981953", //過期時間
// 下面的字段只有當用戶授權(quán)了"profile"和"email"權(quán)限后才會出現(xiàn)
"email": "testuser@gmail.com", //用戶郵箱
"email_verified": "true", //郵箱是否已驗證
"name" : "Test User", //用戶名
"picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg", //用戶頭像
"given_name": "Test", //名
"family_name": "User", //姓
"locale": "en" //所屬地區(qū)
}
注意: 因為在授權(quán)過程中和獲取access_token的過程中都會獲取到idToken,所以服務(wù)器驗證的idToken既可以由客戶端傳給服務(wù)器,也可以是服務(wù)器自己獲取。但是在登錄過程中,客戶端獲取到的授權(quán)code必須傳給服務(wù)器
參考資料
Google Sign-In for server-side apps
Authenticate with a backend server
Using OAuth 2.0 to Access Google APIs