前言
关于使用Google第三方登录服务端如何进行登录验证。Google登录流程中,服务器的主要工作为验证用户信息以确保此次登录为有效登录,然后让当前登录的用户进行应用服务器的相应流程。另一方面,用户在授权登录后,应用服务器可以通过用户的授权获取到access_token,以便后续访问Google的其他API。
登录流程
根据上面的图所示:
在用户点击”Sign in With Google”的按钮后,客户端将会收到一个来自OAuth服务器并且只能使用一次的code,客户端将该code发送至应用服务器,这里由客户端处理。
应用服务器用该code向Google API服务器请求access_token和refresh_token,这里需要注意:
- 每个code只能使用一次
- 只要code有效,那么每次请求Google都将返回access_token
- 在获取到refresh_token之后,需要将其保存起来用于后续刷新access_token使用(access_token存在过期时间),因为在后续的refresh_token交换请求将返回空
请求access_token和refresh_token的API为
https://oauth2.googleapis.com/token
请求参数有:
参数名 解释 code 用户授权后客户端获取到的code client_id 应用ID client_secret 应用密钥 redirect_uri 在 API控制台页面 配置的授权重定向URI grant_type 固定值:authorization_code Google返回的参数有:
参数名 解释 access_token 用于访问Google API的token expires_in access_token有效期, 单位: 秒 id_token Google数字签名了的用户身份信息 scope access_token授予的访问范围,以空格分隔的、区分大小写的字符串列表表示。 token_type token类型,这里总是返回 Bearer
refresh_token 用于刷新access_token的token 下面以golang为例, 发送请求:
1
2
3
4
5
6
7
8
9
10
11
12var (
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
请求参数有:
参数名 解释 client_id 应用ID client_secret 应用密钥 refresh_token access_token值 grant_type 固定值:refresh_token 返回的参数有:
参数名 解释 access_token 用于访问Google API的token expires_in access_token有效期 token_type token类型,这里总是返回 Bearer
scope access_token授予的访问范围,以空格分隔的、区分大小写的字符串列表表示。 服务器获取到access_token和refresh_token之后便可以开始验证用户信息(idToken)是否有效。
如何验证用户身份信息
用户的身份信息是包含在idToken
中的,idToken
为 JWT 格式。按照 Google官网 介绍,idToken
需要验证的内容有:
- 使用Google公钥确认
idToken
被正确的签名 aud
的值应该与你应用的client_id
相同iss
的值为accounts.google.com
或者https://accounts.google.com
- 有效期尚未过
官网建议的验证方式有两种:
- 使用Google的验证库,包括Java, Node.js, PHP, Python, 当然, Go语言的库为: Golang, 如果使用Go语言的库,需要注意如果你的项目中引入了etcd,那么你需要注意关于etcd GRPC版本所引起的一个老生常谈的问题了,我在使用过程中解决办法是降低了Golang的版本,对应go.mod中为:
1 | replace ( |
关于验证idToken
的代码为:
1 | import ( |
注意:Google建议生产环境使用此方法进行验证
- 调用Google的API进行验证, 验证API为: https://oauth2.googleapis.com/tokeninfo?id_token={idToken}, 如:
https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123
请求方法既可以是POST
也可以是GET
, 需要注意的是, 在收到API回复后只有当StatusCode为http.StatusOK
时才能进行下一步校验。
idToken校验API返回的结果为JSON键值对,如:
1 | { |
注意: 因为在授权过程中和获取access_token的过程中都会获取到idToken,所以服务器验证的idToken既可以由客户端传给服务器,也可以是服务器自己获取。但是在登录过程中,客户端获取到的授权code必须传给服务器
参考资料
Google Sign-In for server-side apps