實驗目的
- 了解 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/了。