理解OAuth 2.0并实现一个客户端 | 青训营笔记(下)

简介: 理解OAuth 2.0并实现一个客户端 | 青训营笔记(下)

密码模式 (Resource Owner Password Credentials)

密码模式就是用户直接将用户名密码提供给客户端,客户端使用这些信息到认证服务器请求授权。具体流程如下:

+----------+
| Resource |
|  Owner   |
|          |
+----------+
     v
     |    Resource Owner
    (A) Password Credentials
     |
     v
+---------+                                  +---------------+
|         |>--(B)---- Resource Owner ------->|               |
|         |         Password Credentials     | Authorization |
| Client  |                                  |     Server    |
|         |<--(C)---- Access Token ---------<|               |
|         |    (w/ Optional Refresh Token)   |               |
+---------+                                  +---------------+
       Figure 5: Resource Owner Password Credentials Flow
  • (A) 资源所有者提供用户名密码给客户端;
  • (B) 客户端拿着用户名密码去认证服务器请求令牌;
  • (C) 认证服务器确认后,返回令牌;
  1. 在B中客户端发送的请求中,需要包含这些参数:
参数名称 参数含义 是否必须
grant_type 授权类型,此处值为password 必须
username 用户名。 必须
password 用户的密码。 必须
scope 权限范围。 可选

如:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3w
  1. 在C中,认证服务器返回访问令牌。如:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}

客户端模式 (Client Credentials)

客户端模式,其实就是客户端直接向认证服务器请求令牌。而用户直接在客户端注册即可,一般用于后端 API 的相关操作。其流程如下:

+---------+                                  +---------------+
|         |                                  |               |
|         |>--(A)- Client Authentication --->| Authorization |
| Client  |                                  |     Server    |
|         |<--(B)---- Access Token ---------<|               |
|         |                                  |               |
+---------+                                  +---------------+
                Figure 6: Client Credentials Flow
  • (A) 客户端发起身份认证,请求访问令牌;
  • (B) 认证服务器确认无误,返回访问令牌。
  1. 在A中,客户端发起请求的参数有:
参数名称 参数含义 是否必须
grant_type 授权类型,此处值为client_credentials 必须
scope 权限范围。 可选

如:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials

2) 认证服务器认证后,发放访问令牌,如:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "example_parameter":"example_value"
}

实现OAuth 2.0

上面我们一起了解了OAuth 2.0的流程,现在我们开始实现OAuth 2.0

首先,我需要一个使用场景,大概是这样的。

我有一个需要登录的应用(web app)。 用户可以选择使用github登录。 登录后,显示一个简单的欢迎页面。

有了这个场景后,我们开始设计(这里我们实现的是第一种授权码模式)。

登录页面

在这里,我们充当的角色就是client,我们需要一个简单的页面,让用户选择使用github登录。

这是一个简单的html页面,public/index.html页面。

<!DOCTYPE html>
<html>
<body>
  <a href="https://github.com/login/oauth/authorize?client_id=89ac6f58f15f658d8dd5&redirect_uri=http://localhost:8080/oauth/redirect">
    Login with github
  </a>
</body>
</html>

也就是当用户点击Login with github会访问

https://github.com/login/oauth/authorize?
        client_id=89ac6f58f15f658d8dd5
        &redirect_uri=http://localhost:8080/oauth/redirect

其中:

  • https://github.com/login/oauth/authorizeGitHubOAuth网关地址。
  • client_id=89ac6f58f15f658d8dd5 这个是我申请的客户端ID,
    需要到github.com/settings/ap…注册。
    注册过程很简单,但要注意,最后一栏Authorization callback URL是和下面redirect_uri一致。
    如:
    image.png
    成功后你会获的Client IDClient Secret, 前者就是这里的client_id,两者在后面的编码中需要用到。
  • redirect_uri=http://localhost:8080/oauth/redirect 当用户确认,获取权限后重定向到的该地址。

然后我们写个mian.go, 启动一个简单的服务。

func main() {
    fs := http.FileServer(http.Dir("public"))
    http.Handle("/", fs)
    http.ListenAndServe(":8080", nil)
}

写到这里,我们代码已经能跑起了,如果你点击Login with github我们会看到授权页面。

当我们确定过后,会重定向到我们指定的地址,而且带上code,如:

http://localhost:8080/oauth/redirect?code=260f17a7308f2c566725

此时我们需要做的是,拿着该code去请求访问令牌(access_token),对应授权码模式中的D步骤。

重定向路由

现在我们有code了,我们需要去请求令牌。接下来我们就需要向https://github.com/login/oauth/access_token发送POST请求获取访问令牌(access_token)。

关于更多GitHub重定向URI的信息你可以看这里

现在,让我们将/oauth/redirect路由补全,在该步骤中请求访问令牌(access_token)。

package main
import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)
// 你在注册时得到的
const (
    clientID     = "你的客户端ID"
    clientSecret = "你的客户端密钥"
)
var httpClient = http.Client{}
type OAuthAccessResponse struct {
    AccessToken string `json:"access_token"`
}
func main() {
    fs := http.FileServer(http.Dir("public"))
    http.Handle("/", fs)
    http.HandleFunc("/oauth/redirect", HandleOAuthRedirect)
    http.ListenAndServe(":8080", nil)
}
// HandleOAuthRedirect doc
func HandleOAuthRedirect(w http.ResponseWriter, r *http.Request) {
    // 首先,我们从URI中解析出code参数
    // 如: http://localhost:8080/oauth/redirect?code=260f17a7308f2c566725
    err := r.ParseForm()
    if err != nil {
        log.Printf("could not parse query: %v", err)
        w.WriteHeader(http.StatusBadRequest)
    }
    code := r.FormValue("code")
    // 接下来,我们通过 clientID,clientSecret,code 获取授权密钥
    // 前者是我们在注册时得到的,后者是用户确认后,重定向到该路由,从中获取到的。
    reqURL := fmt.Sprintf("https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s",
        clientID, clientSecret, code)
    req, err := http.NewRequest(http.MethodPost, reqURL, nil)
    if err != nil {
        log.Printf("could not create HTTP request: %v", err)
        w.WriteHeader(http.StatusBadRequest)
    }
    // 设置我们期待返回的格式为json
    req.Header.Set("accept", "application/json")
    // 发送http请求
    res, err := httpClient.Do(req)
    if err != nil {
        log.Printf("could not send HTTP request: %v", err)
        w.WriteHeader(http.StatusInternalServerError)
    }
    defer res.Body.Close()
    // 解析
    var t OAuthAccessResponse
    if err := json.NewDecoder(res.Body).Decode(&t); err != nil {
        log.Printf("could not parse JSON response: %v", err)
        w.WriteHeader(http.StatusBadRequest)
    }
    // 最后获取到access_token后,我们重定向到欢迎页面,也就是表示用户登录成功,同属获取一些用户的基本展示信息
    w.Header().Set("Location", "/welcome.html?access_token="+t.AccessToken)
    w.WriteHeader(http.StatusFound)
}

欢迎页面

上面我们拿到了访问令牌,同时,我们重定向到了欢迎页面。接下来,我们就在欢迎页中获取用户的GitHub名称作为展示。

添加public/welcome.html页面。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello</title>
</head>
<body>
</body>
<script>
    // 获取access_token
    const query = window.location.search.substring(1)
    const token = query.split('access_token=')[1]
    // 访问资源服务器地址,获取相关资源
    fetch('https://api.github.com/user', {
            headers: {
                // 将token放在Header中
                Authorization: 'token ' + token
            }
        })
        // 解析返回的JSON
        .then(res => res.json())
        .then(res => {
            // 这里我们能得到很多信息
            // 具体看这里 https://developer.github.com/v3/users/#get-the-authenticated-user
            // 这里我们就只展示一下用户名了
            const nameNode = document.createTextNode(`Welcome, ${res.name}`)
            document.body.appendChild(nameNode)
        })
</script>

到这里,我们再次运行程序,点击Login with github,同意后我们可以看到类似Welcome, Razeen的信息,此时我们已经完成整个OAuth 2.0的流程了。

获得授权之后,我们能访问的API不仅仅有这些,更多的请看这里

关于安全

  1. 这里我们将访问令牌直接放在了URI中,这么做其实是不安全的。更好的做法是我们创建一个会话session,将cooike发送给用户即可。
  2. 在前面OAuth 2.0的解释中,我们知道在请求权限的过程中,我们可以加上state字段,如
https://github.com/login/oauth/authorize?
        client_id=89ac6f58f15f658d8dd5
        &redirect_uri=http://localhost:8080/oauth/redirect
        &state=xxxxx
  1. 而服务器会原封不动的返回该state。这样我们可以将该字段设置一些随机值,如果资源服务器返回的state值与我们设置的不同,我们认为该请求不是来自正确的资源服务器,应该拒绝。



目录
相关文章
|
9月前
|
前端开发
前端学习笔记202306学习笔记第四十八天-代理解决跨域问题1
前端学习笔记202306学习笔记第四十八天-代理解决跨域问题1
23 0
|
7月前
|
XML 安全 JavaScript
当面试官突然提到第三方登录时,我不禁微笑了~ 探秘WeChat公众号扫码关注登录!
当面试官突然提到第三方登录时,我不禁微笑了~ 探秘WeChat公众号扫码关注登录!
39 0
当面试官突然提到第三方登录时,我不禁微笑了~ 探秘WeChat公众号扫码关注登录!
|
11月前
|
物联网 数据安全/隐私保护
理解OAuth 2.0并实现一个客户端 | 青训营笔记(上)
理解OAuth 2.0并实现一个客户端 | 青训营笔记(上)
64 0
|
前端开发
前端工作总结228-第三方登录请求参考
前端工作总结228-第三方登录请求参考
85 0
|
存储 API 数据库
OAuth 2 实现单点登录,通俗易懂...(上)
OAuth 2 实现单点登录,通俗易懂...(上)
391 0
OAuth 2 实现单点登录,通俗易懂...(上)
|
安全 Java API
OAuth 2 实现单点登录,通俗易懂...(下)
OAuth 2 实现单点登录,通俗易懂...(下)
216 0
OAuth 2 实现单点登录,通俗易懂...(下)
|
数据安全/隐私保护
FastAPI 学习之路(二十八)使用密码和 Bearer 的简单 OAuth2
FastAPI 学习之路(二十八)使用密码和 Bearer 的简单 OAuth2
FastAPI 学习之路(二十八)使用密码和 Bearer 的简单 OAuth2
|
Web App开发 安全