Tornado web.authenticated 用戶認證淺析

原址:http://www.cnblogs.com/apexchu/p/4239844.html

在Web服務中會有用戶登錄後的一系列操作, 如果一個客戶端的http
請求要求是用戶登錄後才能做得操作, 那麼 Web服務器接收請求時
需要判斷該請求裏帶的數據是否有用戶認證的信息.

使用 Tornado 框架開發Web服務, 框架裏提供了tornado.web.authenticated
的 decorator 的輔助開發者做用戶登錄認證, 即開發者在實現一個 handler
(對應一個url資源, 繼承於tornado.web.RequestHandler)時,
該 url的資源操作需要有用戶認證或者登錄爲前提, 那麼在資源請求的方法
覆寫時(overwritten), 例如在 get 與 post 方法定義前以
tornado.web.authenticated 裝飾,並且同時覆寫 get_current_user
方法(RequestHandler只是定義空函數, 默認放回None). 在覆寫之後,
RequestHandler 類的實例裏 current_user 就會有值. current_user
在 tornado源碼中是 getter setter的實現, 真正的成員變量是 _current_user
(稍後解析tornado裏的源碼). authenticated 即實現了 current_user 判斷
這一過程來驗證用戶.

 

先來看簡單的例子(已添加註釋 代碼來自中文文檔):

不使用 tornado.web.authenticated, 直接判斷 current_user 成員

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

# 簡單的用戶認證實現

 

# BaseHandler 基類覆寫 get_current_user

# 覆寫後 RequestHandler 的current_user成員會有值(稍後解釋實現源碼)

# 這裏簡單地判斷請求帶的 secure cookie 是否帶有 user屬性的值

class BaseHandler(tornado.web.RequestHandler):

    def get_current_user(self):

        return self.get_secure_cookie("user")

 

# 實際業務類實現

class MainHandler(BaseHandler):

    def get(self):

        # 判斷 current_user, 如果不存在值,要求重定向到 login頁面

        if not self.current_user:

            self.redirect("/login")

            return

        name = tornado.escape.xhtml_escape(self.current_user)

        self.write("Hello, " + name)

 

class LoginHandler(BaseHandler):

    def get(self):

        self.write('<html><body><form action="/login" method="post">'

                   'Name: <input type="text" name="name">'

                   '<input type="submit" value="Sign in">'

                   '</form></body></html>')

 

    def post(self):

        self.set_secure_cookie("user", self.get_argument("name"))

        self.redirect("/")

 

application = tornado.web.Application([

    (r"/", MainHandler),

    (r"/login", LoginHandler),

], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo="

 

 

 

 

在 Get 方法上添加 authenticated 裝飾器實現用戶認證:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

# 使用裝飾器實現用戶認證

 

class MainHandler(BaseHandler):

    @tornado.web.authenticated

    def get(self):

        """

        直接寫業務邏輯代碼, 方法中不必考慮多寫一份判斷

        代碼少即是多的原則

        """

        name = tornado.escape.xhtml_escape(self.current_user)

        self.write("Hello, " + name)

 

# cookie_secret 是用於 secure_cookie 加密實現的

settings = {

    "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",

    "login_url": "/login",

}  

 

application = tornado.web.Application([

    (r"/", MainHandler),

    (r"/login", LoginHandler),

], **settings) #**  

 

 

 

看完實現的小例子, 就要探究其 decorator 的實現細節:
以知曉 tornado 爲何可以輔助開發者更方便實現用戶認證
源碼版本 tornado 4.0.2 tornado/web.py (已添加註釋):

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

# RequestHandler current_user 與 authenticated實現細節

 

class RequestHandler(object):

    """    

    property 裝飾器將 current_user 設置爲 getter 方法.

    即 handler.current_user 可以當作類數據成員的方式書寫使用

    不需要以方法書寫 

    """

    @property

    def current_user(self):

        """The authenticated user for this request.

 

 

        This is a cached version of `get_current_user`, which you can

        override to set the user based on, e.g., a cookie. If that

        method is not overridden, this method always returns None.

 

 

        We lazy-load the current user the first time this method is called

        and cache the result after that.

        """

        """

        延遲(lazy)方式加載 _current_user值,

        即從 get_current_user()方法中獲取值,

        因此 get_current_user 需要開發者自己覆寫內容.  

        """

        if not hasattr(self, "_current_user"):

            self._current_user = self.get_current_user()

        return self._current_user

 

    @current_user.setter

    def current_user(self, value):

        self._current_user = value

 

    def get_current_user(self):

        """

        默認返回 None,

        之前的 BaseHandler 的樣例代碼覆寫判斷邏輯時

        使用的是 cookie 是否存在 user 屬性作爲判斷

        """

 

        """Override to determine the current user from, e.g., a cookie."""

        return None  

 

# authenticated 裝飾器

def authenticated(method):

    """Decorate methods with this to require that the user be logged in.

 

    If the user is not logged in, they will be redirected to the configured

    `login url <RequestHandler.get_login_url>`.

 

    If you configure a login url with a query parameter, Tornado will

    assume you know what you're doing and use it as-is.  If not, it

    will add a `next` parameter so the login page knows where to send

    you once you're logged in.

    """

    @functools.wraps(method)

    def wrapper(self, *args, **kwargs):

        """

        這裏調用的是 current_user 的 get 方法(property裝飾),

        緊接着調用 return self._current_user

        原本放在業務邏輯代碼中做的判斷, 現在交給 decorator 幫助  

        開發者, 開發者可以少寫代碼, 專注自己的業務

        """

        if not self.current_user:    

            if self.request.method in ("GET", "HEAD"):

                url = self.get_login_url()

                if "?" not in url:

                    if urlparse.urlsplit(url).scheme:

                        # if login url is absolute, make next absolute too

                        next_url = self.request.full_url()

                    else:

                        next_url = self.request.uri

                    url += "?" + urlencode(dict(next=next_url))

                self.redirect(url)

                return

            raise HTTPError(403)

        return method(self, *args, **kwargs)

    return wrapper  

 

 

 

這裏我們要理解的是 authenticated 裝飾器的用法, 繼承於
RequestHandler 的 handler 類, 開發者覆寫 get post 方法
實現時, 如果要判斷請求的合理性(即用戶是否被認證過), 可
以在覆寫方法裏業務代碼前加上判斷代碼, 這樣也可以實現
同樣的功能, 而 Tornado 利用了Python的語言特性, 將用戶
認證的代碼通過 decorator “橋接” 完成, 即 get post 這些 http
請求方法裏的代碼可以保持功能的專注度. 此外, 如果開發
需求更改, 資源請求不需要用戶認證時, 可直接註釋或者刪除
方法上方的 decorator 即可, 方便快捷省事:).

用戶認證未通過的重定向設置

當用戶沒有認證通過時, 可以在程序入口, 設置 settings dict 屬性,
設置 login_url 屬性 參考文檔
“””
login_url: The authenticated decorator will redirect to this url
if the user is not logged in. Can be further customized
by overriding RequestHandler.get_login_url
“””
樣例:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

# settings 屬性設置

 

settings = dict(

# ...

login_url = "/login",

# ...

)

 

"""

tornado.web.authenticated 未通過時, 默認  

redirect 到 "/login"  

"""

application = tornado.web.Application(handlers, **settings)  

 

 

 


用戶認證在什麼場景下使用:

我們通常的業務需求中, 會涉及到 session會話保持 與 cookie 的
用戶數據的讀取場景, 即從 http 請求的 cookie 中讀取 sessionid,
以 sessionid 爲 key, 從內存或者緩存中判斷 sessionid 是否存在值,
以此作爲用戶登錄狀態的認證, 或者是用戶重新打開瀏覽器, 之前
瀏覽器緩存的cookie裏的sessionid重新發送給客戶端, 用戶無需
重新輸入賬號密碼, 即可直接在登錄狀態. 較前兩年基於 memcache
做服務端 session 的緩存, 現在可以使用 Redis 服務替代 memcache,
做緩存數據庫的工作.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章