为什么 Selenium 会导致 CSRF 403?
Why is Selenium causing a CSRF 403?
我正在尝试使用 Django 和 Selenium 创建一个简单的登录测试,但由于 CSRF 失败而收到 403。我期待中间件在 GET 请求上添加 cookie,然后在 POST 上将其解析回来。
这是我目前检查过的内容:
1. cookie 是否在 /accounts/login/
的 GET 请求中设置?
Yes, the cookie is being set in the process_response
method
2。 cookie 在 Selenium 驱动程序上可用吗?
Yes
ipdb> self.selenium.get_cookies()
[{u'domain': u'localhost', u'name': u'csrftoken', u'value': u'DzNbEn9kZw0WZQ4OsRLouriFN5MOIQos', u'expiry': 1470691410, u'path': u'/', u'httpOnly': False, u'secure': True}]
3。在 POST 请求期间是否找到 cookie?
No, this try/except from django.middleware.CsrfViewMiddleware.process_view
fails:
try:
csrf_token = _sanitize_token(
request.COOKIES[settings.CSRF_COOKIE_NAME])
# Use same token next time
request.META['CSRF_COOKIE'] = csrf_token
except KeyError:
csrf_token = None
# Generate token and store it in the request, so it's
# available to the view.
request.META["CSRF_COOKIE"] = _get_new_csrf_key()
代码
class TestLogin(StaticLiveServerTestCase):
@classmethod
def setUpClass(cls):
cls.selenium = getattr(webdriver, settings.SELENIUM_WEBDRIVER)()
cls.selenium.maximize_window()
cls.selenium.implicitly_wait(5)
super(TestLogin, cls).setUpClass()
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super(TestLogin, cls).tearDownClass()
def test_login(self):
self.selenium.get('{}{}'.format(self.live_server_url, '/accounts/login/?next=/'))
assert "Django" in self.selenium.title
un_el = self.selenium.find_element_by_id('id_username').send_keys('the_un')
pw_el = self.selenium.find_element_by_id('id_password')
pw_el.send_keys('the_pw')
pw_el.send_keys(Keys.RETURN)
try:
WebDriverWait(self.selenium, 5).until(EC.title_contains("New Title"))
except TimeoutException as e:
msg = "Could not find 'New Title' in title. Current title: {}".format(self.selenium.title)
raise TimeoutException(msg)
finally:
self.selenium.quit()
问题
接下来我可以尝试什么来调试它?
老问题,但在坚持了几个小时后,答案很简单。
来自docs:
If a browser connects initially via HTTP, which is the default for
most browsers, it is possible for existing cookies to be leaked. For
this reason, you should set your SESSION_COOKIE_SECURE and
CSRF_COOKIE_SECURE settings to True. This instructs the browser to
only send these cookies over HTTPS connections. Note that this will
mean that sessions will not work over HTTP, and the CSRF protection
will prevent any POST data being accepted over HTTP (which will be
fine if you are redirecting all HTTP traffic to HTTPS).
像我一样,您的大部分工作可能都使用 django_extensions + Werkzeug,默认情况下 运行 所有本地工作都通过 SSL。
如果您使用的是 unittest
或 Djangos 版本,我建议您在测试运行时修改这些设置,如下所示:
...
from django.conf import settings
class ProfilePagetest(LiveServerTestCase):
def setUp(self):
settings.CSRF_COOKIE_SECURE = False
settings.SESSION_COOKIE_SECURE = False
self.url = reverse('clientpage:profile')
self.username = 'name@names.com'
self.password = 'strange decisions...'
get_user_model().objects.create_user(self.username, self.username, self.password)
self.browser = webdriver.Firefox()
这应该可以解决 CSRF 验证问题。
我正在尝试使用 Django 和 Selenium 创建一个简单的登录测试,但由于 CSRF 失败而收到 403。我期待中间件在 GET 请求上添加 cookie,然后在 POST 上将其解析回来。
这是我目前检查过的内容:
1. cookie 是否在 /accounts/login/
的 GET 请求中设置?
Yes, the cookie is being set in the
process_response
method
2。 cookie 在 Selenium 驱动程序上可用吗?
Yes
ipdb> self.selenium.get_cookies()
[{u'domain': u'localhost', u'name': u'csrftoken', u'value': u'DzNbEn9kZw0WZQ4OsRLouriFN5MOIQos', u'expiry': 1470691410, u'path': u'/', u'httpOnly': False, u'secure': True}]
3。在 POST 请求期间是否找到 cookie?
No, this try/except from
django.middleware.CsrfViewMiddleware.process_view
fails:
try:
csrf_token = _sanitize_token(
request.COOKIES[settings.CSRF_COOKIE_NAME])
# Use same token next time
request.META['CSRF_COOKIE'] = csrf_token
except KeyError:
csrf_token = None
# Generate token and store it in the request, so it's
# available to the view.
request.META["CSRF_COOKIE"] = _get_new_csrf_key()
代码
class TestLogin(StaticLiveServerTestCase):
@classmethod
def setUpClass(cls):
cls.selenium = getattr(webdriver, settings.SELENIUM_WEBDRIVER)()
cls.selenium.maximize_window()
cls.selenium.implicitly_wait(5)
super(TestLogin, cls).setUpClass()
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super(TestLogin, cls).tearDownClass()
def test_login(self):
self.selenium.get('{}{}'.format(self.live_server_url, '/accounts/login/?next=/'))
assert "Django" in self.selenium.title
un_el = self.selenium.find_element_by_id('id_username').send_keys('the_un')
pw_el = self.selenium.find_element_by_id('id_password')
pw_el.send_keys('the_pw')
pw_el.send_keys(Keys.RETURN)
try:
WebDriverWait(self.selenium, 5).until(EC.title_contains("New Title"))
except TimeoutException as e:
msg = "Could not find 'New Title' in title. Current title: {}".format(self.selenium.title)
raise TimeoutException(msg)
finally:
self.selenium.quit()
问题
接下来我可以尝试什么来调试它?
老问题,但在坚持了几个小时后,答案很简单。
来自docs:
If a browser connects initially via HTTP, which is the default for most browsers, it is possible for existing cookies to be leaked. For this reason, you should set your SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE settings to True. This instructs the browser to only send these cookies over HTTPS connections. Note that this will mean that sessions will not work over HTTP, and the CSRF protection will prevent any POST data being accepted over HTTP (which will be fine if you are redirecting all HTTP traffic to HTTPS).
像我一样,您的大部分工作可能都使用 django_extensions + Werkzeug,默认情况下 运行 所有本地工作都通过 SSL。
如果您使用的是 unittest
或 Djangos 版本,我建议您在测试运行时修改这些设置,如下所示:
...
from django.conf import settings
class ProfilePagetest(LiveServerTestCase):
def setUp(self):
settings.CSRF_COOKIE_SECURE = False
settings.SESSION_COOKIE_SECURE = False
self.url = reverse('clientpage:profile')
self.username = 'name@names.com'
self.password = 'strange decisions...'
get_user_model().objects.create_user(self.username, self.username, self.password)
self.browser = webdriver.Firefox()
这应该可以解决 CSRF 验证问题。