在网页中接入谷歌(Google)帐号登录

陪她去流浪 桃子 2019年07月21日 编辑 阅读次数:20152

不久前,我在自己的博客系统中接入了谷歌登录,最近正好有一个项目也要接入谷歌登录,所以今天写一篇文章简要介绍一下接入方式。

谷歌登录(Google Sign-In)官方的介绍在这里:Add Google Sign-In to Your Web App。 接入谷歌登录不需要什么审核,也不需要提交任何资源,相对于国内来说,这个流程足够简单省事儿不少。

接入谷歌登录的几个步骤

创建 ClientID

谷歌登录使用 OAuth 2.0 进行认证,所以需要先创建 ClientID。

首先进入到 https://console.developers.google.com,在左上角选择一个自己的项目,没有的话,在弹出的窗口中新建一个。

项目查看页

新建项目页

然后进入到OAuth 用户同意屏幕填写在要求用户登录时展示的相关信息。

用户同意屏幕

在这里,可以填写你的应用的名字、图标等信息。

最重要的是“Authorized JavaScript origins”这个字段,谷歌只会允许在这个列表内的域名使用指定的ClientID。所以,这里填写你的网站域名(带协议)。回车添加,然后保存。

然后回到左侧第一栏的“Credentials”,选择“Create Credentials”创建一个“OAuth Client ID”

创建 OAuth Client ID

应用类型选择“Web Application”,然后填写名字,其它的可以不用填。点击创建,ClientID就创建好了。

新的ID

指定你的ClientID

1
<meta name="google-signin-client_id" content="这里是你的ID.apps.googleusercontent.com">

加载谷歌平台库

1
<script src="https://apis.google.com/js/platform.js" async defer></script>

添加谷歌登录按钮

1
<div class="g-signin2" data-onsuccess="onSignIn"></div>

登录回调处理

注意到上面的按钮中一个特别的data-onsuccess="onSignIn",在登录成功后,这个函数会被回调。

1
2
3
4
5
6
7
function onSignIn(googleUser) {
  var profile = googleUser.getBasicProfile();
  console.log('ID: ' + profile.getId()); // Do not send to your backend! Use an ID token instead.
  console.log('Name: ' + profile.getName());
  console.log('Image URL: ' + profile.getImageUrl());
  console.log('Email: ' + profile.getEmail()); // This is null if the 'email' scope is not present.
}

前端就可以通过上面的代码拿到登录用户的相关信息,profile.getId()是用户的唯一ID。

登出用户

增加一个可点击的按钮/链接,调用 signOut() 即可:

1
2
3
4
5
6
7
8
9
<a href="#" onclick="signOut();">Sign out</a>
<script>
  function signOut() {
    var auth2 = gapi.auth2.getAuthInstance();
    auth2.signOut().then(function () {
      console.log('User signed out.');
    });
  }
</script>

完整可用的代码

当完成上面的步骤后,就可以得到官方首页展示的代码,可以相应修改后直接使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<html lang="en">
  <head>
    <meta name="google-signin-scope" content="profile email">
    <meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com">
    <script src="https://apis.google.com/js/platform.js" async defer></script>
  </head>
  <body>
    <div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div>
    <script>
      function onSignIn(googleUser) {
        // Useful data for your client-side scripts:
        var profile = googleUser.getBasicProfile();
        console.log("ID: " + profile.getId()); // Don't send this directly to your server!
        console.log('Full Name: ' + profile.getName());
        console.log('Given Name: ' + profile.getGivenName());
        console.log('Family Name: ' + profile.getFamilyName());
        console.log("Image URL: " + profile.getImageUrl());
        console.log("Email: " + profile.getEmail());

        // The ID token you need to pass to your backend:
        var id_token = googleUser.getAuthResponse().id_token;
        console.log("ID Token: " + id_token);
      }
    </script>
  </body>
</html>

安全地与后端API接口通信

上面在代码中通过profile.getId()能拿到用户的唯一ID号,可以用它来唯一标识用户。但是:不要直接把它传递给后端接口,因为后端程序无法确定这个ID是用户真正通过登录得到的,还是随意手写的其他某人的ID。

取面代之,使用上面的 ID Token:

1
2
3
// The ID token you need to pass to your backend:
var id_token = googleUser.getAuthResponse().id_token;
console.log("ID Token: " + id_token);

这个 Token 是一个使用谷歌私钥签名的 JWT Token。 JWT Token 就是一个字符串,它由 3 部分组成:头部,数据,签名。 头部是一个JSON对象,用于描述签名算法。数据也是一个JSON对象,保存公共数据和用户数据。签名是前两部分的校验和,用于确保该 Token 未被修改。 3 个部分通过 Base64 + HMAC(常用的算法之一) 算法合并产生出一个字符串。

然后通过发送一个请求,发送此 Token 给后端。一般是在请求头部中设置Authorization: Bearer id_token的方式把此Token传递给后端。

后端解析 ID Token 中的用户数据

在收到前端发送过来的 Token 后,首先需要校验此 Token 是否合法,然后后端解析此Token,取得相关数据,查找相关的用户,创建自己的会话信息。

校验有以下几个步骤

  • 使用签发方(这里的谷歌登录)的公钥验证此 JWT 是否由签发方签发
  • 判断 aud 字段是否是你的 ClientID
  • 验证 iss 字段是否是accounts.google.comhttps://accounts.google.com
  • 验证 exp 字段确保此 Token 尚未过期
  • 如果是企业用户/G Suite Domain用户,还需求验证 hd 是否为公司域名

取用户数据

如果上面所有的条件都满足,那么,这是一个合法的 Token,可以完全地取相关的数据了。

  • sub 是用户的唯一ID。即上面的profile.getId()
  • email 是用户的 Email
  • name 是用户的名字
  • picture 是用户的头像

创建自己的会话

如果这个sub对应的用户已经存在,则创建此用户的会话。如果不存在,则可以创建一个新的用户,关联此sub

解析 JWT Token 的库

谷歌官方提供了库来解析:Google API Client Library。 但是这个库包含了太多我用不到的内容,所以我自己写了一个:Google IDToken Verifier。实现了所有谷歌登录要求的过程。

2021-04-23 更新:经评论用户“7”的推荐,官方也有一个非常类似的库,建议使用:https://google.golang.org/api/idtoken

使用方式极其简单:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"fmt"

	googleidtokenverifier "github.com/movsb/google-idtoken-verifier"
)

func main() {
	token := "eyJhbGciOiJSUzI1NiIsImtpZCI6ImE0MzEzZTdmZDFl..."
	clientID := "YOUR_CLIENT_ID.apps.googleusercontent.com"
	claims, err := googleidtokenverifier.Verify(token, clientID)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Iss:\t%s\nSub:\t%s\nEmail:\t%s\nName:\t%s\nDomain:\t%s\n",
		claims.Iss, claims.Sub, claims.Email, claims.Name, claims.Domain)
}

注意:代码会在内部获取谷歌的公钥,所以,请确保这段代码在你的服务器上是“可行的”。

参考资源

标签:JWT · 谷歌 · 登录