专栏首页敏而好学Easy Basic HTTP authentication with Tornado

Easy Basic HTTP authentication with Tornado

I recently got a chance to play around with Tornado, which is pretty neat (although that certainly isn’t news). One thing that I tried to do pretty quickly and had a hard time with was Basic authentication (you know, the little “so-and-so requires a username and password” pop-up). Paulo Suzart posted a working example over on gist, but it was a bit light on context, and Dhanan Jaynene’s request interceptors are a bit overkill for this purpose (although very useful for more complex behavior!). Let’s start with the “hello world” example from theTornado site, and I’ll show you what I’ve cooked up. I’m only an hour or two into exploring Tornado, like I hinted at above, so if you see any room for improvement, I’d love to hear from you. (I’ve made all the code I’ve written very verbose in the hopes that’ll help people understand and customize it without much trial-and-error. Feel free to tighten things up.)

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

application = tornado.web.Application([
    (r"/", MainHandler),
])

if __name__ == "__main__":
    application.listen(8888)    tornado.ioloop.IOLoop.instance().start()

Now, what I really wanted was to be able to add a decorator to any RequestHandler that (without any other modifications) would perform all the back-and-forth with the browser, so that I could write something like this:

@require_basic_auth
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

If you’re not familiar with Python decorators, in the simplest usage, this will effectively lead to the function require_basic_auth being invoked with MainHandler (the class) as its argument. Whatever that function returns will replace the definition of MainHandler. Nifty, huh? Let’s see how we can use this. We could mess with the get(), put(), etc. methods on RequestHandler, but it turns out that there’s a method that Tornadoinvokes above them called _execute(). If we replace that one, we’ll be able to do all of this in one fell swoop.

def require_basic_auth(handler_class):
    # Should return the new _execute function, one which enforces
    # authentication and only calls the inner handler's _execute() if
    # it's present.
    def wrap_execute(handler_execute):
        # I've pulled this out just for clarity, but you could stick
        # it in _execute if you wanted.  It returns True iff
        # credentials were provided.  (The end of this function might
        # be a good place to see if you like their username and
        # password.)
        def require_basic_auth(handler, kwargs):
            auth_header = handler.request.headers.get('Authorization')
            if auth_header is None or not auth_header.startswith('Basic '):
                # If the browser didn't send us authorization headers,
                # send back a response letting it know that we'd like
                # a username and password (the "Basic" authentication
                # method).  Without this, even if you visit put a
                # username and password in the URL, the browser won't
                # send it.  The "realm" option in the header is the
                # name that appears in the dialog that pops up in your
                # browser.
                handler.set_status(401)
                handler.set_header('WWW-Authenticate', 'Basic realm=Restricted')
                handler._transforms = []
                handler.finish()
                return False
            # The information that the browser sends us is
            # base64-encoded, and in the format "username:password".
            # Keep in mind that either username or password could
            # still be unset, and that you should check to make sure
            # they reflect valid credentials!
            auth_decoded = base64.decodestring(auth_header[6:])
            username, password = auth_decoded.split(':', 2)
            kwargs['basicauth_user'], kwargs['basicauth_pass'] = username, password
            return True

        # Since we're going to attach this to a RequestHandler class,
        # the first argument will wind up being a reference to an
        # instance of that class.
        def _execute(self, transforms, *args, **kwargs):
            if not require_basic_auth(self, kwargs):
                return False
            return handler_execute(self, transforms, *args, **kwargs)

        return _execute

    handler_class._execute = wrap_execute(handler_class._execute)
    return handler_class

It’s pretty simple, isn’t it? There is one subtlety to keep track of, and it has to do with kwargs (the keyword argument dictionary). If you’ll notice, require_basic_auth uses it without the stars. This lets us pass a reference to the dictionary object, and not send the key/value pairs in the dictionary to require_basic_auth as arguments. Then, when we modify the dictionary, the username and password get passed into handler_execute() as arguments. Naturally, we have to change our RequestHandler to take them:

@require_basic_auth
class MainHandler(tornado.web.RequestHandler):
    def get(self, basicauth_user, basicauth_pass):
        self.write('Hi there, {0}!  Your password is {1}.' \
            .format(basicauth_user, basicauth_pass))
    def post(self, **kwargs): 
        basicauth_user = kwargs['basicauth_user']
        basicauth_pass = kwargs['basicauth_pass']
        self.write('Hi there, {0}!  Your password is {1}.' \
            .format(basicauth_user, basicauth_pass))

Either method works—you can take them as normal arguments, or as keyword arguments again. P.S.: If we tighten up the decorator, we wind up with this:

def require_basic_auth(handler_class):
    def wrap_execute(handler_execute):
        def require_basic_auth(handler, kwargs):
            auth_header = handler.request.headers.get('Authorization')
            if auth_header is None or not auth_header.startswith('Basic '):
                handler.set_status(401)
                handler.set_header('WWW-Authenticate', 'Basic realm=Restricted')
                handler._transforms = []
                handler.finish()
                return False
            auth_decoded = base64.decodestring(auth_header[6:])
            kwargs['basicauth_user'], kwargs['basicauth_pass'] = auth_decoded.split(':', 2)
            return True
        def _execute(self, transforms, *args, **kwargs):
            if not require_basic_auth(self, kwargs):
                return False
            return handler_execute(self, transforms, *args, **kwargs)
        return _execute

    handler_class._execute = wrap_execute(handler_class._execute)
    return handler_class

参考: http://kelleyk.com/post/7362319243/easy-basic-http-authentication-with-tornado

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • kvm常见故障及解决方案

    在虚拟机运行过程中关闭宿主服务器就有可能导致这种情况出现,由于宿主服务器中的kvm虚拟机控制器与安装在kvm中的虚拟机会话被异常重置,所以我们可以如下解决:

    党志强
  • Linux进程的Uninterruptible sleep(D)状态

    Here are the different values that the s, stat and state output specifiers (head...

    党志强
  • OpenIndiana 151a8 发行注记

       oi_151a_prestable8也叫oi_151a8,该版本修复了一些bug,添加了一些安全补丁,并且第一次重新编译了JDS。该版本以ISO的形式发...

    党志强
  • mydumper安装笔记

    参考:http://www.tuicool.com/articles/2eAVVvN

    二狗不要跑
  • 项目上线后,谈一下感触比较深的一点:查询优化

    儿子有道题不会做,喊我过去教他。我推了推一旁的老公:我头疼,你去吧。老公不动,我:零花钱涨一千。话音刚落,老公就屁颠屁颠跑去儿子房间。进去不到几分钟,一声怒吼伴...

    数据和云
  • python note #1

    To record my process of studying python and to practice my English meanwhile, I'...

    用户2398817
  • 业界 | 从视频到语句,优必选获TRECVID 2017子任务冠军

    机器之心
  • React Native 圆形进度条组件

    npm i--save react-native-circular-progress

    forrest23
  • How to use Google Test for C++ in Visual Studio

    In Visual Studio 2017 version 15.5 and later, Google Test is integrated into the...

    战神伽罗
  • SpringCloud分布式开发五大神兽

    IT故事会

扫码关注云+社区

领取腾讯云代金券