如何在Django网站中验证urllib2脚本以访问HTTPS web服务?
大家好。
我正在开发一个使用 Django、mod_wsgi 和 Apache2 的网站,这个网站通过 HTTPS 来处理所有请求和响应,以保护敏感信息。所有的页面都设置了重定向,如果用户没有登录,就会被引导到登录页面。此外,还有几个页面是像 RESTful 网络服务那样工作的。
现在我正在写一个脚本,使用 urllib/urllib2 来联系这些服务,以便下载一系列非常大的文件。但是在尝试登录时,我遇到了 403: FORBIDDEN 的错误。
我用来进行身份验证和登录的方法(初步草稿)是:
def login( base_address, username=None, password=None ):
# prompt for the username (if needed), password
if username == None:
username = raw_input( 'Username: ' )
if password == None:
password = getpass.getpass( 'Password: ' )
log.info( 'Logging in %s' % username )
# fetch the login page in order to get the csrf token
cookieHandler = urllib2.HTTPCookieProcessor()
opener = urllib2.build_opener( urllib2.HTTPSHandler(), cookieHandler )
urllib2.install_opener( opener )
login_url = base_address + PATH_TO_LOGIN
log.debug( "login_url: " + login_url )
login_page = opener.open( login_url )
# attempt to get the csrf token from the cookie jar
csrf_cookie = None
for cookie in cookieHandler.cookiejar:
if cookie.name == 'csrftoken':
csrf_cookie = cookie
break
if not cookie:
raise IOError( "No csrf cookie found" )
log.debug( "found csrf cookie: " + str( csrf_cookie ) )
log.debug( "csrf_token = %s" % csrf_cookie.value )
# login using the usr, pwd, and csrf token
login_data = urllib.urlencode( dict(
username=username, password=password,
csrfmiddlewaretoken=csrf_cookie.value ) )
log.debug( "login_data: %s" % login_data )
req = urllib2.Request( login_url, login_data )
response = urllib2.urlopen( req )
# <--- 403: FORBIDDEN here
log.debug( 'response url:\n' + str( response.geturl() ) + '\n' )
log.debug( 'response info:\n' + str( response.info() ) + '\n' )
# should redirect to the welcome page here, if back at log in - refused
if response.geturl() == login_url:
raise IOError( 'Authentication refused' )
log.info( '\t%s is logged in' % username )
# save the cookies/opener for further actions
return opener
我使用 HTTPCookieHandler 来在脚本中存储 Django 的认证 cookies,这样我就可以访问网络服务并通过重定向。
我知道 Django 的 CSRF 中间件会让我出局,如果我没有在登录信息中传递 csrf token,所以我首先从第一次加载页面/表单的 cookiejar 中提取这个 token。正如我提到的,这在 HTTP/开发版本的网站上是有效的。
具体来说,当我尝试通过 HTTPS 连接将凭据提交到登录页面/表单时,出现了 403 错误。而在使用 HTTP 连接的开发服务器上,这个方法是有效的。
我没有看到任何 Apache 的目录指令会阻止访问那个区域。脚本可以成功连接到登录页面而不发送任何数据,所以我觉得问题可能不在 Apache 上(但我也可能错了)。
我使用的 Python 安装都是编译过 SSL 的。
我还读到 urllib2 不允许通过代理进行 HTTPS 连接。我对代理不太熟悉,所以不确定从远程机器运行脚本是否算作代理连接,这是否会导致问题。这个问题会导致访问失败吗?
从我能看出来,问题可能出在 cookies 和提交的数据组合上,但我不太清楚接下来该怎么做。
任何帮助都将不胜感激。谢谢!
2 个回答
这个在我的Django设置上运行得很好,使用的是https,灵感来自于你的代码。我开始觉得问题可能不在这段代码里……服务器有没有什么提示?我可能需要看看apache的设置。
我在本地机器上用ssl通过nginx连接到我的服务器,所以apache可能是需要关注的地方。我想一个缩小范围的方法是试试你的脚本在我的登录页面上能不能工作 :) 有空发个邮件给我吧!
import urllib
import urllib2
import contextlib
def login(login_url, username, password):
"""
Login to site
"""
cookies = urllib2.HTTPCookieProcessor()
opener = urllib2.build_opener(cookies)
urllib2.install_opener(opener)
opener.open(login_url)
try:
token = [x.value for x in cookies.cookiejar if x.name == 'csrftoken'][0]
except IndexError:
return False, "no csrftoken"
params = dict(username=username, password=password, \
this_is_the_login_form=True,
csrfmiddlewaretoken=token,
)
encoded_params = urllib.urlencode(params)
with contextlib.closing(opener.open(login_url, encoded_params)) as f:
html = f.read()
print html
# we're in.
请原谅我自己回答自己的问题,但我想记录一下,这似乎解决了我的问题:
原来我需要在发送登录信息的请求中,将HTTP Referer头设置为登录页面的URL。
req.add_header( 'Referer', login_url )
原因可以在Django的CSRF文档中找到,特别是第4步。
由于我们的服务器设置有点特殊,生产环境使用HTTPS,而DEBUG设置为False,所以我没有看到通常在DEBUG信息中输出的csrf_failure失败原因(在这种情况下是:'Referer检查失败 - 没有referer')。我最后把这个失败原因打印到了Apache的错误日志中,然后在网上查了一下。这样我找到了code.djangoproject/.../csrf.py和Referer头的解决办法。