详细介绍
auth.go
基本信息验证功能,具体介绍可参考https://zhuanlan.zhihu.com/p/...
auth.go对外提供了接口BasicAuth和BasicAuthForRealm, 这两个接口以中间件的方式提供默认的账号供使用。
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
if realm == "" {
realm = "Authorization Required"
}
realm = "Basic realm=" + strconv.Quote(realm)
pairs := processAccounts(accounts) //处理账号信息
return func(c *Context) {
// Search user in the slice of allowed credentials
user, found := pairs.searchCredential(c.requestHeader("Authorization"))
if !found {
// Credentials doesn't match, we return 401 and abort handlers chain.
c.Header("WWW-Authenticate", realm)
c.AbortWithStatus(http.StatusUnauthorized)
return
}
// The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using
// c.MustGet(gin.AuthUserKey).
c.Set(AuthUserKey, user)
}
}
strconv.Quote() 这个方法的主要作用是把字符串转成带双引号的字符串,该功能在转结构体字符串时比较实用,例如:
a := A{
A: "test",
B: 18,
}
by, err := json.Marshal(a)
if err != nil {
return
}
str := string(by)
fmt.Println(str)
fmt.Println(strconv.Quote(str))
结果是:
{"A":"test","B":18}
"{\"A\":\"test\",\"B\":18}"
processAccounts:把密码和用户名组合通过base64加密作为value,user作为user构造成新的authPair结构体存入slice切片中。
func processAccounts(accounts Accounts) authPairs {
length := len(accounts)
assert1(length > 0, "Empty list of authorized credentials")
pairs := make(authPairs, 0, length)
for user, password := range accounts {
assert1(user != "", "User can not be empty")
value := authorizationHeader(user, password)
pairs = append(pairs, authPair{
value: value,
user: user,
})
}
return pairs
}
func authorizationHeader(user, password string) string {
base := user + ":" + password
return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
}
searchCredential:把http请求头中的Authorization值 和存储的base64编码的value做对比。没找到就返回401,找到就设置context的user 为authPair的key。
func (a authPairs) searchCredential(authValue string) (string, bool) {
if authValue == "" {
return "", false
}
for _, pair := range a {
if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {
return pair.user, true
}
}
return "", false
}
这里有两个地方可以学习:
- 如果要比较用于验证用户数据密钥信息的字节切片时,使用reflact.DeepEqual()、bytes.Equal(),bytes.Compare()会使应用程序遭受计时攻击(Timing Attack),可使用crypto/subtle.ConstantTimeCompare()避免泄漏时间信息。
这块可参考: http://blog.codeg.cn/2015/01/... - string和[]byte转换,这个转换是zero-copy,性能比较高,推荐使用。
string和[]byte转换:
func StringToBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(
&struct {
string
Cap int
}{s, len(s)},
))
}
// BytesToString converts byte slice to string without a memory allocation.
func BytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
总结:
该模块功能虽然比较少,但是可以学的内容还是比较多:
- strconv.Quote()
- subtle.ContantTImeCompare
- string和[]byte比较
- 基本身份认证功能。