DecoratorをつかってApp EngineでBasic認証.

DecoratorをつかってGoogle App EngineBasic認証をする仕掛けを作ってみた.Google App Engineにはデフォルトでgoogleのアカウントを使っての認証が入っていて,人間を相手のサービスを作るには簡単でいいのだけど,クライアント側も作ろうと思うとちょっと面倒なので.

使い方

webapp.RequestHandlerのgetメソッドなどを修飾することを前提としている.こんな感じ.第一引数がユーザ名とパスワードのdict, 第二引数がrealmである.

userDict = {"userA": "passA",
	    "userB": "passB"}

class MainPage(webapp.RequestHandler):
    @basicAuth(userDict, "simplestore")
    def get(self):
	logging.info('authenticated as ' + self.request.basic_user)
	...

認証が成功した場合には,self.request.basic_userにユーザ名をセットする.

実装

実装にはhttp://d.hatena.ne.jp/sugyan/20090311/1236724687を参考にさせていただいた.ありがとうございます.

フローとしては,すごく単純化して書くとこんな感じ.

  • 'Authorization'ヘッダが無ければ,401を返してbasic認証を要求
  • 'Authorization'ヘッダがあれば,中身を解析してユーザ辞書とつき合わせる.
    • 認証に成功すれば本体の関数を実行
    • 失敗したら401を返して再度認証を要求
import base64
import logging

def basicAuth(userDict, realm):
  def _deco(_func):
    def _f(_self, *args, **kw):
      def _send_401(_self, message):
        _self.response.set_status(401)
        _self.response.headers['WWW-Authenticate'] = \
           'Basic realm="%s"' % (realm)
        _self.response.out.write('<body><h1>%s</h1></body>\n' % message)

      auth_header = _self.request.headers.get('Authorization')        
      msg = 'Basic authentication required'
      if auth_header:
        try:
          (scheme, base64Str) = auth_header.split(' ')
          if scheme == 'Basic':
            (username, password) = base64.b64decode(base64Str).split(':')
            if userDict[username] == password:
              _self.request.basic_user = username
              _func(_self, *args, **kw)
              return
            else:
              logging.info("failed login attempt :" + username)
          else:
            logging.error("got invalid scheme from client " + schme)
            msg = "Only the basic authentication is accepted."
        except KeyError, err:
          logging.info("failed login attempt :" + username)
        except (ValueError, TypeError), err:
          logging.info("failed to authenticate: "+ err.__str__())
        _send_401(_self, msg)
            
    return _f
  return _deco