详细介绍

auth.go

基本信息验证功能,具体介绍可参考https://zhuanlan.zhihu.com/p/...
auth.go对外提供了接口BasicAuthBasicAuthForRealm, 这两个接口以中间件的方式提供默认的账号供使用。

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
}

这里有两个地方可以学习:

  1. 如果要比较用于验证用户数据密钥信息的字节切片时,使用reflact.DeepEqual()bytes.Equal()bytes.Compare()会使应用程序遭受计时攻击(Timing Attack),可使用crypto/subtle.ConstantTimeCompare()避免泄漏时间信息。
    这块可参考: http://blog.codeg.cn/2015/01/...
  2. 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))
}

总结:
该模块功能虽然比较少,但是可以学的内容还是比较多:

  1. strconv.Quote()
  2. subtle.ContantTImeCompare
  3. string和[]byte比较
  4. 基本身份认证功能。

白沙云影
1 声望1 粉丝