原址: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,
做缓存数据库的工作.