Cwww3's Blog

Record what you think

0%

OAuth

OAuth

  • 作用 让”客户端”安全可控地获取”用户”的授权,与”服务商提供商”进行互动。
  • 思路 OAuth在”客户端”与”服务提供商”之间,设置了一个授权层(authorization layer) 。”客户端”不能直接登录”服务提供商”,只能登录授权层,以此将用户与客户端区分开来。”客户端”登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。”客户端”登录授权层以后,”服务提供商”根据令牌的权限范围和有效期,向”客户端”开放用户储存的资料。

    OAuth 2.0 运行流程

image.png
  • A : 用户打开客户端以后,客户端要求用户给予授权。
  • B : 用户同意给予客户端授权。
  • C : 客户端使用上一步获得的授权,向认证服务器申请令牌。
  • D : 认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
  • E : 客户端使用令牌,向资源服务器申请获取资源。
  • F : 资源服务器确认令牌无误,同意向客户端开放资源。

OAuth 2.0 用户授权给客户端的方式

注意 不管哪一种授权方式,第三方应用申请令牌之前,都必须先到系统备案,说明自己的身份,拿到两个身份识别码:

客户端 ID(client ID)和客户端密钥(client secret)

  • 授权码模式(authorization code)
  • 先申请一个授权码,再通过授权码获取令牌。适用于有后端的Web应用,最常用,安全性最高。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
  • 第一步 A 网站提供一个链接,用户点击后就会跳转到 B 网站,授权用户数据给 A 网站使用。示意链接:
image.png
  • response_type=code 表示要求返回授权码,client_id 表示 是谁在请求, redirect_uri 表示B接收或拒绝A发起的请求后,跳转的地址 scope = read 表示要求的权限范围是只读。

  • 第二步 用户点击链接跳转后,B网站会要求用户登录,并询问是否同意给予A的授权,同意后,跳转到redirect_uri指定的网址,跳转时,会携带一个授权码。 示意链接 :

image.png
  • 第三步 A拿到授权码后,就可以在后端向B网站请求令牌。示意链接:
image.png
  • client_id 和 client_secret 用来让B确认A的身份,client_secret 是保密的,因此只能在后端发请求,grant_type=AUTHORIZATION_CODE 表示采用授权的方式是授权码方式,code 是之前拿到的授权码,redirect_uri 是令牌拿到后的回调网址

  • 第四步 B 网站收到请求以后,就会颁发令牌。具体做法是向redirect_uri指定的网址,发送一段 JSON 数据。示意数据 :

image.png
  • access_token 就是请求数据的令牌,expires_in 表示令牌过期时间 ,refresh_token就是刷新令牌的令牌

  • 简化模式(implicit)

  • 密码模式(resource owner password credentials)

  • 客户端模式(client credentials)

使用令牌

  • A 网站拿到令牌以后,就可以向 B 网站的 API 请求数据了。
  • 此时,每个发到 API 的请求,都必须带有令牌。具体做法是在请求的头信息,加上一个Authorization字段,令牌就放在这个字段里面。

应用

github配置

image-20210410004233566

公网访问本地应用

1
./ding -config=./ding.cfg -subdomain=cwww3 8080
image-20210410004313240

代码

  • 本地程序
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package main

import (
"bytes"
"encoding/json"
"fmt"
"github.com/labstack/echo"
"github.com/labstack/gommon/log"
"io/ioutil"
"net/http"
)

const (
ClientId = "clientId"
ClientSecret = "clientSecret"
)

// token请求体
type tokenReq struct {
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Code string `json:"code"`
}

// token响应体
type tokenResp struct {
AccessToken string `json:"access_token"`
Scope string `json:"scope"`
TokenType string `json:"token_type"`
}

func main() {
e := echo.New()
// 触发github授权
e.GET("/authorization", authorization)
// 处理回调 获取用户信息
e.GET("/callback", getUserInfo)
// 监听
if err := e.Start(fmt.Sprintf("127.0.0.1:%d", 8080)); err != nil {
fmt.Printf("server close, err=%v\n", err)
}
}

// github授权
func authorization(c echo.Context) error {
// github后台已经配置了 redirect_uri=http://cwww3.vaiwan.com/callback
github := "https://github.com/login/oauth/authorize?client_id=" + ClientId
// 提供用户用户点击界面
return c.HTML(http.StatusOK, fmt.Sprintf(`<script>window.location.href='%v'</script>`, github))
}

// 获取用户信息
func getUserInfo(c echo.Context) error {
var err error
var data []byte
// 获取code
args := new(struct {
Code string `json:"code"`
})
if err = c.Bind(args); err != nil {
return c.String(http.StatusOK, "参数错误")
}
// 构建token请求体
reqBody := tokenReq{
ClientId: ClientId,
ClientSecret: ClientSecret,
Code: args.Code,
}
if data, err = json.Marshal(reqBody); err != nil {
return c.String(http.StatusOK, fmt.Sprintf("序列化失败 err=%v", err))
}

req, _ := http.NewRequest(http.MethodPost, "https://github.com/login/oauth/access_token", bytes.NewBuffer(data))

req.Header.Set("Content-Type", "application/json")
// 接收json格式
req.Header.Set("Accept", "application/json")

// token响应体
var tokenResp tokenResp
if err = DoRequest(req, &tokenResp); err != nil {
return c.String(http.StatusOK, fmt.Sprintf("请求解析失败 err=%v", err))
}

r, _ := http.NewRequest(http.MethodGet, "https://api.github.com/user", nil)
// 设置token 请求用户信息
r.Header.Add("Authorization", "token "+tokenResp.AccessToken)
userRespBody, _ := http.DefaultClient.Do(r)
defer userRespBody.Body.Close()
userData, _ := ioutil.ReadAll(userRespBody.Body)

return c.String(http.StatusOK, string(userData))
}

// 封装请求与解析
func DoRequest(r *http.Request, i interface{}) error {
resp, err := http.DefaultClient.Do(r)
if err != nil {
log.Error("do fail err=%v", err)
return err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Error("read fail err=%v", err)
return err
}
if err := json.Unmarshal(data, &i); err != nil {
log.Error("parse fail err=%v", err)
return err
}
return nil
}

效果

image-20210410004854567

  • 获得用户数据
image-20210410004926153
Donate comment here.