我有一个需要身份验证的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
但是,如果我在没有用户名和密码的情况下提供此会话ID,我将收到HTTP 401 Not Authorized
失败,如下所示:
curl --cookie 'zknsrv=821aaad0ba34fd51f77b2452c7ae3c182237deb3' http://localhost/AuthTest
我错过了什么?
非常感谢您的帮助。
答案 0 :(得分:9)
所以,简短的回答是你可以做到这一点,但你必须编写自己的CherryPy工具(before_handler
),你一定不能在CherryPy配置中启用基本身份验证(也就是说,您不应该执行tools.auth.on
或tools.auth.basic...
之类的任何操作) - 您必须自己处理HTTP基本身份验证。原因是内置的基本身份验证功能显然非常原始。如果您通过启用基本身份验证来保护某些内容,就像我上面那样,它会在检查会话之前执行身份验证检查,并且您的cookie将不执行任何操作。
幸运的是,即使CherryPy无法同时执行这两种操作,您仍然可以使用其内置的会话代码。您仍然需要编写自己的代码来处理基本身份验证部分,但总的来说这并不是很糟糕,使用会话代码是一个很大的胜利,因为编写自定义会话管理器是将安全漏洞引入Web应用程序的好方法。
我最终能够从名为Simple authentication and access restrictions helpers的CherryPy wiki上的页面中获取大量内容。该代码使用CP会话,但它不使用基本身份验证,而是使用具有提交?username=USERNAME&password=PASSWORD
的登录表单的特殊页面。我所做的基本上只是将提供的check_auth
函数从使用特殊登录页面更改为使用HTTP身份验证头。
通常,您需要一个可以添加为CherryPy工具的功能 - 特别是before_handler
。 (在原始代码中,此函数名为check_auth()
,但我将其重命名为protect()
。)此函数首先尝试查看cookie是否包含(有效)会话ID,如果失败,它会尝试查看标头中是否有HTTP身份验证信息。
然后,您需要一种方法来要求对给定页面进行身份验证;我使用require()
以及一些条件执行此操作,这些只是返回True
的callables。就我而言,这些条件是zkn_admin()
和user_is()
函数;如果您有更复杂的需求,您可能还需要查看原始代码中的member_of()
,any_of()
和all_of()
。
如果您这样做,您已经有办法登录 - 您只需向使用@require()
装饰器保护的任何URL提交有效的会话cookie或HTTPBA凭据。您现在需要的只是一种注销方式。
(原始代码包含AuthController
类,其中包含login()
和logout()
,您可以通过放置HTTP文档树中的整个AuthController
对象在您的CherryPy根类中auth = AuthController()
,并使用例如http://example.com/auth/login和http://example.com/auth/logout的URL来访问它。我的代码不使用authcontroller对象,只是一些函数。)
user_verify()
和user_is_admin()
debugprint()
函数,该函数仅在设置DEBUG
变量时打印输出,为了清楚起见,我已将这些调用留下。cherrypy.tools.WHATEVER
(见最后一行);我根据应用名称调用了zkauth
。但请注意不要将其称为auth
,或称为任何其他内置工具的名称。cherrypy.tools.WHATEVER
。 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配置中启用内置会话和您自己的cherrypy.tools.WHATEVER
。同样,请注意不要启用cherrypy.tools.auth
。我的配置最终看起来像这样:
config_root = {
'/' : {
'tools.zkauth.on': True,
'tools.sessions.on': True,
'tools.sessions.name': 'zknsrv',
}
}