建议点击 查看原文 查看最新内容。
原文链接: https://typonotes.com/posts/2024/01/25/authz-in-http-request/
大家都知道, 在做 HTTP 请求的时候, 通常需要提供 账号名和密码, 例如
代码语言:javascript复制$ curl -u username:password http://api.example.com
其实, 这种就是 HTTP Authentication[1] 中的 Basic 模式(Schema)
翻译一下
- 首先将账号密码使用 冒号: 链接
- 随后进行 base64 编码
- 最后放在 Header 的 Authorization 中。
$ val=base64("username:password")
$ curl -H "Authorization: Basic ${username:password} http://api.example.com
除了 Basic 之外, HTTP 标准 Schema[2] 包括以下
- Basic: 常见
- Bearer: 常见
- Digest
- HOBA
- Mutual
- Negotiate / NTLM
- VAPID
- SCRAM
- AWS4-HMAC-SHA256
除了以上之外, 你当然可以 自定义 自己服务器的 验证 模式, 走非标路线让黑客琢磨不透。
几种常见的 Authorization 封装方式
分享几种我见过的封装方式
1. 直接封装
这种数据比较初级阶段, 只提供 固定验证方式的封装。
但 如果需要再增加一种验证方式, 例如 Bearer Token
,就比较无力了。
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
等。而是 通过这些简单类型创建一个新类型,再使用。 这是类型的基础知识!!!
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[3]
2.2 通过 Context 获取验证信息
接下来, 在构建 request 的时候, 就通过 ctx.Value
获取 相应值。如果 断言 成功, 则添加到 HTTP Header 中。
// 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[4]
3. DefaultHeader 传递
这种方式就解决了 枚举 带来的有限性。简单粗暴
在定义配置的时候, 直接设置一个 DefaultHeader 来承载所有。当然, 这个 DefaultHeader 不仅仅只用来保存验证方式。
代码语言:javascript复制// Configuration provides the configuration to connect
type Configuration struct {
DefaultHeader map[string]string `json:"defaultHeader,omitempty"`
}
源代码在 go-bamboo-v1/configuration#L52 - Github[5]
在初始化 Request 的时候, 直接从 Map 转移到 Request Header 中即可。
代码语言:javascript复制// 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[6]
4. 接口传递
这种方式同样不存在 枚举 的局限性。同时看起来要比 DefaultHeader 更优雅。
4.1 定义接口
首先定义一个接口, 用于返回 HTTP Request Authorization 的值(包含 验证模式 和 验证值)。
代码语言:javascript复制type Authorizer interface {
Authorization() string
}
在 Client 作为字段使用, 并在初始化 Client 的时候传入
代码语言:javascript复制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 接口即可返回值。
代码语言:javascript复制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[7]
4.3. 案例扩展
注意:这个案例可以扩展, 同时返回 Header 名称和值
代码语言:javascript复制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
}
参考资料
[1]
HTTP Authentication: https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication
[2]
HTTP 标准 Schema: https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
[3]
go-bamboo-v1/configuation.go - Github: https://github.com/gfleury/go-bitbucket-v1/blob/6e30c5760c87b9095bafc130f209edcee4903a19/configuration.go#L15
[4]
go-bamboo-v1/api_client.go - Github: https://github.com/gfleury/go-bitbucket-v1/blob/6e30c5760c87b9095bafc130f209edcee4903a19/api_client.go#L288
[5]
go-bamboo-v1/configuration#L52 - Github: https://github.com/gfleury/go-bitbucket-v1/blob/6e30c5760c87b9095bafc130f209edcee4903a19/configuration.go#L52
[6]
go-bamboo-v1/api_client.go - Github: https://github.com/gfleury/go-bitbucket-v1/blob/6e30c5760c87b9095bafc130f209edcee4903a19/api_client.go#L293
[7]
go-bamboo/client.go#L100 - Github: https://github.com/tangx/go-bamboo/blob/b50568b35addef27a4bd0f04b841db86b50feb70/client.go#L100