python实现简单用户认证和角色制授权
实验目的
- 了解 BRAC 原理
- 基于 Tornado 框架,编写简单认证与授权程序
实验素材
任务 1:实现简单用户认证
- 阅读 自学教程 6.3 了解用户认证; 教程 4.1 和 4.2 了解 mongodb 使用。 也可以使用 tornado 推荐的方法引入数据库连接 RequestHandler.initialize。
- 在 mongodb 中建立 user文档,必须包含 identity, alias name, password 三个属性。其中 password 的内容用 MD5 加密。
- 修改程序 cookies.py, 另存为 sample_auth.py , 该程序能够数据库实现用户认证。
注:教程上的缺陷:教程在 application 类中初始化了 mongodb 的连接,这等于默认应用运行于单线程之下。实战中, 建议每次使用前连接。 不用担心连接开销, 驱动一般内置 缓冲池 管理的。
任务 2:实现按角色授权
- 修改 sample_auth.py 为 auth.py 。 该程序支持 admins, users, vips, guests 四种角色。 匿名用户默认属于 guests, 登录用户默认 属于 users。
- auth.py 至少有四个页面,支持不同角色的服务。 请修改 mongodb ,加入合适的数据。
- 为了方便设置权限, 请实现装饰器 @roles(rolelist),例如, @role([‘vips’,’user’]) 表示vip和普通用户都可以访问的url, 其他用户转没有权限网页
任务1步骤如下:
- 了解教程中的用户认证方式;
- 学会如何向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,如下:
- 修改教程所给代码如下
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, #http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html ;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”,并输入测试数据,就可以了。
- 这个实验主要是学会使用python 操作mongodb并体验下利用数据库和装饰器来进行用户认证
任务二:
- 首先要理解和编写装饰类,了解其中的原理:除了实验中所说的,还可以看看http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819879946007bbf6ad052463ab18034f0254bf355000;主要是要明白装饰类是用来增强原先的函数的,更加本质上其实是修改了原本的函数指针,但没有改变它的运行上下文,也就是可以访问原先函数可以访问的,比如self,理解这个很重要,也正是因为这个原因,所以装饰器@tornado.web.authenticated才可以进行验证self.current_user是否存在。
- 根据实验需要编写如下的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中)
<!--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>
<!--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>
<!--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>
<!--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>
<!--permission.html--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Welcome Back!</title> </head> <body> <h1>Permission denied</h1> </body> </html>
<!--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>
<!--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>
虽然里面几个文件的内容都是一样的,但只是为了方便,实验中主要是为了进行测试,不同的角色能够访问的网页是不同的。
-
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, #http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html ;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()
以上是程序运行的程序,@role装饰类主要是用于根据访问者的角色判断其是否可以进行访问当前的网页。大体的测试思路是:
-
运行程序 createDB.py建立文档
- 运行程序 python auth.py
- 访问localhost:8000/login,选择输入用户信息或者是匿名登录
- 进入相应的主页,若不是匿名登录会统一来到index.html的主页,通过在这个主页中选择相应的网页,如果角色符合就可以进行访问,否则会被拒绝
-
附加:关于cookie的,当客户端第一次登录网页并填写信息时,服务器会产生一个cookie(也就是程序中的set_security_cookie),而且在这个cookie会连同响应报文一起发给客户端;之后客户端再登录时,就可以通过这个cookie直接通过@tornado.web.authenticated的认证,所以就可以直接进入到只有一定权限才能访问的网页了。比如我们一开始没有cookie时,是访问不了localhost:8000/(这个只有登录的非匿名用户才可以访问),但是我们第一次登录后,获取cookie并在这个cookie的有效期内我们就可以不用再登录(输入用户名和密码)而直接进入localhost:8000/了。