今天Any给我讲了一下助手的Oauth的逻辑,突然对Oauth茅塞顿开

先白嫖一张图片过来

OAuth2.0授权码模式--认证流程

现在我们模拟一个场景,就是我们使用助手作为授权服务器来访问上课啦这个第三方应用

  1. 上课啦 先会将 redirectId state(随机生成) clientId(事先在助手服务器上有绑定) 发给助手服务器
  2. 助手 收到 这些参数后就会随机生成一个 code 然后重定向到 hduhelp.com/active 页面
  3. 在 hduhelp.com/active 页面我们主要用于激活这个授权码,就是会跳出一个登录页面,让用户登录,生成一个token,然后将token-code-state 绑定在一起存到数据库中
  4. 然后因为 Oauth2.0 的具体格式原因,助手会 query.add(state) \ query.add(code) \ clientId \ c.redirect(redirectId) 到上课啦的重定向页面,上课啦会校验 state 分析途中有没有被修改
  5. 然后 上课啦 会把 code + state + clientId + redirectUrl 重新发给助手
  6. 助手通过刚才的数据库中查询 code 是否匹配来返回 token,同时将刚才存储的code删除,一般这个code是短暂的
  7. 上课啦拿到token后,显示登录成功,也会生成一个新的token,这个token会将这个token和用户的uid绑定在一起

这里有些细节其实可以更清楚一点

  1. client_id 其实会 事先在助手服务器上注册记录
  2. redirect_uri:上课啦的重定向URI,用户授权后将重定向到此地址
  3. 一般还会加一个 scope 字段来表示 请求的权限范围(可选)
  4. state是随机生成的状态值,用于防止CSRF攻击

下面是我的草图:

image-20240929010805006

下面是助手实际的Oauth过程(默认已登录):

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
func TokenLoginHandler(c *gin.Context) {
ctx := c.Request.Context()
tokenStr := c.Param("token") // 这里默认已经获取到了用于的token所以不需要再繁琐的登录
clientId := c.Param("clientId")
if tokenStr == "" || clientId == "" {
request.SetJSONError(c, 400, 40001, "bad request")
return
}
client, err := model.LookupClientByClientID(c, clientId)

//clientID VAL 判断 client_id 是否注册
if err != nil || client == nil {
model.OAuthRequestLog(ctx, false, clientId, "", "origin token to token", c.GetHeader("user-agent"), tokenStr, "", errors.New("invalid client"))
request.SetJSONError(c, 403, 40301, "authorization failed")
return
}
//get origin token // 判断这个token是否有效
token := authModel.LookupAccessToken(ctx, tokenStr)
if !token.Valid() || token.IsOAuth() {
model.OAuthRequestLog(c.Request.Context(), false, clientId, "", "origin token to token", c.GetHeader("user-agent"), tokenStr, "", errors.New("invalid code"))
request.SetJSONError(c, 401, 40100, "unauthorized")
return
}
// gen redirect url 获取code 并将 code-token-client_id-redirectUrl 绑定在一起
uuid, _ := utils.UUID()
code, _ := model.CreateEnabledAuthorizedCode(ctx, token.UserId, clientId, client.DefaultRedirectUrl, uuid)

redirectUrl, err := url.ParseRequestURI(client.DefaultRedirectUrl)
if err != nil {
c.Redirect(302, "https://app.hduhelp.com")
return
}
// 固定的 Oauth2.0 格式返回给第三方应用
query := c.Request.URL.Query()
query.Add("code", code)
query.Add("state", uuid)
query.Add("serverVerify", "true")
redirectUrl.RawQuery = query.Encode()

model.OAuthRequestLog(ctx, true, clientId, token.UserId, "origin token to token", c.GetHeader("user-agent"), tokenStr, "", nil)

c.Redirect(302, redirectUrl.String())
}

顺便提供一张 any 的疯狂草图: