BasicAuth认证与Go
Basic Auth是一种开放平台认证方式,简单的说就是需要你输入用户名和密码才能继续访问。Bath Auth是其中一种认证方式,另一种是OAuth。
Basic Auth认证处理简单几乎没有什么优点了,最大的缺点就是安全性低。不用说,OAuth认证方式克服了Basic Auth认证的所有缺点,并且也是目前广泛应用的。
gin框架提供了Bath Auth认证中间件,我们来看看该怎么玩。
首先是服务端代码:
package main
import "gopkg.in/gin-gonic/gin.v1"
var secrets = gin.H{
"foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
"austin": gin.H{"email": "austin@example.com", "phone": "666"},
"lena": gin.H{"email": "lena@guapa.com", "phone": "523433"},
}
func getSecrets(c *gin.Context) {
user := c.MustGet(gin.AuthUserKey).(string)
if secret, ok := secrets[user]; ok {
c.JSON(200, gin.H{"user": user, "secret": secret})
} else {
c.JSON(200, gin.H{"user": user, "secret": "No SECRET :("})
}
}
func main() {
r := gin.Default()
authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
"foo": "bar", //用户名:密码
"austin": "1234",
"lena": "hello2",
"manu": "4321",
}))
authorized.GET("/secrets", getSecrets)
r.Run(":8080")
}
让我们从main
函数开始。Group
函数注册了一个群组路由,gin.BasicAuth
就是中间件,它的参数gin.Accounts
其实是一个map[string]string
类型的映射,这里是用来记录用户名和密码。
然后在路由/admin
之下,又注册了/secrets
路由,所以他的完整路由应该是/admin/secrets
。处理器是getSecrets
函数。
在getSecrets
函数中,首先从上下文gin.Context
中获取用户名。gin.AuthUserKey
是一个字符串常量,定义如下:
const AuthUserKey = "user"
这里获取的用户名就是你访问这个URL时输入的用户名,后台会验证密码,如果用户名和密码都对上了,认证才会成功。
接下来会利用获得的用户名去secrets
结构中查找用户信息。gin.H
是一个map[string]interface{}
的映射。最后通过JSON返回查询结果。
现在万事具备,让我们用在浏览器中输入localhost:8080/admin/secrets进行访问,不出意外你会看到一个提示框,要你输入用户名和密码。用户名输入foo
,密码输入bar
。然后你就能看到一个关于foo
的信息的JSON串了。
下面让我们用curl试试。打开PowerShell,输入curl localhost:8080/admin/secrets
,然后帅气的回车。
。。。
为什么什么也没有?
再次输入curl -I localhost:8080/admin/secrets
回车,查看响应头,得到的居然是一个401的响应。这是很正常的,应为你并没有提供任何有关用户名和密码的信息,所以认证必然是失败的,认证失败就会得到401的响应。
我们可以看看BasicAuth
函数的源代码:
func BasicAuth(accounts Accounts) HandlerFunc {
return BasicAuthForRealm(accounts, "")
}
继续追查BasicAuthForRealm
函数:
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
pairs := processAccounts(accounts)
return func(c *Context) {
user, found := pairs.searchCredential(c.Request.Header.Get("Authorization"))
if !found {
c.AbortWithStatus(401)
} else {
c.Set(AuthUserKey, user)
}
}
}
让我们省略一些无关紧要的细节。这里的accounts
就是我们调用BasicAuth
函数时带入的用户名-密码映射,这里需要用来进行身份验证。最后返回了一个匿名函数,这个匿名函数就是中间件。
在匿名函数中首先会去请求头中去查找Authorization
请求首部,如果找到了,就调用Set
函数把这个请求首部中的用户名设置到上下文,也就是gin.Context
中。无独有偶,这里我们又看到了AuthUserKey
,现在你知道为什么在getSecrets
函数中我们要用它去获取用户名了吧。需要注意的是searchCredential
函数除了获取用户名还会进行密码验证。
如果请求头中没有Authorization
请求首部,那么恭喜,你将毫无悬念的得到401响应。
所以,当我们使用curl访问时,需要设置一下请求头。但是问题又来了,该如何设置请求头呢?
不要紧,其实想通过验证只需要在访问时提供用户名和密码就行了,这一要求通过URL也是可以办到的。如下:
curl foo:bar@localhost:8080/admin/secrets
这次就能看到返回的JSON串了。是这样子的:
{"secret":{"email":"foo@bar.com","phone":"123433"},"user":"foo"}
既然这样,我们不妨再看看这样访问时curl发送给服务器的请求头是怎样的。通过-v
选项能达到这一目的:
curl -v foo:bar@localhost:8080/admin/secrets
这是我们将得到如下的输出:
> GET /admin/secrets HTTP/1.1
> Host: localhost:8080
> Authorization: Basic Zm9vOmJhcg==
> User-Agent: curl/7.60.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Date: Wed, 11 Jul 2018 06:14:26 GMT
< Content-Length: 64
<
{"secret":{"email":"foo@bar.com","phone":"123433"},"user":"foo"}
>
符号后面的是请求,<
符号后面的是响应。我们注意到请求头中有这样一行请求首部:
Authorization: Basic Zm9vOmJhcg==
这正是我们苦苦寻觅的验证信息!不过Basic后面一串乱码又是什么鬼?
其实那不是乱码,那是foo:bar
经过base64编码后的结果。也就是说,写在请求头中的验证信息必须经过base64编码。这里提供一个网站可以在线进行base64编解码。有关认证的更多信息可以访问这里。
如果想通过设置请求头进行验证,按照上面的模式设置请求头就可以啦。下面让我们用这种方式来获取austin
的秘密。首先需要访问base64编码网站,得到austin:1234
经过编码后的内容,然后在PowerShell中输入以下命令:
curl -v -H 'Authorization: Basic YXVzdGluOjEyMzQ=' localhost:8080/admin/secrets
-H
用来向请求头添加自定义字段。再次帅气的回车,你将得到以下内容:
> GET /admin/secrets HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.60.0
> Accept: */*
> Authorization: Basic YXVzdGluOjEyMzQ=
>
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Date: Wed, 11 Jul 2018 08:35:21 GMT
< Content-Length: 71
<
{"secret":{"email":"austin@example.com","phone":"666"},"user":"austin"}
成功获取austin
的秘密,是不是666。
最后总结一下认证的三种方式:
- 通过浏览器访问,输入用户名和密码;
- 通过URL提供用户名和密码,如:user:password@localhost:8080/admin/secrets;
- 通过在请求头中添加
Authorization
字段提供用户名和密码,需要经过base64编码。