8

問題を説明させてください:

Pyramidを介して静的アセットを提供しています:

config.add_static_view(name='static', path='/var/www/static')

そしてそれはうまくいきます。

これで、データベースにセッションを作成するカスタムセッションファクトリができました。ブラウザがセッションCookieを提示しているかどうかをチェックします。含まれている場合は、DBからセッションを検索します。そうでない場合は、DBに新しいセッションが作成され、Cookieがブラウザに返されます。

ここまでは順調ですね。

これで、(ホームページを生成する)内部ではhome_view、リクエスト変数にアクセスしません。

@view_config(route_name='home', renderer="package:templates/home.mak")
def home_view(request):
    return {}

このため、ユーザーがホームページにアクセスすると、サーバー上にセッションが作成されません。これは、Pyramidがアクセスしたときにのみセッションを怠惰request.sessionに作成するためだと思います。したがって、ホームページ要求の応答ヘッダーには、Set-Cookieセッションのヘッダーは含まれていません。

ホームページのmakoテンプレート内で、JavaScriptファイルとCSSファイルの静的URLを生成しています...

<link rel="stylesheet" href="${request.static_url(...)}"

<script src="${request.static_url(...)}"></script>

現在、私はPyramidから静的アセットを提供しているため、これらのアセットに対するすべてのリクエストはPyramidの機械全体を通過します。

つまり、ブラウザが静的アセットをフェッチするリクエストを送信すると、Pyramidが何らかの方法でセッションを作成します。つまり、Pyramidはデータベースにセッションを作成し、ブラウザが静的アセットのリクエストを送信したときにセッションCookieを送り返します。これが問題1です。

ブラウザは、静的アセットに対するすべてのリクエストを並行して送信します。FirefoxとChromeの最近のバージョンを使用しています。実際のHTMLドキュメントのHTTPリクエストはヘッダーを返さなかったSet-Cookieため、静的アセットのリクエストにはCookieヘッダーがありません。これが意味するのは、Pyramidはどのリクエストに対してもセッションCookieを認識せず、静的アセットに対して取得するリクエストごとにデータベースに新しいセッションを作成することです。

ホームページで7つの静的アセットをフェッチしている場合、7つのセッションエントリが作成されます。これは、これらすべてのリクエストがサーバーと並行して行われ、セッションCookieがないため、Pyramidがそれぞれのセッションを作成するためです。

ホームページリクエストの一部として意図的にセッションにアクセスした場合、この問題は発生しません。DBにセッションを作成し、ブラウザにCookieを送信します。ブラウザは、サーバーから要求する静的アセットごとに(並行して)Cookieを送り返します。

@view_config(route_name='home', renderer="package:templates/home.mak")
def home_view(request):
    if request.session: pass
    return {}

静的アセットリクエストでのセッションの作成を防ぐにはどうすればよいですか。さらに良いことに、静的アセットのリクエストを受け取ったときにPyramidがセッションファクトリに触れないようにしたいのですが、これは可能ですか?

次に、Pyramidが静的リクエストで新しいセッションを作成する理由がわかりません。

アップデート

これがセッションファクトリーです。

def DBSessionFactory(
        secret,
        cookie_name="sess",
        cookie_max_age=None,
        cookie_path='/',
        cookie_domain=None,
        cookie_secure=False,
        cookie_httponly=False,
        cookie_on_exception=True
    ):

    # this is the collable that will be called on every request
    # and will be passed the request
    def factory(request):
        cookieval = request.cookies.get(cookie_name)
        session_id = None
        session = None

        # try getting a possible session id from the cookie
        if cookieval is not None:
            try:
                session_id = signed_deserialize(cookieval, secret)
            except ValueError:
                pass

        # if we found a session id from  the cookie
        # we try loading the session
        if session_id is not None:
            # _load_session will return an object that implements
            # the partial dict interface (not complete, just the basics)
            session = _load_session(session_id)

        # if no session id from cookie or no session found
        # for the id in the database, create new
        if session_id is None or session is None:
            session = _create_session()

        def set_cookie(response):
            exc = getattr(request, 'exception', None)
            if exc is not None and cookie_on_exception == False:
                return
            cookieval = signed_serialize(session.session_id, secret)
            response.set_cookie(
                cookie_name,
                value=cookieval,
                max_age = cookie_max_age,
                path = cookie_path,
                domain = cookie_domain,
                secure = cookie_secure,
                httponly = cookie_httponly,
            )

        def delete_cookie(response):
            response.delete_cookie(
                cookie_name,
                path = cookie_path,
                domain = cookie_domain,
            )

        def callback(request, response):
            if session.destroyed:
                _purge_session(session)
                delete_cookie(response)
                return

            if session.new:
                set_cookie(response)

            # this updates any changes to the session
            _update_session(session)


        # at the end of request
        request.add_response_callback(callback)

        # return the session from a call to the factory
        return session

    # return from session factory
    return factory

その後、

factory = DBSessionFactory('secret')
config.set_session_factory(factory)

アップデート

私のカスタム認証:

class RootFactory:
    __acl__ = [
        (Allow, Authenticated, 'edit'),

        # only allow non authenticated users to login
        (Deny, Authenticated, 'login'),
        (Allow, Everyone, 'login'),
    ]

    def __init__(self, request):
        self.request = request



class SessionAuthenticationPolicy(CallbackAuthenticationPolicy):
    def __init__(self, callback=None, debug=False):
        self.callback = callback
        self.debug = debug

    def remember(self, request, principal, **kw):
        return []

    def forget(self, request):
        return []

    def unauthenticated_userid(self, request):
        if request.session.loggedin:
            return request.session.userid
        else:
            return None

その後、

config.set_root_factory(RootFactory)

config.set_authentication_policy(SessionAuthenticationPolicy())
config.set_authorization_policy(ACLAuthorizationPolicy())
4

2 に答える 2

2

ダミープロジェクトでこの動作を再現することはできません。そのため、ここに示されていないものに影響を与えるいくつかの構成があると思います。明らかに、認証が呼び出されると、認証ポリシーに従ってセッションが作成されます。静的アセットは(デフォルトで)登録されてNO_PERMISSION_REQUIREDいます。これは、Pyramid内の認証APIを呼び出さないことを意味します(これが事実であることを確認しました)。

静的アセットのリクエストは、リクエストパイプライン全体を呼び出します。つまり、サブスクライバー、has_permissionまたは他のセキュリティAPIを呼び出すルートファクトリにコードがある場合、またはセッションに直接触れる場合、これは表示されている動作を説明します。セッションは認証に結合されているためです。

于 2013-03-27T05:57:03.690 に答える
2

問題を再現するためのダミープロジェクトを次に示します。

  1. virtualenv環境をセットアップし、その中にPyramidをインストールします。

  2. スタータープロジェクトをインストールします。pcreate -s starter IssueApp

  3. この単純なツリーが作成されるように、不要なファイルをすべて削除します。

.
├── CHANGES.txt
├── development.ini
├── issueapp
│   ├── __init__.py
│   └── static
│       └── pyramid.png
├── README.txt
└── setup.py

アプリ全体をファイルに書き込むことに注意してください__init__.py。これにより、他のすべてが削除されます。

次に、プロジェクト(env) $ python setup.py developをインストールします。これにより、プロジェクトが仮想環境にインストールされます。

development.iniファイル:

[app:main]
use = egg:IssueApp#main

pyramid.reload_all = true
pyramid.reload_templates = true
pyramid.debug_all = true
pyramid.debug_notfound = true
pyramid.debug_routematch = true
pyramid.prevent_http_cache = true
pyramid.default_locale_name = en

[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 7777

[loggers]
keys = root, issueapp

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = INFO
handlers = console

[logger_issueapp]
level = INFO
handlers =
qualname = issueapp

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s

__init__.pyファイル:

from pyramid.config import Configurator

from pyramid.view import view_config
from pyramid.response import Response

from pyramid.authentication import CallbackAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy

from pyramid.security import (
    Allow, Deny,
    Everyone, Authenticated,
)


def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)

    #config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_static_view(name='static', path='issueapp:static')
    config.add_route('home', '/')

    config.set_root_factory(RootFactory)
    config.set_authentication_policy(DummyAuthPolicy())
    config.set_authorization_policy(ACLAuthorizationPolicy())

    config.scan()
    return config.make_wsgi_app()


@view_config(route_name='home')
def home_view(request):
    src = request.static_url('issueapp:static/pyramid.png')
    return Response('<img src='+ src + '>')


class RootFactory:
    __acl__ = [
        (Allow, Authenticated, 'edit'),
        (Deny, Authenticated, 'login'),
        (Allow, Everyone, 'login'),
    ]

    def __init__(self, request):
        self.request = request


class DummyAuthPolicy(CallbackAuthenticationPolicy):
    def __init__(self, callback=None, debug=False):
        self.callback = callback
        self.debug = debug

    def remember(self, request, principal, **kw):
        return []

    def forget(self, request):
        return []

    def unauthenticated_userid(self, request):
        # this will print the request url
        # so we can know which request is causing auth code to be called            
        print('[auth]: ' + request.url)

        # this means the user is authenticated
        return "user"

今すぐアプリを実行します

pserve  development.ini  --reload
Starting subprocess with file monitor
Starting server in PID 2303.
serving on http://0.0.0.0:7777

最後に、ブラウザからすべての履歴をクリアし(これは重要であるか、問題が明らかにならない可能性があります)、ページにアクセスします。これはコンソールに出力されます:

[auth]: http://192.168.56.102:7777/static/pyramid.png   

これは、静的リクエストに対して認証コードが呼び出されていることを示しています。

ここで、ログレベルをに設定するとDEBUG、これはページにアクセスしたときのコンソールの出力です。

pserve development.ini --reload
ファイルモニターでサブプロセスを開始する
PID2339でサーバーを起動しています。
http://0.0.0.0:7777で提供
2013-03-27 03:40:55,539 DEBUG [issueapp][Dummy-2]ルートがURLhttp://192.168.56.102:7777/に一致しました; route_name:'home'、path_info:'/'、pattern:'/'、matchdict:{}、述語:''
2013-03-27 03:40:55,540 DEBUG [issueapp] [Dummy-2] debug_authorization of url http://192.168.56.102:7777/(コンテキストに対して名前''を表示):許可(許可が登録されていません)
2013-03-27 03:40:55,685 DEBUG [issueapp][Dummy-3]ルートがURLに一致http://192.168.56.102:7777/static/pyramid.png; route_name:'__static /'、path_info:'/static/pyramid.png'、pattern:'static / * subpath'、matchdict:{'subpath':('pyramid.png'、)}、述語:''
[認証]:http://192.168.56.102:7777 / static / pyramid.png
2013-03-27 03:40:55,687 DEBUG [issueapp] [Dummy-3] debug_authorization of url http://192.168.56.102:7777/static/pyramid.png(ビュー名''をコンテキストに対して):ACLDeniedパーミッション'__no_permission_required__ 'via ACE''in ACL [(' Allow'、' system.Authenticated'、' edit')、(' Deny'、' system.Authenticated'、' login')、(' Allow'、' system.Everyone ' 、'login')]プリンシパルのコンテキスト['system.Everyone'、'system.Authenticated'、' user']


[auth]: ...メッセージは静的アセットリクエストに対しては1回だけ印刷され、ホームページリクエストに対しては印刷されないことに注意してください。これは、認証ポリシーが静的アセットについて参照されるが、通常の要求については参照されないことを意味するため、奇妙です。(もちろん、許可が含まれている場合を除きますが、私の見解ではそうではありません)。

于 2013-03-27T07:44:41.737 に答える