几种封装 HTTP Authorization 的分装方式
建议点击 查看原文 查看最新内容。
原文链接:
https://typonotes.com/posts/2024/01/25/authz-in-http-request/
大家都知道, 在做 HTTP 请求的时候, 通常需要提供 账号名和密码, 例如
1
| $ curl -u username:password http://api.example.com
|
其实, 这种就是
HTTP Authentication
中的 Basic 模式(Schema)
翻译一下
- 首先将账号密码使用 冒号: 链接
- 随后进行 base64 编码
- 最后放在 Header 的 Authorization 中。
1
2
| $ val=base64("username:password")
$ curl -H "Authorization: Basic ${username:password} http://api.example.com
|
除了 Basic 之外,
HTTP 标准 Schema
包括以下
- Basic: 常见
- Bearer: 常见
- Digest
- HOBA
- Mutual
- Negotiate / NTLM
- VAPID
- SCRAM
- AWS4-HMAC-SHA256
除了以上之外, 你当然可以 自定义 自己服务器的 验证 模式, 走非标路线让黑客琢磨不透。
几种常见的 Authorization 封装方式
分享几种我见过的封装方式
1. 直接封装
这种数据比较初级阶段, 只提供 固定验证方式的封装。
但 如果需要再增加一种验证方式, 例如 Bearer Token
,就比较无力了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| type BasicConfig struct {
Username string `json:"user"`
Password string `json:"name"`
}
func (bc *BasicConfig) request(method string, url string, body io.Reader) {
req, _ := http.NewRequest(method, url, body)
// 硬编码
auth := bc.Username + ":" + bc.Password
authz := base64.StdEncoding.EncodeToString([]byte(auth))
req.Header.Set("Authorization", "Basic "+authz)
_, _ = http.DefaultClient.Do(req)
}
|
2. Context 传递
这种方式使用 Context 进行验证参数的传递, 具有一定扩展性, 但 依旧存在 枚举 验证方式的问题。
2.1 定义 Context Key 类型
注意: 在定义 context key 的时候, 不能直接使用 简单类型, 例如 stirng, int
等。 而是 通过这些简单类型创建一个新类型,再使用。 这是类型的基础知识!!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| type contextKey string
func (c contextKey) String() string {
return "auth " + string(c)
}
var (
// ContextOAuth2 takes a oauth2.TokenSource as authentication for the request.
ContextOAuth2 = contextKey("token")
// ContextBasicAuth takes BasicAuth as authentication for the request.
ContextBasicAuth = contextKey("basic")
// ContextAccessToken takes a string oauth2 access token as authentication for the request.
ContextAccessToken = contextKey("accesstoken")
// ContextAPIKey takes an APIKey as authentication for the request
ContextAPIKey = contextKey("apikey")
)
|
源代码在
go-bamboo-v1/configuation.go - Github
2.2 通过 Context 获取验证信息
接下来, 在构建 request 的时候, 就通过 ctx.Value
获取 相应值。 如果 断言 成功, 则添加到 HTTP Header 中。
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
| // prepareRequest build the request
func (c *APIClient) prepareRequest(
ctx context.Context) (localVarRequest *http.Request, err error) {
if ctx != nil {
// add context to the request
localVarRequest = localVarRequest.WithContext(ctx)
// Walk through any authentication.
// OAuth2 authentication
if tok, ok := ctx.Value(ContextOAuth2).(oauth2.TokenSource); ok {
// We were able to grab an oauth2 token from the context
var latestToken *oauth2.Token
if latestToken, err = tok.Token(); err != nil {
return nil, err
}
latestToken.SetAuthHeader(localVarRequest)
}
// Basic HTTP Authentication
if auth, ok := ctx.Value(ContextBasicAuth).(BasicAuth); ok {
localVarRequest.SetBasicAuth(auth.UserName, auth.Password)
}
// AccessToken Authentication
if auth, ok := ctx.Value(ContextAccessToken).(string); ok {
localVarRequest.Header.Add("Authorization", "Bearer "+auth)
}
}
return localVarRequest, nil
}
|
源代码在
go-bamboo-v1/api_client.go - Github
这种方式就解决了 枚举 带来的有限性。 简单粗暴
在定义配置的时候, 直接设置一个 DefaultHeader 来承载所有。 当然, 这个 DefaultHeader 不仅仅只用来保存验证方式。
1
2
3
4
| // Configuration provides the configuration to connect
type Configuration struct {
DefaultHeader map[string]string `json:"defaultHeader,omitempty"`
}
|
源代码在
go-bamboo-v1/configuration#L52 - Github
在初始化 Request 的时候, 直接从 Map 转移到 Request Header 中即可。
1
2
3
4
5
6
7
8
9
10
| // prepareRequest build the request
func (c *APIClient) prepareRequest(
ctx context.Context, cfg *Configuration) (localVarRequest *http.Request, err error) {
for header, value := range cfg.DefaultHeader {
localVarRequest.Header.Add(header, value)
}
return localVarRequest, nil
}
|
源代码在
go-bamboo-v1/api_client.go - Github
4. 接口传递
这种方式同样不存在 枚举 的局限性。 同时看起来要比 DefaultHeader 更优雅。
4.1 定义接口
首先定义一个接口, 用于返回 HTTP Request Authorization 的值(包含 验证模式 和 验证值)。
1
2
3
| type Authorizer interface {
Authorization() string
}
|
在 Client 作为字段使用, 并在初始化 Client 的时候传入
1
2
3
4
5
6
7
8
9
10
11
12
| type Client struct {
authorizer Authorizer // User credentials
}
func NewClient(httpClient *http.Client, creds Authorizer) *Client {
c := &Client{
client: httpClient,
BaseURL: baseURL,
authorizer: creds,
}
return c
}
|
4.2 通过接口获取验证信息
在构建 HTTP Request, 直接调用 Authorzer 接口即可返回值。
1
2
3
4
5
6
7
8
9
10
11
12
13
| func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
creds := c.authorizer
req.Header.Set("Authorization", creds.Authorization())
req.Header.Set("Accept", "application/json")
return req, nil
}
|
源码在
go-bamboo/client.go#L100 - Github
4.3. 案例扩展
注意: 这个案例可以扩展, 同时返回 Header 名称和值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| type Authorizer interface {
Authorization() (string, string)
}
func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
k, creds := c.authorizer
req.Header.Set(k, creds)
req.Header.Set("Accept", "application/json")
return req, nil
}
|