4

認証が必要な CherryPy Web アプリケーションがあります。次のような構成で HTTP 基本認証を実行しています。

app_config = {
    '/' : {
        'tools.sessions.on': True,
        'tools.sessions.name': 'zknsrv',
        'tools.auth_basic.on': True,
        'tools.auth_basic.realm': 'zknsrv',
        'tools.auth_basic.checkpassword': checkpassword,
        }
    }

この時点で、HTTP 認証はうまく機能します。たとえば、これにより、内部で定義した成功したログイン メッセージが表示されますAuthTest

curl http://realuser:realpass@localhost/AuthTest/

セッションがオンになっているので、Cookie を保存して、CherryPy が設定したものを調べることができます。

curl --cookie-jar cookie.jar http://realuser:realpass@localhost/AuthTest/

ファイルは最終的に次のcookie.jarようになります。

# Netscape HTTP Cookie File
# http://curl.haxx.se/rfc/cookie_spec.html
# This file was generated by libcurl! Edit at your own risk.

localhost       FALSE   /       FALSE   1348640978      zknsrv  821aaad0ba34fd51f77b2452c7ae3c182237deb3

401 Not Authorizedただし、次のように、ユーザー名とパスワードなしでこのセッション ID を指定すると、HTTPエラーが発生します。

curl --cookie 'zknsrv=821aaad0ba34fd51f77b2452c7ae3c182237deb3' http://localhost/AuthTest

私は何が欠けていますか?

助けてくれてありがとう。

4

1 に答える 1

9

したがって、短い答えは、これを行うことができるということですが、独自の CherryPy ツール(a before_handler) を作成する必要があり、CherryPy 構成で基本認証を有効にしてはなりません (つまり、などを行うべきではありませんtools.auth.on) tools.auth.basic...- HTTP 基本認証を自分で処理する必要があります。この理由は、組み込みの基本認証が明らかにかなり原始的であるためです。上記のように基本認証を有効にして何かを保護すると、セッションをチェックする前にその認証チェックが行われ、Cookie は何もしません。

散文での私の解決策

幸い、CherryPy には両方の組み込みを行う方法がありませんが、組み込みのセッション コードを使用することはできます。基本認証部分を処理するために独自のコードを記述する必要がありますが、全体としてはそれほど悪くはありません。カスタム セッション マネージャーを記述することは、Web アプリケーションにセキュリティ バグを導入する良い方法であるため、セッション コードを使用することは大きなメリットです。 .

私は最終的に、CherryPy wiki の「簡易認証およびアクセス制限ヘルパー」というページから多くのものを取得することができました。このコードは CP セッションを使用しますが、Basic Auth ではなく、ログイン フォームを送信する特別なページを使用します?username=USERNAME&password=PASSWORD。私がしたことは基本的に、提供されたcheck_auth機能を特別なログイン ページの使用から HTTP auth ヘッダーの使用に変更しただけです。

一般に、CherryPy ツールとして追加できる関数、具体的にはbefore_handler. (元のコードでは、この関数は と呼ばcheck_auth()れていましたが、名前を に変更しましたprotect()。) この関数は、最初に Cookie に (有効な) セッション ID が含まれているかどうかを確認し、それが失敗した場合、HTTP 認証情報があるかどうかを確認します。ヘッダーで。

次に、特定のページに対して認証を要求する方法が必要です。require()と、いくつかの条件を使用してこれを行います。これは、 を返す callable ですTrue。私の場合、それらの条件はzkn_admin()、およびuser_is()関数です。より複雑なニーズがある場合は、元のコードのmember_of()any_of()、およびも参照することをお勧めします。all_of()

そのようにすれば、すでにログインする方法があります。有効なセッション Cookie または HTTPBA クレデンシャルを、@require()デコレーターで保護する任意の URL に送信するだけです。今必要なのは、ログアウトする方法だけです。

(元のコードには、代わりにandAuthControllerを含むクラスがあり、CherryPy ルート クラス内に配置するだけで HTTP ドキュメント ツリー内のオブジェクト全体を使用でき、たとえばhttp://example.com/の URL でアクセスできます。 auth/loginおよびhttp://example.com/auth/logout . 私のコードは authcontroller オブジェクトを使用せず、いくつかの機能のみを使用しています。)login()logout()AuthControllerauth = AuthController()

私のコードに関するいくつかのメモ

  • 警告: 私は HTTP 認証ヘッダー用に独自のパーサーを作成したため、それは私が言ったことのみを解析します。つまり、Digest 認証などではなく、HTTP 基本認証のみを意味します。私のアプリケーションでは問題ありません。あなたの場合、そうではないかもしれません。
  • 私のコードの他の場所で定義されたいくつかの関数を想定していますuser_verify():user_is_admin()
  • debugprint()また、変数が設定されている場合にのみ出力を出力する関数も使用していDEBUGます。わかりやすくするために、これらの呼び出しは残しています。
  • 呼び出すことができますcherrypy.tools.WHATEVER(最後の行を参照)。zkauthアプリの名前に基づいて呼び出しました。authただし、それを 、または他の組み込みツールの名前と呼ばないように注意してください。
  • cherrypy.tools.WHATEVER次に、CherryPy 構成で有効にする必要があります。
  • すべての TODO: メッセージからわかるように、このコードはまだ流動的な状態にあり、エッジ ケースに対して 100% テストされていません。申し訳ありません! それでも、先に進むのに十分なアイデアが得られることを願っています。

コードでの私の解決策

import base64
import re
import cherrypy 

SESSION_KEY = '_zkn_username'

def protect(*args, **kwargs):
    debugprint("Inside protect()...")

    authenticated = False
    conditions = cherrypy.request.config.get('auth.require', None)
    debugprint("conditions: {}".format(conditions))
    if conditions is not None:
        # A condition is just a callable that returns true or false
        try:
            # TODO: I'm not sure if this is actually checking for a valid session?
            # or if just any data here would work? 
            this_session = cherrypy.session[SESSION_KEY]

            # check if there is an active session
            # sessions are turned on so we just have to know if there is
            # something inside of cherrypy.session[SESSION_KEY]:
            cherrypy.session.regenerate()
            # I can't actually tell if I need to do this myself or what
            email = cherrypy.request.login = cherrypy.session[SESSION_KEY]
            authenticated = True
            debugprint("Authenticated with session: {}, for user: {}".format(
                    this_session, email))

        except KeyError:
            # If the session isn't set, it either wasn't present or wasn't valid. 
            # Now check if the request includes HTTPBA?
            # FFR The auth header looks like: "AUTHORIZATION: Basic <base64shit>"
            # TODO: cherrypy has got to handle this for me, right? 

            authheader = cherrypy.request.headers.get('AUTHORIZATION')
            debugprint("Authheader: {}".format(authheader))
            if authheader:
                #b64data = re.sub("Basic ", "", cherrypy.request.headers.get('AUTHORIZATION'))
                # TODO: what happens if you get an auth header that doesn't use basic auth?
                b64data = re.sub("Basic ", "", authheader)

                decodeddata = base64.b64decode(b64data.encode("ASCII"))
                # TODO: test how this handles ':' characters in username/passphrase.
                email,passphrase = decodeddata.decode().split(":", 1)

                if user_verify(email, passphrase):

                    cherrypy.session.regenerate()

                    # This line of code is discussed in doc/sessions-and-auth.markdown
                    cherrypy.session[SESSION_KEY] = cherrypy.request.login = email
                    authenticated = True
                else:
                    debugprint ("Attempted to log in with HTTBA username {} but failed.".format(
                            email))
            else:
                debugprint ("Auth header was not present.")

        except:
            debugprint ("Client has no valid session and did not provide HTTPBA credentials.")
            debugprint ("TODO: ensure that if I have a failure inside the 'except KeyError'"
                        + " section above, it doesn't get to this section... I'd want to"
                        + " show a different error message if that happened.")

        if authenticated:
            for condition in conditions:
                if not condition():
                    debugprint ("Authentication succeeded but authorization failed.")
                    raise cherrypy.HTTPError("403 Forbidden")
        else:
            raise cherrypy.HTTPError("401 Unauthorized")

cherrypy.tools.zkauth = cherrypy.Tool('before_handler', protect)

def require(*conditions):
    """A decorator that appends conditions to the auth.require config
    variable."""
    def decorate(f):
        if not hasattr(f, '_cp_config'):
            f._cp_config = dict()
        if 'auth.require' not in f._cp_config:
            f._cp_config['auth.require'] = []
        f._cp_config['auth.require'].extend(conditions)
        return f
    return decorate

#### CONDITIONS
#
# Conditions are callables that return True
# if the user fulfills the conditions they define, False otherwise
#
# They can access the current user as cherrypy.request.login

# TODO: test this function with cookies, I want to make sure that cherrypy.request.login is 
#       set properly so that this function can use it. 
def zkn_admin():
    return lambda: user_is_admin(cherrypy.request.login)

def user_is(reqd_email):
    return lambda: reqd_email == cherrypy.request.login

#### END CONDITIONS

def logout():
    email = cherrypy.session.get(SESSION_KEY, None)
    cherrypy.session[SESSION_KEY] = cherrypy.request.login = None
    return "Logout successful"

cherrypy.tools.WHATEVERあとは、CherryPy 構成で組み込みセッションと独自のセッションの両方を有効にするだけです。繰り返しますが、有効にしないように注意してcherrypy.tools.authください。私の構成は次のようになりました。

config_root = {
    '/' : {
        'tools.zkauth.on': True, 
        'tools.sessions.on': True,
        'tools.sessions.name': 'zknsrv',
        }
    }
于 2012-12-19T19:19:59.277 に答える