导言
- 关于
OAuth2.0
是什么,可以参考阮一峰老师的文章 — OAuth 2.0 的一个简单解释 - 以下将使用
Go
语言,通过OAuth2.0
,达到第三方登录Github的目的。并在登录成功后,展示用户Github
的信息。 - 最终源码参考: Go语言第三方登录Github
原理
第三方网站登录 Github
的原理如下:
- 用户在第三方网站点击
Github授权登录
后,浏览器跳转到Github
登录页面。 - 用户在
Github
登录成功后,浏览器重定向回第三方网站。此时浏览器会携带一个code
。 - 第三方网站服务器通过
code
向Github
索取token
。 Github
返回token
。- 第三方网站服务器收到
token
后,就可以通过token
获取用户信息了。
不懂也没关系,下面将循序渐进的进行实现。
实战
登记阶段
第三方网站要登录 Github
必须进行登记。访问这个网址,可以进行登记。
上面的 Authorization callback URL
就是 用户在 Github
登录成功后重定向回的 url
。
登记成功后,可以看到以下信息:
Github
会通过识别这些信息,判断第三方网站能否进行合法登录。
在编写代码阶段,我会把这些信息放在一个结构体
Conf
中。结构体代码如下:
type Conf struct { ClientId string // 对应: Client ID ClientSecret string // 对应: Client Secret RedirectUrl string // 对应: Authorization callback URL }```
接下来,我们进行代码编写。
页面展示
前端代码如下:
views/hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="https://github.com/login/oauth/authorize?client_id={{.ClientId}}&redirect_uri={{.RedirectUrl}}">Github 第三方授权登录</a>
</body>
</html>
上面的 ClientId
和 RedirectUrl
将在后续传入。
后端代码如下:
main.go
package main
type Conf struct {
ClientId string
ClientSecret string
RedirectUrl string
}
var conf = Conf{
ClientId: "7e5fe351bc9b131c6f2a",
ClientSecret: "9fd22c13ae790685c59e3fb4a9b444b75b506a5b",
RedirectUrl: "http://localhost:9090/oauth/redirect",
}
func Hello(w http.ResponseWriter, r *http.Request) {
// 解析指定文件生成模板对象
var temp *template.Template
var err error
if temp, err = template.ParseFiles("views/hello.html"); err != nil {
fmt.Println("读取文件失败,错误信息为:", err)
return
}
// 利用给定数据渲染模板(html页面),并将结果写入w,返回给前端
if err = temp.Execute(w, conf); err != nil {
fmt.Println("读取渲染html页面失败,错误信息为:", err)
return
}
}
func main() {
http.HandleFunc("/", Hello)
if err := http.ListenAndServe(":9090", nil); err != nil {
fmt.Println("监听失败,错误信息为:", err)
return
}
}
此时,开启 Go
服务器后,打开浏览器访问 http://localhost:9090
,将会有以下结果:
获取 code
点击上面的 Github 第三方授权登录
后,会发现浏览器跳转到了Github
登录页面。
当成功登录了
Github
后,浏览器跳转回了Authorization callback URL
,而且还在后面拼接了一串字符串。
这里, 2549ef9e7720ec61354a
就是 Github
给我们返回的 code
。
显然,此时我们已经知道 code
了,那服务器怎么获取?
这里采用了一个方法,就是在服务器再编写一个 handler
,代码如下:
// 这个函数下面还会用到,请记住函数名
func Oauth(w http.ResponseWriter, r *http.Request) {
var err error
var code = r.URL.Query().Get("code") // 获取code
// (省略的代码下面会说)...
}
并在main.go
中加入一句代码:
http.HandleFunc("/oauth/redirect", Oauth) // 这个和 Authorization callback URL 有关
这样,当用户在 Github
登录成功后,服务器就能得到 code
了。
接下来,服务器就可以通过 code
去获取用户的 token
了。
获取用户 token
要想获得 token
,必须访问Github
提供 token
的接口。这个接口其实就是一个 url
,以下代码可以获得这个 url
。
// 通过code获取token认证url
func GetTokenAuthUrl(code string) string {
return fmt.Sprintf(
"https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s",
conf.ClientId, conf.ClientSecret, code,
)
}
直接访问这个 url
就能获得 token
了,代码如下:
// 获取 token
func GetToken(url string) (*Token, error) {
// 形成请求
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, url, nil); err != nil {
return nil, err
}
req.Header.Set("accept", "application/json")
// 发送请求并获得响应
var httpClient = http.Client{}
var res *http.Response
if res, err = httpClient.Do(req); err != nil {
return nil, err
}
// 将响应体解析为 token,并返回
var token Token
if err = json.NewDecoder(res.Body).Decode(&token); err != nil {
return nil, err
}
return &token, nil
}
type Token struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"` // 这个字段没用到
Scope string `json:"scope"` // 这个字段也没用到
}
小提示:
token
中,只有AccessToken
字段是有用的。
于是,OAuth
函数变成了这样:
func Oauth(w http.ResponseWriter, r *http.Request) {
var err error
// 获取 code
var code = r.URL.Query().Get("code")
// 获取 token
var tokenAuthUrl = GetTokenAuthUrl(code)
var token *Token
if token, err = GetToken(tokenAuthUrl); err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v",token)
// ....
}
接下来,我们测试一下。
重启 Go
服务器,继续访问 http://localhost:9090 ,点击链接并登录。
如果没有出现登录页面,那就是你的电脑的
cookies
中已经保存了登录状态
在服务器端会输出以下信息:
至此,第三方网站已经成功登录了 Github
。
接下来,我们利用这个 token
,就能获取用户的Github
信息了。
获取用户信息
代码如下:
// 获取用户信息
func GetUserInfo(token *Token) (map[string]interface{}, error) {
// 形成请求
var userInfoUrl = "https://api.github.com/user" // github用户信息获取接口
var req *http.Request
var err error
if req, err = http.NewRequest(http.MethodGet, userInfoUrl, nil); err != nil {
return nil, err
}
req.Header.Set("accept", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("token %s", token.AccessToken))
// 发送请求并获取响应
var client = http.Client{}
var res *http.Response
if res, err = client.Do(req); err != nil {
return nil, err
}
// 将响应的数据写入 userInfo 中,并返回
var userInfo = make(map[string]interface{})
if err = json.NewDecoder(res.Body).Decode(&userInfo); err != nil {
return nil, err
}
return userInfo, nil
}
以上代码将会返回一个 用户信息map
。
展示用户信息
为了方便,我们继续在Oauth
函数中添加代码。
最终,Oauth
函数如下:
func Oauth(w http.ResponseWriter, r *http.Request) {
var err error
// 获取 code
var code = r.URL.Query().Get("code")
// 通过 code, 获取 token
var tokenAuthUrl = GetTokenAuthUrl(code)
var token *Token
if token, err = GetToken(tokenAuthUrl); err != nil {
fmt.Println(err)
return
}
// 通过token,获取用户信息
var userInfo map[string]interface{}
if userInfo, err = GetUserInfo(token); err != nil {
fmt.Println("获取用户信息失败,错误信息为:", err)
return
}
// 将用户信息返回前端
var userInfoBytes []byte
if userInfoBytes, err = json.Marshal(userInfo); err != nil {
fmt.Println("在将用户信息(map)转为用户信息([]byte)时发生错误,错误信息为:", err)
return
}
w.Header().Set("Content-Type", "application/json")
if _, err = w.Write(userInfoBytes); err != nil {
fmt.Println("在将用户信息([]byte)返回前端时发生错误,错误信息为:", err)
return
}
}
最终结果展示
- 在所有代码编写完毕后,重启
Go
服务器。 - 访问 http://localhost:9090 。
2. 点击
Github 第三方授权登录
链接。
3. 在
Github
登录成功后。
隔了一会儿,跳转回第三方网站。
以上就是用户的
Github
信息了。
项目源码
最后
感谢阮一峰老师。