用 Django 的 session 验证机制也可以做到,前后端的完全分离。当然本文只是为了证明可行性,在实际生产环境中,对于前后端完全分离, 主流的认证机制是 token 或 JWT token。从回答下面的问题,引出咱们的正题。
Django 整个认证过程中,从后端到前端, session id是怎么被触发写入cookie 的?
后端代码调用 response.set_cookie() 就会在响应头里写 Set-Cookie: sessionid=cji4w5gut31v62tq39b9lmwjtd4vb2si; expires=Thu, 31-Mar-2022 04:06:13 GMT; httponly; Max-Age=1209600; Path=/ (简单点的类似 Set-Cookie: rob_testcookie=robert; Path=/)
浏览器收到响应且解析响应头发现有 Set-Cookie,则就会触发自己写 cookie 也会把 Max-Age、Path 等信息写入cookie,然后在下次请求该domain的相关 URL 的时候把 cookie 带上, 这是浏览器自动的行为。
所以如果要用代码模拟浏览器的行为,就需要手动解析响应头,然后把这些信息自己保存起来比如写文件里,最后下次请求的时候,带上这些信息。(代码中可以调用 get_sessionid 去真正的获取 seesionid 然后再和业务接口通信, 当然也可以修改下 django_session 表里的数据同时把伪造的 sessionid 拿到放到 cookie 中,也就可以和业务接口通信了), 请参考: 下面的代码
import requests
import json
def get_sessionid():
input_dict = {'username': 'admin', 'password': 'start01all'}
url = 'http://192.168.56.101:8082/myself_login/'
# 参考 https://blog.csdn.net/cpxsxn/article/details/98184681 避开 CSRF 的限制
# res = requests.post(url, data=input_dict, allow_redirects=False)
res = requests.post(url, data=input_dict, allow_redirects=False,
headers={'X-CSRFToken': 'mycsrftokenabc', 'Cookie': 'csrftoken=mycsrftokenabc'})
print 'type(res.headers) = {0}, res.headers = {1}'.format(type(res.headers), res.headers)
print 'type(res.text) = {0}, res.text = {1}'.format(type(res.text), res.text)
print 'res.status_code = {0}'.format(res.status_code)
var_cookie = res.headers.get('Set-Cookie')
print 'var_cookie = {0}'.format(var_cookie)
tmp_list = var_cookie.split(';')
print '----------------------------'
for item in tmp_list:
if item.find('sessionid') != -1:
session_related_info = item.split(',')
for data in session_related_info:
print 'session_relataed data = {0}'.format(data)
if data.find('sessionid') != -1:
sessionid = data.split('=')[1]
return sessionid
# 可以调用 get_sessionid 去真正的获取 seesionid 然后再和业务接口通信, 其中会涉及避开 CSRF 的限制
# 当然也可以修改下 django_session 表里的数据同时把伪造的 sessionid 拿到放到 cookie 中,也就可以和业务接口通信了
url = 'http://192.168.56.101:8082/data/student_data/'
# sessionid = get_sessionid()
sessionid = 'tvh1n5j5n22spesiq9mtvph5llkwl2c01'
print 'sessionid = {0}'.format(sessionid)
cookie = 'csrftoken=mycsrftokenabc;sessionid={0}'.format(sessionid)
res = requests.get(url,params={'user':'robert'}, headers={'X-CSRFToken': 'mycsrftokenabc', 'Cookie': cookie})
print res.text
进阶:
后来发现 request 包有 session 函数,恍然大悟原来一切都有现成的包,于是:
>>> import requests
>>>
>>> url = 'http://192.168.56.101:8082/myself_login/'
>>> input_dict = {'password': 'start01all', 'username': 'admin'}
>>> s = requests.session()
>>>
>>> Case 1:
>>> res = s.post(url, data=input_dict,headers={'X-CSRFToken': 'mycsrftokenabc', 'Cookie': 'csrftoken=mycsrftokenabc'}, allow_redirects=False)
>>>
>>> res.cookies
<RequestsCookieJar[Cookie(version=0, name='csrftoken', value='lJif1y7X2w110MlDXsegTbdoC5GNBS0J', port=None, port_specified=False, domain='192.168.56.101', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=1606134216, discard=False, comment=None, comment_url=None, rest={}, rfc2109=False), Cookie(version=0, name='rob_testcookie', value='robert', port=None, port_specified=False, domain='192.168.56.101', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False), Cookie(version=0, name='sessionid', value='3k6e9t52ki988eslyfwc63d0taswd82o', port=None, port_specified=False, domain='192.168.56.101', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=1575894216, discard=False, comment=None, comment_url=None, rest={'httponly': None}, rfc2109=False)]>
>>>
>>>
>>> Case 2: 把 case 1 中的 allow_redirects 参数去掉即设置为 True, 注意看后端日志都会有重定向 url /hello 的请求;同时由于 /hello 的视图函数并不会写 cookie, 所以 print res.cookies 会打印为空
>>> res = s.post(url, data=input_dict,headers={'X-CSRFToken': 'mycsrftokenabc', 'Cookie': 'csrftoken=mycsrftokenabc'})
>>>
>>> res.cookies
<RequestsCookieJar[]>
>>>
经常很多请求只有在登录后才能进行,实现登录效果一般的做法是执行登录请求,然后从返回结果中提取sessionid放入自定义cookie中。
这种方法在requests中也行得通,但requests提供了更为简单的方法,直接使用request.Session类来请求即可,其保持登录的原理是保留之前请求中服务端通过set-cookie等设置的参数。