上一篇文章,我们已经为框架定义了【响应对象】,该框架不仅可以获取客户端上传的报文信息,还能生成一些简单信息,如自定义响应头等等。今天我们再次完善一下该框架,给框架增加权限验证的功能,给框架加上一个BasicAuth
的方法。
本篇文章所依赖的环境为:
本次代码已经放到了gitee
上:gitee.com/pdudo/golea…
什么是http BashAuth
在日常生活或者工作中,你是否见到过这样的页面呢?
当我们打开窗口,网页并不是里面加载出来的,而是弹出了一个输入框,让我们输入其用户名和密码,输入后待服务器验证通过后,才开始展现实际的网页内容。这个就是BashAuth
了,全称为: Basic access authentication
,也称基本认证。是一种极其简单http
协议身份验证方式。BashAuth
认证的方式是通过请求头来发送和校验的,而非我们熟知的cookie
、token
等。
还有一点需要注意,这里所展现的输入框,其实是浏览器底层拆解协议所支持的。
BasicAuth
认证底层原理
BasicAuth
使用的http
协议进行身份验证的,所以会将身份信息携带在请求头中进行传输。
第一步: 用户在第一次进行浏览器请求页面的时候,不会携带认证信息,此时服务器接接收信息后,判断报文中请求头没有WWW-Authenticate
,此时会返回客户端报文,其中需要将报文响应状态码为401
,响应头新增一个key
为WWW-
Authenticate
,值为realm
加上请求域,做法是为了避免和其他URL
相冲突。所以响应核心报文为:
HTTP/1.0 401 Unauthorized WWW-Authenticate: Basic realm="pdudo sites"
其中状态码为401
代表客户端错误,指的是需要身份验证,WWW-Authenticate
响应头的值为Basic realm="pdudo sites"
,Basic
代表验证的模式,realm
代表请求域,请求域是为了和其他URL
相冲突。
第二步: 当浏览器接收到该报文后,会判断其状态码为401
并且请求头中有key
为WWW-Authenticate
,此时便会弹出输入框,让用户输入信息以便服务器校验。
输入之后,不是直接发送给服务器,而是会根据服务器发送的验证模式进行校验,如basic
,加入客户端发送的报文如下:
GET /index HTTP/1.1 HTTP_AUTHORIZATION: Basic cGR1ZG86anVlamlu
其中Get
代表请求方式,/index
表示请求路径,HTTP/1.1
是http
的版本。请求头HTTP_AUTHORIZATION
表示验证数据,Basic
是验证方式,cGR1ZG86anVlamlu
表示用户名:密码
,只不过浏览器使用base64
进行编码了而已。
第三步: 服务器在接收到报文后,发现有HTTP_AUTHORIZATION
,则会使用base64
解码,发现客户端上传的数据为pdudo:juejin
,以:
为分割,前者是用户名,后则是密码。当验证通过后,进行发送其他信息,若验证未通过,则重复第一步。
将BasicAuth
认证添加到框架中
我们已经知晓了BasicAuth
认证的底层原理,所以可以开始修改我们的框架了,我们将其代码写到上一篇所述的response
类中,代码如下:
def basicAuth(self): if "HTTP_AUTHORIZATION" not in self.response: return None,None,"HTTP_AUTHORIZATION request header not found" else: originalVal = self.response["HTTP_AUTHORIZATION"] types = originalVal.split(" ")[0] values = originalVal.split(" ")[1] if types == "Basic": userAndPasswd = base64.b64decode(values).decode() user = userAndPasswd.split(":")[0] passwd = userAndPasswd.split(":")[1] return user,passwd,None else: return None, None, "HTTP_AUTHORIZATION request error,unrecognized type: %s " %(types)
当客户端函数调用basicAuth
时,服务器检查客户端http
报文中,是否有HTTP_AUTHORIZATION
,如果没有,则直接返回HTTP_AUTHORIZATION request header not found
。
若请求头有该key
,则将其抓出来存储到originalVal
中,它的值类似于: Basic cGR1ZG86anVlamlu
,前者是认证类型,后者是加密后字符串,所以我们需要分开将其取出来,存到types
和values
中,再判断types
是否是Basic
,如果是我们使用base64
进行解密,解密后的值类似于pdudo:juejin
,以冒号(:
)隔开,前者为用户名,后者为密码,所以我们需要将字符串按照:
进行拆分,前者保存到user
中,后者保存到passwd
中,最后将其数据给函数。
那当第一步检查请求头没有HTTP_AUTHORIZATION
或者用户名密码不对,应该如何返回呢? 这个需要用户在函数中进行调用即可,例如:
@myWeb.routes(path="/index",methods="all",regular=False) def index(r): username, password , isok = r.basicAuth() if isok != None or username != "pdudo" or password != "juejin": r.set_headers("WWW-Authenticate","Basic realm="pdudo sites"") r.status_code(401) return return "123"
上述代码,使用我们自己编写的myWeb
框架,定义了一个路由/index
,其方法为Get
或者Post
,regular=False
则不使用正则路由,在函数中,通过r.basicAuth()
来获取客户端上传的用户名密码,当用户名不等于pdudo
或者密码不等于juejin
或者解析失败的时候,会将响应状态码定义为401
,并且设置响应头,key
为WWW-Authenticate
,值为Basic realm="pdudo sites"
。
框架功能测试
这里就不贴myWeb.py
的代码了,太长了,大家可以去gitee
上看,这里贴一下main.py
代码:
为了方便,我将每次获取到的数据,都打印一下,方便待会查看数据。
执行代码后,打开浏览器输入http://pdudo.juejin.cn:8888/index
,当弹出框后,我们第一个故意输错,第二次才输正确:
我们查看服务器日志,如下:
一共收到了3次,第一个输出后,并没有携带报文,被挡回来了,所以用户名和密码都为None
,第二次则是密码输入不正确被挡回来了,第三次输入正确,成功得到数据123
。
总结
本篇文章,我们介绍了一个极其古老的认证方式: BashAuth
, 它是通过请求头来发送数据和验证的。接着我们了解了一下其认证过程,其中最常用的是类型是Basic
,它表示将数据进行base64
编码后传输,注意是编码,不是加密。所以在使用该功能的时候,最好配合https
使用,以便被抓包后破解。 最后将其添加到我们的web
框架myWeb.py
中。