实验目的

  1. 了解 BRAC 原理
  2. 基于 Tornado 框架,编写简单认证与授权程序



实验素材

  1. python 2.7.x 或 python 3.4.x
  2. tornado 4.2.x
  3. Sublime 或 Vim 或 Emacs 等编辑器,也可以使用 pyscript 或 pycharm 等集成环境(IDE)
  4. Tornado 官方 文档 ,参考中文 文档
  5. Tornado 自学 教程 ,作者的 博客
  6. curl 工具 官网
  7. mongodb & pymongo



任务 1:实现简单用户认证

  1. 阅读 自学教程 6.3 了解用户认证; 教程 4.1 和 4.2 了解 mongodb 使用。 也可以使用 tornado 推荐的方法引入数据库连接 RequestHandler.initialize
  2. 在 mongodb 中建立 user文档,必须包含 identity, alias name, password 三个属性。其中 password 的内容用 MD5 加密。
  3. 修改程序 cookies.py, 另存为 sample_auth.py , 该程序能够数据库实现用户认证。

注:教程上的缺陷:教程在 application 类中初始化了 mongodb 的连接,这等于默认应用运行于单线程之下。实战中, 建议每次使用前连接。 不用担心连接开销, 驱动一般内置 缓冲池 管理的。



任务 2:实现按角色授权

  1. 修改 sample_auth.py 为 auth.py 。 该程序支持 admins, users, vips, guests 四种角色。 匿名用户默认属于 guests, 登录用户默认 属于 users。
  2. auth.py 至少有四个页面,支持不同角色的服务。 请修改 mongodb ,加入合适的数据。
  3. 为了方便设置权限, 请实现装饰器 @roles(rolelist),例如, @role([‘vips’,’user’]) 表示vip和普通用户都可以访问的url, 其他用户转没有权限网页

任务1步骤如下:

  1. 了解教程中的用户认证方式;
  2. 学会如何向mongodb中添加数据和建立文档, 编写如下的python脚本将测试建立user文档并向其中输入一组测试数据
import hashlib
from pymongo import MongoClient

def createDB():
    client = MongoClient("127.0.0.1", 27017)
    db = client["privace"]
    user = db.user
    #md5
    m1 = hashlib.md5()
    m1.update("user")
    password = m1.hexdigest()
    user.insert({"identity":"user", "alias name": "user", "password": password})

def createAll():
    client = MongoClient("127.0.0.1", 27017)
    db = client["privace"]
    user = db.user
    user.remove();

if __name__ == '__main__':
    createAll()
    createDB()

在终端中进行脚本运行并查看mongodb,如下:

python 关闭证书验证 windows_python

  1. 修改教程所给代码如下
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.options
import os.path
import hashlib

from pymongo import MongoClient
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)


class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        return self.get_secure_cookie("aliasName")

class LoginHandler(BaseHandler):
    def get(self):
        self.render("login.html")

    def post(self):
         #self.set_secure_cookie("username", self.get_argument("username"))
         identity = self.get_argument("identity")
         #aliasName = self.get_argument("alias")
         password = self.get_argument("password")
        #md5
        md5Password = hashlib.md5()
        md5Password.update(password)
        password = md5Password.hexdigest()
         print password
        client = MongoClient("127.0.0.1", 27017)
         self.db = client["privace"]
         userInfo = self.db.user
         user = userInfo.find_one({"identity": identity})
         print "user"+str(user)
         if user:
             if password == user["password"]:
                 self.set_secure_cookie("aliasName", user["alias name"]) #store the salias Name thrount cookie
                 #print self.aliasName
                 self.redirect("/")
             else:
                 self.redirect("/login")
         else:
             self.redirect("/login")

class WelcomeHandler(BaseHandler):
    @tornado.web.authenticated
    def get(self):
        self.render("index.html", user=self.current_user)

class LogoutHandler(BaseHandler):
     def get(self):
         if(self.get_argument("logout", None)):
             #self.clear_cookie("username")
             self.redirect("/")

if __name__ == '__main__':
    tornado.options.parse_command_line()

    settings = {
        "template_path": os.path.join(os.path.dirname(__file__), "templates"),
        "cookie_secret": "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=",
        "xsrf_cookies": True, # ;event the attacker "get" the cookiet, but the xsrf_cookies is safe. the attacker cann't make the false request(form)
        "login_url":"/login"
    }

    application = tornado.web.Application([
        (r'/', WelcomeHandler),
        (r'/login', LoginHandler),
        (r'/logout', LogoutHandler)
        ], **settings)

    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

在终端中输入命令"python sample_auth.py",启动服务器,此时便可以在浏览器中输入“localhost:8000/login”,并输入测试数据,就可以了。

  1. 这个实验主要是学会使用python 操作mongodb并体验下利用数据库和装饰器来进行用户认证

任务二:

  1. 首先要理解和编写装饰类,了解其中的原理:除了实验中所说的,还可以看看http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819879946007bbf6ad052463ab18034f0254bf355000;主要是要明白装饰类是用来增强原先的函数的,更加本质上其实是修改了原本的函数指针,但没有改变它的运行上下文,也就是可以访问原先函数可以访问的,比如self,理解这个很重要,也正是因为这个原因,所以装饰器@tornado.web.authenticated才可以进行验证self.current_user是否存在。
  2. 根据实验需要编写如下的python脚本建立文档和导入测试数据
import hashlib
from pymongo import MongoClient

def createDB():
    client = MongoClient("127.0.0.1", 27017)
    db = client["privace"]
    role = db.role
    #md5
    m1 = hashlib.md5()
    m1.update("admin")
    password = m1.hexdigest()
    role.insert({"identity":"admin", "alias name": "admin", "password": password, "role": "admin"})

    m1 = hashlib.md5()
    m1.update("user")
    password = m1.hexdigest()
    role.insert({"identity":"user", "alias name": "user", "password": password, "role": "user"})
    
    m1 = hashlib.md5()
    m1.update("vip")
    password = m1.hexdigest()
    role.insert({"identity":"vip", "alias name": "vip", "password": password, "role": "vip"})


def createAll():
    client = MongoClient("127.0.0.1", 27017)
    db = client["privace"]
    role = db.role
    role.remove();

if __name__ == '__main__':
    createAll()
    createDB()

同时也增加了如下的html文件(也位于templates中)



python 关闭证书验证 windows_python_02

python 关闭证书验证 windows_html_03

<!--admin.html-->

<!DOCTYPE html>,{{ 
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Welcome Back!</title>
</head>

<body>
<h1>Welcome back,{{ role }} • {{ user }}</h1>
</body>

</html>
  1. View Code



python 关闭证书验证 windows_python_02

python 关闭证书验证 windows_html_03

<!--guest.html-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Welcome Back!</title>
</head>

<body>
    <h1>Welcome back, Guest</h1>
</body>

</html>
  1. View Code



python 关闭证书验证 windows_python_02

python 关闭证书验证 windows_html_03

<!--index.html-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Welcome Back!</title>
</head>

<body>
    <h1>Welcome back,{{ role }} • {{ user }}</h1>
    <form action="/" method="POST">
    {% raw xsrf_form_html() %}
        <input type="radio", name="role", value="user"> User
        <br>
        <input type="radio", name="role", value="vip"> Vip
        <br>
        <input type="radio", name="role", value="admin"> Admin
        <br>
        <input type="submit" value="Log In"/>
    </form>
</body>

</html>
  1. View Code



python 关闭证书验证 windows_python_02

python 关闭证书验证 windows_html_03

<!--login.html-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Plase Log In</title>
</head>

<body>
    <form action="/login" method="POST">
        {% raw xsrf_form_html() %}
        identity: <input type="text" name="identity"/>
        <br/>
        password: <input type="text" name="password"/>
        <br/>
        guest:      <input type="checkbox" name="guest" value="guest"/>Guest
        <br/>
        <input type="submit" value="Log In"/>
    </form>
</body>

</html>
  1. View Code



python 关闭证书验证 windows_python_02

python 关闭证书验证 windows_html_03

<!--permission.html-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Welcome Back!</title>
</head>

<body>
    <h1>Permission denied</h1>
</body>

</html>
  1. View Code



python 关闭证书验证 windows_python_02

python 关闭证书验证 windows_html_03

<!--user.html-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Welcome Back!</title>
</head>

<body>
<h1>Welcome back,{{ role }} • {{ user }}</h1>
</body>

</html>
  1. View Code



python 关闭证书验证 windows_python_02

python 关闭证书验证 windows_html_03

<!--vip.html-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Welcome Back!</title>
</head>

<body>
<h1>Welcome back,{{ role }} • {{ user }}</h1>
</body>

</html>
  1. View Code

虽然里面几个文件的内容都是一样的,但只是为了方便,实验中主要是为了进行测试,不同的角色能够访问的网页是不同的。


import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.options
import os.path
import hashlib
import functools

from pymongo import MongoClient
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)


def role(roleList):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(self, *args, **kw):
            identify = self.current_user
            client = MongoClient()
            db = client["privace"]
            roleSet = db.role
            person = roleSet.find_one({"identity": identify})
            role = person["role"]
            if role in roleList:
                func(self)
            else:
                self.redirect("/permission")
        return wrapper
    return decorator

                    

class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        return self.get_secure_cookie("identity")
#use self to get the identify and get the role

class LoginHandler(BaseHandler):
    def get(self):
        self.render("login.html")

    def post(self):
        guest = self.get_argument("guest", None);
        if guest != None:
            self.redirect("/guest")
            return
        #self.set_secure_cookie("username", self.get_argument("username"))
        identity = self.get_argument("identity")
        #aliasName = self.get_argument("alias")
        password = self.get_argument("password")
        #md5
        md5Password = hashlib.md5()
        md5Password.update(password)
        password = md5Password.hexdigest()
        client = MongoClient()
        self.db = client["privace"]
        role = self.db.role
        person = role.find_one({"identity": identity})
        if person:
            if password == person["password"]:
                self.set_secure_cookie("identity", person["identity"]) #store the salias Name thrount cookie
                self.redirect("/")
            else:
                self.redirect("/login")
        else:
            self.redirect("/login")

#only not for guest
class WelcomeHandler(BaseHandler):
    @tornado.web.authenticated
    def get(self):
        client = MongoClient()
        self.db = client["privace"]
        role = self.db.role
        person=role.find_one({"identity": self.current_user})
        self.render("index.html", user=self.current_user, role=person["role"])

    def post(self):
        choice = self.get_argument("role");
        print choice
        if choice == "user":
            self.redirect("/user")
        elif choice == "vip":
            self.redirect("/vip")
        elif choice == "admin":
            self.redirect("/admin")
        else:
            pass

class WelcomeUserHandler(BaseHandler):
    @tornado.web.authenticated
    @role(['admin', 'vip', 'user'])
    def get(self):
        client = MongoClient()
        self.db = client["privace"]
        roleInfo = self.db.role
        person = roleInfo.find_one({"identity": self.current_user})
        self.render("user.html", user=self.current_user, role=person["role"])



class WelcomeAdminHandler(BaseHandler):
    @tornado.web.authenticated
    @role(['admin'])
    def get(self):
        client = MongoClient()
        self.db = client["privace"]
        roleInfo = self.db.role
        person = roleInfo.find_one({"identity": self.current_user})
        self.render("admin.html", user=self.current_user, role=person["role"])

class WelcomeVipHandler(BaseHandler):
    @tornado.web.authenticated
    @role(['vip'])
    def get(self):
        client = MongoClient()
        self.db = client["privace"]
        roleInfo = self.db.role
        person = roleInfo.find_one({"identity": self.current_user})
        self.render("vip.html", user=self.current_user, role=person["role"])

class WelcomeGuestHandler(BaseHandler):
    @role(['guest'])
    def get(self):
        self.render("guest.html")

class LogoutHandler(BaseHandler):
     def get(self):
         if(self.get_argument("logout", None)):
             self.clear_cookie("username")
             self.redirect("/")

class PermissionHandler(BaseHandler):
    def get(self):
        self.render("permission.html")


if __name__ == '__main__':
    tornado.options.parse_command_line()

    settings = {
        "template_path": os.path.join(os.path.dirname(__file__), "templates"),
        "cookie_secret": "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=",
        "xsrf_cookies": True, # ;event the attacker "get" the cookiet, but the xsrf_cookies is safe. the attacker cann't make the false request(form)
        "login_url":"/login"
    }

    application = tornado.web.Application([
        (r'/', WelcomeHandler),
        (r'/user', WelcomeUserHandler),
        (r'/admin', WelcomeAdminHandler),
        (r'/vip', WelcomeVipHandler),
        (r'/guest', WelcomeGuestHandler),
        (r'/login', LoginHandler),
        (r'/logout', LogoutHandler),
        (r'/permission', PermissionHandler)
        ], **settings)

    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()
  1. View Code

以上是程序运行的程序,@role装饰类主要是用于根据访问者的角色判断其是否可以进行访问当前的网页。大体的测试思路是:

  • 运行程序 createDB.py建立文档

  • 运行程序 python auth.py
  • 访问localhost:8000/login,选择输入用户信息或者是匿名登录
  • 进入相应的主页,若不是匿名登录会统一来到index.html的主页,通过在这个主页中选择相应的网页,如果角色符合就可以进行访问,否则会被拒绝
  • python 关闭证书验证 windows_tornado_16

  • python 关闭证书验证 windows_tornado_17

  • python 关闭证书验证 windows_tornado_18

附加:关于cookie的,当客户端第一次登录网页并填写信息时,服务器会产生一个cookie(也就是程序中的set_security_cookie),而且在这个cookie会连同响应报文一起发给客户端;之后客户端再登录时,就可以通过这个cookie直接通过@tornado.web.authenticated的认证,所以就可以直接进入到只有一定权限才能访问的网页了。比如我们一开始没有cookie时,是访问不了localhost:8000/(这个只有登录的非匿名用户才可以访问),但是我们第一次登录后,获取cookie并在这个cookie的有效期内我们就可以不用再登录(输入用户名和密码)而直接进入localhost:8000/了。