前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Easy Basic HTTP authentication with Tornado

Easy Basic HTTP authentication with Tornado

作者头像
党志强
发布2020-02-11 11:36:29
9550
发布2020-02-11 11:36:29
举报
文章被收录于专栏:敏而好学

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.)

代码语言:javascript
复制
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:

代码语言:javascript
复制
@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.

代码语言:javascript
复制
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:

代码语言:javascript
复制
@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:

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015/07/01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档