在tornado http代理服务上加入基本认证[basic auth]

起因

这两天在用tornado实现一个http代理,属于正向代理,同步的方式调用。  对于我这的应用场景是爬虫的代理,这代理服务会发布在各个公网云主机上。 但是放到公网就会遇到安全的问题,我想大家的web服务,经常会受到别人的嗅探包,主要是http proxy的方式,他这是撞代理。  对于安全,我这边不仅用了黑白名单的方式,而且用了会加入基本认证。 简单看了下基本认证的资料,他其实就是在request请求体里面加入header,名叫Authorization。 通过抓包分析得出,他的格式是这样的,  “Basic Zm9vOmJhcg==” 空格左面是他的加密方式,右面是base64加密的用户名和密码。


BASIC认证的过程
1.  客户端向服务器请求数据,请求的内容可能是一个网页或者是一个其它的MIME类型,此时,假设客户端尚未被验证,则客户端提供如下请求至服务器:
Get /index.html HTTP/1.0
Host:www.google.com
2.  服务器向客户端发送验证请求代码401,服务器返回的数据大抵如下:
HTTP/1.0 401 Unauthorised
Server: SokEvo/1.0
WWW-Authenticate: Basic realm=”google.com”
Content-Type: text/html
Content-Length: xxx
3.  当符合http1.0或1.1规范的客户端(如IE,FIREFOX)收到401返回值时,将自动弹出一个登录窗口,要求用户输入用户名和密码。
4.  用户输入用户名和密码后,将用户名及密码以BASE64加密方式加密,并将密文放入前一条请求信息中,则客户端发送的第一条请求信息则变成如下内容:
Get /index.html HTTP/1.0
Host:www.google.com
Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx

注:xxxx….表示加密后的用户名及密码。
5.  服务器收到上述请求信息后,将Authorization字段后的用户信息取出、解密,将解密后的用户名及密码与用户数据库进行比较验证,如用户名及密码正确,服务器则根据请求,将所请求资源发送给客户端:


  长话短说,直接仍代码。

标注下原文链接,最近不少缺德网站转载后,也不留个链接。 http://xiaorui.cc/?p=1904

这是服务端的实现,很简单就是先用self.request.headers.get取出Authorization,然后decode(‘base64’)来解析用户名和密码。

from tornado.escape import utf8
from hashlib import md5

class BasicAuthHandler(RequestHandler):
    def get(self):

        # Authorization: Basic base64("user:passwd")
        auth_header = self.request.headers.get('Authorization', None)
        if auth_header is not None:
            # Basic Zm9vOmJhcg==
            auth_mode, auth_base64 = auth_header.split(' ', 1)
            assert auth_mode == 'Basic'

            auth_username, auth_password = auth_base64.decode('base64').split(':', 1)
            if auth_username == username or auth_password == password:
                self.write('ok')
            else:
                self.write('fail')
        else:
            '''
            HTTP/1.1 401 Unauthorized
            WWW-Authenticate: Basic realm="renjie"
            '''
            self.set_status(401)
            self.set_header('WWW-Authenticate', 'Basic realm="%s"' % realm)

可以直接扔到一个函数里判断….

你自己定义下  base_auth_user , base_auth_passwd

def base_auth_valid(auth_header):
    from tornado.escape import utf8
    from hashlib import md5
    auth_mode, auth_base64 = auth_header.split(' ', 1)
    assert auth_mode == 'Basic'
    auth_username, auth_password = auth_base64.decode('base64').split(':', 1)
    if auth_username == base_auth_user and auth_password == base_auth_passwd:
        return True
    else:
        return False

客户端测试:

curl  -u xiaorui:123 -x 127.0.0.1:8888 http://www.163.com

[ruifengyun@devops toproxy (master)]$ curl -vv  -u 1xiaorui:123 -x 127.0.0.1:8888 http://www.xiaorui.cc
* Rebuilt URL to: http://www.xiaorui.cc/
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1…
* Connected to 127.0.0.1 (127.0.0.1) port 8888 (#0)
* Server auth using Basic with user ‘1xiaorui’
> GET http://www.xiaorui.cc/ HTTP/1.1
> Authorization: Basic MXhpYW9ydWk6MTIz
> User-Agent: curl/7.37.1
> Host: www.xiaorui.cc
> Accept: */*
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 403 Forbidden
< Date: Tue, 25 Aug 2015 15:36:56 GMT
< Content-Length: 0
< Content-Type: text/html; charset=UTF-8
* Server TornadoServer/4.0.2 is not blacklisted
< Server: TornadoServer/4.0.2
<
* Connection #0 to host 127.0.0.1 left intact

下面是我服务段的返回结果,直接给他一个403认证失败。 

一般来说,客户端和服务器的认证方式,除了basic auth外,还有digest , 这里我就不详细说明,大家瞅瞅就懂了。 

 if auth_header is not None:
            auth_mode, params = auth_header.split(' ', 1)
            assert auth_mode == 'Digest'
            param_dict = {}
            for pair in params.split(','):
                k, v = pair.strip().split('=', 1)
                if v[0] == '"' and v[-1] == '"':
                    v = v[1:-1]
                param_dict[k] = v
            assert param_dict['realm'] == realm
            assert param_dict['opaque'] == opaque
            assert param_dict['nonce'] == nonce
            assert param_dict['username'] == username
            assert param_dict['uri'] == self.request.path
            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()
            h2 = md5(utf8('%s:%s' % (self.request.method,
                                     self.request.path))).hexdigest()
            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()

在nginx、apache http服务上,还记得有个htpasswd么?  对的,他是生成基本认证的本地库,其实就一个文件。 我们也可以把账号密码都放入htpasswd里面,正好python也有针对htpassswd的操作接口。 我打算在自己的开源项目toproxy里面用htpasswd,验证http代理认证。 

import htpasswd

with htpasswd.Basic("/path/xiaorui/user.db") as userdb:
    userdb.add("bob", "password")
    userdb.change_password("alice", "newpassword")

with htpasswd.Group("/path/xiaorui/group.db") as groupdb:
    groupdb.add_user("bob", "admins")
    groupdb.delete_user("alice", "managers")


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

1 Response

  1. docker 2015年8月27日 / 下午5:47

    我的用token的方式,更好控制

发表评论

邮箱地址不会被公开。 必填项已用*标注