every相关知识
-
JS中some()和every()和join()和concat()和pop()等一、Array1、some()和every()some()是对数组中每一项运行指定函数,如果该函数对任一项返回true,则返回true。every()是对数组中的每一项运行给定函数,如果该函数对每一项返回true,则返回true。var array = [1,3,5,7,9,11,13,15,17] undefined array.some(function(item,index){ return item>9})//true返回 truevar array = [1,3,5,7,9,11,13,15,17] undefined array.every(function(item,index){ return item>9})//false返回 false 2、join()join() 方法用于把数组中的所有元素放入一个字符串。var arr =[4,5,34,3453,3453,33,"哈哈哈哈"];var arr2 = arr.join(""); console.log(arr2
-
js数组迭代方法 (every、some、filter、map、forEach) 介绍及其应用示例ES5为数组新增了5个迭代方法,语法简单易懂,而且非常实用,大大方便了处理数组的任务。 这5个迭代方法的函数接受3个参数:数组中当前项的值、该项在数组中的索引值和数组对象本身。 1. every 作用:对数组中的每一项运行给定函数,如果每一项的运行结果都返回 true,则返回 true。 示例: // 示例数组 var arr = [3, 'abc', 6, 0, '5', 8, true, null, function(){console.warn('hello')}, {a:1, b:2}]; var result = null; // 判断数组中每一项是否都为
-
MYSQL错误代码:1248 Every derived table must have its own alias 解决MYSQL中执行如下嵌套子查询时报错select id from (select * from pythontab where type=1)报错如下: 错误代码: 1248 Every derived table must have its own alias这句话的意思是说每个派生出来的表都必须有一个自己的别名当我执行到这里的时候就抛出了这个异常,原来我进行嵌套查询的时候子查询出来的的结果是作为一个派生表来进行上一级的查询的,所以子查询的结果必须要有一个别名把MySQL语句改成:select id from (select * from pythontab where type=1) as t;问题就解决了,虽然只加了一个没有任何作用的别名t
-
JS中的关于json的实例所有的东西都是真的! 完善编辑器中的every函数,如果集合(collection)中的所有对象都存在对应的属性(pre),并且属性(pre)对应的值为真。函数返回ture。反之,返回false。 代码: function every(collection, pre) { // Is everyone being true? var bCheck=true; for(var i in collection){ if(!collection[i][pre]){ bCheck=false; } } return bCheck; } every([{"user": "Tinky-Winky", "sex": "male"}, {"user": "Dipsy", "sex": "male"}, {"user": "Laa-
every相关课程
every相关教程
- 3. 类装饰器 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。通过类装饰器扩展类的属性和方法:function extension<T extends { new(...args:any[]): {} }>(constructor: T) { // 重载构造函数 return class extends constructor { // 扩展属性 public coreHour = '10:00-15:00' // 函数重载 meeting() { console.log('重载:Daily meeting!') } }}@extensionclass Employee { public name!: string public department!: string constructor(name: string, department: string) { this.name = name this.department = department } meeting() { console.log('Every Monday!') }}let e = new Employee('Tom', 'IT')console.log(e) // Employee { name: 'Tom', department: 'IT', coreHour: '10:00-15:00' }e.meeting() // 重载:Daily meeting!函数表达式的写法:const extension = (constructor: Function) => { constructor.prototype.coreHour = '10:00-15:00' constructor.prototype.meeting = () => { console.log('重载:Daily meeting!'); }}@extensionclass Employee { public name!: string public department!: string constructor(name: string, department: string) { this.name = name this.department = department } meeting() { console.log('Every Monday!') }}let e: any = new Employee('Tom', 'IT')console.log(e.coreHour) // 10:00-15:00e.meeting() // 重载:Daily meeting!代码解释:以上两种写法,其实本质是相同的,类装饰器函数表达式将构造函数作为唯一的参数,主要用于扩展类的属性和方法。
- 3.2 多行注释 多行注释是在 /* */ 的两个星号中间来编写注释内容,以 /* 开头,换行的话每一行都以 * 号开头,最终以 */ 结尾,多行注释是会被编译到 CSS 中的,并且在多行注释中我们可以使用插值,如果你忘记了什么是插值,赶快复习一下前面的 Sass 插值章节。下面我们举例看下多行注释:/* This is a comment * contain interpolation: * 1 + 1 = #{1 + 1} * 8 * 8 = #{8 * 8} */.box2 { width: 200px; height: 100px; p { color: red } .main { width: 100%; heigth: auto }}上面的代码转换为 CSS 如下:/* This is a comment * contain interpolation: * 1 + 1 = 2 * 8 * 8 = 64 */.box2 { width: 200px; height: 100px;}.box2 p { color: red;}.box2 .main { width: 100%; heigth: auto;}通过上面的代码可以看出,多行注释最终将会被编译为 CSS 的多行注释并带到 CSS 中去,而且在多行注释中可以使用插值来进行一些运算。在默认的情况下,如果处于压缩模式,则转换后的 CSS 中也是不包含多行注释的,如果你忘了什么是压缩模式,赶快复习下 Sass 输出格式章节。所以我们可以通过以 /*! 开头,来保证多行注释在任何模式下都会被转换为 CSS 输出。我们举例看下:/*! This comment will be * included even in every mode. */.box2 { width: 200px; height: 100px; p { color: red } .main { width: 100%; heigth: auto }}通过上面这种注释方式就可以保证你的注释内容在任何模式下都会被输出。
- 3.2 Django 中 Session 操作相关源码 Django 中和 Session 相关的代码较多,我们不去深入追究源码细节,主要是程序的执行过程。比如在哪里设置的 request.session 值以及 session 相关信息如何保存到数据库中的等等。我们先整体看下 Session 相关的代码位置:这里有几个比较重要的地方,一个是 backends 目录,下面是不同保存 Session 数据方式的代码,如使用数据库存储、缓存存储或者文件系统存储等,每种存储方式对应着一个代码文件,里面定义的类和方法都是一致的,这样就可以无缝切换存储引擎。第二个是 middleware.py 文件,我们先要了解下 Django 中 MIDDLEWARE 的工作流程,可以如下图所示:由于在 settings.py 中间设置了 Session 的中间件,所以 request 和 response 也会经历 Session 中间件的这个流程,看 session 目录下的 middleware.py 文件的代码:class SessionMiddleware(MiddlewareMixin): def __init__(self, get_response=None): self.get_response = get_response engine = import_module(settings.SESSION_ENGINE) self.SessionStore = engine.SessionStore def process_request(self, request): session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME) request.session = self.SessionStore(session_key) def process_response(self, request, response): """ If request.session was modified, or if the configuration is to save the session every time, save the changes and set a session cookie or delete the session cookie if the session has been emptied. """ try: accessed = request.session.accessed modified = request.session.modified empty = request.session.is_empty() except AttributeError: pass else: # First check if we need to delete this cookie. # The session should be deleted only if the session is entirely empty if settings.SESSION_COOKIE_NAME in request.COOKIES and empty: response.delete_cookie( settings.SESSION_COOKIE_NAME, path=settings.SESSION_COOKIE_PATH, domain=settings.SESSION_COOKIE_DOMAIN, ) else: if accessed: patch_vary_headers(response, ('Cookie',)) if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty: if request.session.get_expire_at_browser_close(): max_age = None expires = None else: max_age = request.session.get_expiry_age() expires_time = time.time() + max_age expires = http_date(expires_time) # Save the session data and refresh the client cookie. # Skip session save for 500 responses, refs #3881. if response.status_code != 500: try: request.session.save() except UpdateError: raise SuspiciousOperation( "The request's session was deleted before the " "request completed. The user may have logged " "out in a concurrent request, for example." ) response.set_cookie( # 默认值便是session_id settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, path=settings.SESSION_COOKIE_PATH, secure=settings.SESSION_COOKIE_SECURE or None, httponly=settings.SESSION_COOKIE_HTTPONLY or None, samesite=settings.SESSION_COOKIE_SAMESITE, ) return response首先看 __init__() 方法中的设置了对应 Session 的存储引擎,接下来的 process_request() 方法中我们可以看到 request.session = self.SessionStore(session_key) 这行语句正是给 request 的 session 属性赋值,而这个值正是存储引擎模块下的 SessionStore 类的实例。而对于 process_response() 方法,从代码中可以看到,它完成了 Session 数据的保存以及将 session_id 值写到 cookie 中去并返回给客户端,调用的方法正是我们前面介绍到的 set_cookie() 方法。接下来,我们看看其他几个 python 文件中的代码。例如下面的 models.py 定义了 Session 的 model 模型,包括字段以及管理器:# 源码位置:django/contrib/sessions/models.pyfrom django.contrib.sessions.base_session import ( AbstractBaseSession, BaseSessionManager,)class SessionManager(BaseSessionManager): use_in_migrations = Trueclass Session(AbstractBaseSession): objects = SessionManager() @classmethod def get_session_store_class(cls): from django.contrib.sessions.backends.db import SessionStore return SessionStore class Meta(AbstractBaseSession.Meta): db_table = 'django_session'从这段代码可以看到 Session 如果使用数据库保存数据的话,其建立的表名为:django_session,其字段类型并不在这里定义,而是继承的父类 BaseSessionManager,这个类定义就在 base_session.py 文件中:class BaseSessionManager(models.Manager): def encode(self, session_dict): """ Return the given session dictionary serialized and encoded as a string. """ session_store_class = self.model.get_session_store_class() return session_store_class().encode(session_dict) def save(self, session_key, session_dict, expire_date): s = self.model(session_key, self.encode(session_dict), expire_date) if session_dict: s.save() else: s.delete() # Clear sessions with no data. return sclass AbstractBaseSession(models.Model): session_key = models.CharField(_('session key'), max_length=40, primary_key=True) session_data = models.TextField(_('session data')) expire_date = models.DateTimeField(_('expire date'), db_index=True) objects = BaseSessionManager() class Meta: abstract = True verbose_name = _('session') verbose_name_plural = _('sessions') def __str__(self): return self.session_key @classmethod def get_session_store_class(cls): raise NotImplementedError def get_decoded(self): session_store_class = self.get_session_store_class() return session_store_class().decode(self.session_data)从这里就可以看到 django_session 表有3个字段,分别是session_key、session_data 和 expire_date。继承这个基类必须要实现 get_session_store_class() 这个方法。另外 app.py 用于指明 session 应用名称,exceptions.py 定义了两个简单的异常,serializers.py 的内容也比较简单,仅仅使用 pickle 模块封装了一个用于序列化的类:import picklefrom django.core.signing import JSONSerializer as BaseJSONSerializerclass PickleSerializer: """ Simple wrapper around pickle to be used in signing.dumps and signing.loads. """ protocol = pickle.HIGHEST_PROTOCOL def dumps(self, obj): return pickle.dumps(obj, self.protocol) def loads(self, data): return pickle.loads(data)JSONSerializer = BaseJSONSerializer我们也可以简单使用下这个类,其实就是熟悉 pickle 模块的 dumps() 和 loads() 方法。(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from django.contrib.sessions.serializers import PickleSerializer>>> from hello_app.views import Member>>> Member.objects.all().get(name='spyinx-1')<Member: <spyinx-1, 18017715080>>>>> m = Member.objects.all().get(name='spyinx-1')>>> PickleSerializer().dumps(m)b'\x80\x05\x95o\x01\x00\x00\x00\x00\x00\x00\x8c\x15django.db.models.base\x94\x8c\x0emodel_unpickle\x94\x93\x94\x8c\thello_app\x94\x8c\x06Member\x94\x86\x94\x85\x94R\x94}\x94(\x8c\x06_state\x94h\x00\x8c\nModelState\x94\x93\x94)\x81\x94}\x94(\x8c\x06adding\x94\x89\x8c\x02db\x94\x8c\x07default\x94ub\x8c\x02id\x94K\x05\x8c\x04name\x94\x8c\x08spyinx-1\x94\x8c\x03age\x94\x8c\x0225\x94\x8c\x03sex\x94K\x00\x8c\noccupation\x94\x8c\x07teacher\x94\x8c\tphone_num\x94\x8c\x0b18017715080\x94\x8c\x05email\x94\x8c\n221@qq.com\x94\x8c\x04city\x94\x8c\x08shenzhen\x94\x8c\rregister_date\x94\x8c\x08datetime\x94\x8c\x08datetime\x94\x93\x94C\n\x07\xe4\x04\t\x10\x18\n\x00\x00\x00\x94\x85\x94R\x94\x8c\x0cvip_level_id\x94N\x8c\x0f_django_version\x94\x8c\x062.2.11\x94ub.'>>> m1 = PickleSerializer().loads(s)>>> type(m1)<class 'hello_app.models.Member'>>>> m1.name'spyinx-1'最后,关于 Session 的引擎不用过多细究,里面默认用到的是 backends/db.py。如果使用的是缓存引擎,代码内容也是大同小异的。主要认真研究两个类:backends/base.py 中的 SessionBase 类。这个是所有 SessionStore 的基类,它具有的方法正是我们操作 session 的方法;backends/db.py 中的 SessionStore 类。前面的 request.session 便是该类的一个实例,它的代码内容也是不复杂的,主要针对该种存储方式需要完成特定的保存(save())、删除(delete()) 以及导入(load())等方法。到此为止,Django 中关于 Session 的代码就这么多了。剩下的代码细节还需要各位去慢慢专研,也希望大家能认真钻研 Django 的源码,多多思考,然后多多实践。
- 2.2 使用 Form 校验数据 使用表单校验传过来的数据是否合法,这大概是使用 Form 类的优势之一。比如前面的实验2中,我们完成了一个对输入密码字段的校验,要求输入的密码必须含有大小写以及数字,三者缺一不可。在 Form 中,对于使用 Form 校验数据,我们会用到它的如下几个方法:Form.clean():默认是返回一个 cleaned_data。对于 cleaned_data 的含义,后面会在介绍 Form 属性字段时介绍到,我们看源码可知,clean() 方法只是返回 Form 中得到的统一清洗后的正确数据。# 源码位置:django/forms/forms.py# ...@html_safeclass BaseForm: # ... def clean(self): """ Hook for doing any extra form-wide cleaning after Field.clean() has been called on every field. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field named '__all__'. """ return self.cleaned_data # ...# ...我们继续在前面的命令行上完成实验。上面输入的数据中由于 password 字段不满足条件,所以得到的cleaned_data 只有 name 字段。 想要调用 clean() 方法,必须要生成 cleaned_data 的值,而要生成cleaned_data 的值,就必须先调用 full_clean() 方法,操作如下:>>> login_bound.full_clean()>>> login_bound.clean(){'name': 'test11'}>>> login_bound.cleaned_data{'name': 'test11'}来从源代码中看看为什么要先调用 full_clean() 方法才有 cleaned_data 数据:# 源码位置:django/forms/forms.py@html_safeclass BaseForm: # ... def full_clean(self): """ Clean all of self.data and populate self._errors and self.cleaned_data. """ self._errors = ErrorDict() if not self.is_bound: # Stop further processing. return self.cleaned_data = {} # If the form is permitted to be empty, and none of the form data has # changed from the initial data, short circuit any validation. if self.empty_permitted and not self.has_changed(): return self._clean_fields() self._clean_form() self._post_clean() def _clean_fields(self): for name, field in self.fields.items(): # value_from_datadict() gets the data from the data dictionaries. # Each widget type knows how to retrieve its own data, because some # widgets split data over several HTML fields. if field.disabled: value = self.get_initial_for_field(field, name) else: value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) try: if isinstance(field, FileField): initial = self.get_initial_for_field(field, name) value = field.clean(value, initial) else: value = field.clean(value) self.cleaned_data[name] = value if hasattr(self, 'clean_%s' % name): value = getattr(self, 'clean_%s' % name)() self.cleaned_data[name] = value except ValidationError as e: self.add_error(name, e) # ...可以看到,全局搜索 cleaned_data 字段,可以发现 cleaned_data 的初始赋值在 full_clean() 方法中,然后会在 _clean_fields() 方法中对 Form 中所有通过校验的数据放入到 cleaned_data,形成相应的值。Form.is_valid():这个方法就是判断 Form 表单中的所有字段数据是否都通过校验。如果有一个没有通过就是 False,全部正确才是 True:>>> from hello_app.views import LoginForm>>> from django import forms>>> login_bound = LoginForm({'name': 'test11', 'password': '111111', 'save_login': False})>>> login_bound.is_valid()>>> login_bound.clean(){'name': 'test11'}>>> login_bound.cleaned_data{'name': 'test11'}注意:我们发现在使用了 is_valid() 方法后,对应 Form 实例的 cleaned_data 属性值也生成了,而且有了数据。参看源码可知 is_valid() 方法同样调用了 full_clean() 方法:# 源码位置:django/forms/forms.py# ...@html_safeclass BaseForm: # ... @property def errors(self): """Return an ErrorDict for the data provided for the form.""" if self._errors is None: self.full_clean() return self._errors def is_valid(self): """Return True if the form has no errors, or False otherwise.""" return self.is_bound and not self.errors # ... # ...在看到这段源代码的时候,我们可以这样考虑下,如果想让上面的 is_valid() 方法调用后并不继续调用 full_clean() 方法,这样 cleaned_data 就不会生成,再调用 clean() 方法就会报错。那么我们如何做呢?很简单,只需要控制 if self._errors is None 这个语句不成立即可:(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from hello_app.views import LoginForm>>> from django import forms>>> login_bound = LoginForm({'name': 'test11', 'password': 'SPYinx1111', 'save_login': False})>>> print(login_bound._errors)None>>> login_bound._errors=[]>>> login_bound.is_valid()True>>> login_bound.cleaned_dataTraceback (most recent call last): File "<console>", line 1, in <module>AttributeError: 'LoginForm' object has no attribute 'cleaned_data'>>> login_bound.clean()Traceback (most recent call last): File "<console>", line 1, in <module> File "/root/.pyenv/versions/django-manual/lib/python3.8/site-packages/django/forms/forms.py", line 430, in clean return self.cleaned_dataAttributeError: 'LoginForm' object has no attribute 'cleaned_data'看到了源码之后,我们要学会操作,学会分析一些现象,然后动手实践,这样才会对 Django 的源码越来越熟悉。Form.errors:它是一个类属性,保存的是校验错误的信息。该属性还有一些有用的方法,我们在下面实践中熟悉:(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from hello_app.views import LoginForm>>> from django import forms>>> login_bound = LoginForm({'name': 'te', 'password': 'spyinx1111', 'save_login': False})>>> login_bound.errors{'name': ['账号名最短4位'], 'password': ['密码需要包含大写、小写和数字']}>>> login_bound.errors.as_data(){'name': [ValidationError(['账号名最短4位'])], 'password': [ValidationError(['密码需要包含大写、小写和数字'])]}# 中文编码>>> login_bound.errors.as_json()'{"name": [{"message": "\\u8d26\\u53f7\\u540d\\u6700\\u77ed4\\u4f4d", "code": "min_length"}], "password": [{"message": "\\u5bc6\\u7801\\u9700\\u8981\\u5305\\u542b\\u5927\\u5199\\u3001\\u5c0f\\u5199\\u548c\\u6570\\u5b57", "code": ""}]}'看到最后的 as_json() 方法,发现转成 json 的时候中文乱码,这个输出的是 unicode 编码结果。第一眼看过去特别像 json.dumps() 的包含中文的情况。为了能解决此问题,我们先看源码,找到原因:# 源码位置:django/forms/forms.py@html_safeclass BaseForm: # ... @property def errors(self): """Return an ErrorDict for the data provided for the form.""" if self._errors is None: self.full_clean() return self._errors # ... def full_clean(self): """ Clean all of self.data and populate self._errors and self.cleaned_data. """ self._errors = ErrorDict() # ... # ... # 源码位置: django/forms/utils.py@html_safeclass ErrorDict(dict): """ A collection of errors that knows how to display itself in various formats. The dictionary keys are the field names, and the values are the errors. """ def as_data(self): return {f: e.as_data() for f, e in self.items()} def get_json_data(self, escape_html=False): return {f: e.get_json_data(escape_html) for f, e in self.items()} def as_json(self, escape_html=False): return json.dumps(self.get_json_data(escape_html)) # ...可以看到,errors 属性的 as_json() 方法最后调用的就是 json.dumps() 方法。一般要解决它的中文输出显示问题,只需要加上一个 ensure_ascii=False 即可。这里我们也只需要改下源码:(django-manual) [root@server first_django_app]# vim ~/.pyenv/versions/django-manual/lib/python3.8/site-packages/django/forms/utils.py# ... def as_json(self, escape_html=False): return json.dumps(self.get_json_data(escape_html), ensure_ascii=False)# ...然后我们再次进行 shell 命令行下,执行刚才的命令,发现中文输出已经正常了。django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from hello_app.views import LoginForm>>> from django import forms>>> login_bound = LoginForm({'name': 'te', 'password': 'spyinx1111', 'save_login': False})>>> login_bound.errors{'name': ['账号名最短4位'], 'password': ['密码需要包含大写、小写和数字']}>>> login_bound.errors.as_json()'{"name": [{"message": "账号名最短4位", "code": "min_length"}], "password": [{"message": "密码需要包含大写、小写和数字", "code": ""}]}'>>>
- 1. 深入 Django 中 CSRF 校验过程 上一节中提到了,针对 CSRF 攻击有效的解决方案是在网页上添加一个随机的校验 token 值,我们前面的登录的模板页面中添加的 {% csrf_token %},这里正好对应着一个随机值。我们拿之前的登录表单来进行观察,会发现这样几个现象:网页上隐藏的 csrf_token 值会在每次刷新时变化;对应在请求和响应头部的 cookie 中的 csrftoken值却一直不变;这样子我们对应会产生几个思考问题:为什么网页上的 token 值会变,而 cookie 中的 token 则一直不变?整个 token 的校验过程是怎样的,有密码?如果有密码,密码存在哪里?今天我们会带着这两个问题,查看下 Django 内部源码,找到这些问题的代码位置。我可能不会很完整的描述整个代码运行的逻辑,因为篇幅不够,而且细节太多,容易迷失在代码的海洋里。首先毋庸置疑的第一步是找我们在 settings.py 中设置的 CSRF 中间件:MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',]我们在上一讲中提到过中间件类的两个函数:process_request() 和 process_response()。而在 CSRF 中间件文件中还有一个方法:process_view()。中间类比较完整的处理流程示意图如下所示,可以看到中间件的 process_view() 方法如果返回 None,则会执行下一个 中间件的 process_view() 方法。一旦它返回 HttpResponse 实例,则直接跳过视图函数到达最后一个中间件的 process_response() 方法中。我们来关注下 django.middleware 目录下的 csrf.py 文件,所有的答案都在这里可以找到。首先看最核心的中间件类:# 源码位置:django/middleware/csrf.py# ...class CsrfViewMiddleware(MiddlewareMixin): def _accept(self, request): # Avoid checking the request twice by adding a custom attribute to # request. This will be relevant when both decorator and middleware # are used. request.csrf_processing_done = True return None def _reject(self, request, reason): response = _get_failure_view()(request, reason=reason) log_response( 'Forbidden (%s): %s', reason, request.path, response=response, request=request, logger=logger, ) return response def _get_token(self, request): # ... def _set_token(self, request, response): # ... def process_request(self, request): csrf_token = self._get_token(request) if csrf_token is not None: # Use same token next time. request.META['CSRF_COOKIE'] = csrf_token def process_view(self, request, callback, callback_args, callback_kwargs): if getattr(request, 'csrf_processing_done', False): return None # Wait until request.META["CSRF_COOKIE"] has been manipulated before # bailing out, so that get_token still works if getattr(callback, 'csrf_exempt', False): return None # Assume that anything not defined as 'safe' by RFC7231 needs protection if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): if getattr(request, '_dont_enforce_csrf_checks', False): # Mechanism to turn off CSRF checks for test suite. # It comes after the creation of CSRF cookies, so that # everything else continues to work exactly the same # (e.g. cookies are sent, etc.), but before any # branches that call reject(). return self._accept(request) # 判断是不是 https 协议,不然不用执行这里 if request.is_secure(): # ... csrf_token = request.META.get('CSRF_COOKIE') if csrf_token is None: # No CSRF cookie. For POST requests, we insist on a CSRF cookie, # and in this way we can avoid all CSRF attacks, including login # CSRF. return self._reject(request, REASON_NO_CSRF_COOKIE) # Check non-cookie token for match. request_csrf_token = "" if request.method == "POST": try: request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') except IOError: # Handle a broken connection before we've completed reading # the POST data. process_view shouldn't raise any # exceptions, so we'll ignore and serve the user a 403 # (assuming they're still listening, which they probably # aren't because of the error). pass if request_csrf_token == "": # Fall back to X-CSRFToken, to make things easier for AJAX, # and possible for PUT/DELETE. request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '') request_csrf_token = _sanitize_token(request_csrf_token) if not _compare_salted_tokens(request_csrf_token, csrf_token): return self._reject(request, REASON_BAD_TOKEN) return self._accept(request) def process_response(self, request, response): if not getattr(request, 'csrf_cookie_needs_reset', False): if getattr(response, 'csrf_cookie_set', False): return response if not request.META.get("CSRF_COOKIE_USED", False): return response # Set the CSRF cookie even if it's already set, so we renew # the expiry timer. self._set_token(request, response) response.csrf_cookie_set = True return response这里比较复杂的部分就是 process_view() 方法。process_request() 方法只是从请求头中取出 csrftoken 值或者生成一个 csrftoken 值放到 request.META 属性中去;process_response() 会设置对应的 csrftoken 值到 cookie 或者 session 中去。这里获取 csrftoken 和 设置 csrftoken 调用的正是 _get_token() 和 set_token()方法:class CsrfViewMiddleware(MiddlewareMixin): # ... def _get_token(self, request): if settings.CSRF_USE_SESSIONS: try: return request.session.get(CSRF_SESSION_KEY) except AttributeError: raise ImproperlyConfigured( 'CSRF_USE_SESSIONS is enabled, but request.session is not ' 'set. SessionMiddleware must appear before CsrfViewMiddleware ' 'in MIDDLEWARE%s.' % ('_CLASSES' if settings.MIDDLEWARE is None else '') ) else: try: cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME] except KeyError: return None csrf_token = _sanitize_token(cookie_token) if csrf_token != cookie_token: # Cookie token needed to be replaced; # the cookie needs to be reset. request.csrf_cookie_needs_reset = True return csrf_token def _set_token(self, request, response): if settings.CSRF_USE_SESSIONS: if request.session.get(CSRF_SESSION_KEY) != request.META['CSRF_COOKIE']: request.session[CSRF_SESSION_KEY] = request.META['CSRF_COOKIE'] else: response.set_cookie( settings.CSRF_COOKIE_NAME, request.META['CSRF_COOKIE'], max_age=settings.CSRF_COOKIE_AGE, domain=settings.CSRF_COOKIE_DOMAIN, path=settings.CSRF_COOKIE_PATH, secure=settings.CSRF_COOKIE_SECURE, httponly=settings.CSRF_COOKIE_HTTPONLY, samesite=settings.CSRF_COOKIE_SAMESITE, ) # Set the Vary header since content varies with the CSRF cookie. patch_vary _headers(response, ('Cookie',)) # ...如果我们没在 settings.py 中设置 CSRF_USE_SESSIONS 值时,在 django/conf/global_settings.py 默认设置为 False,那么我们就是调用前面熟悉的 response.set_cookie() 方法取设置 cookie 中的 key-value 值,也是我们在上面第二张图片所看到的 Set-Cookie 里面的值。我们来看最核心的处理方法:process_view()。它的执行流程如下所列,略有删减,请仔细研读和对照代码:判断视图方法是否有 csrf_exempt 属性。相当于该视图方法添加了 @csrf_exempt 装饰器,这样不用检验 csrf_token 值,直接返回 None,进入下面的中间件执行,直到视图函数去处理 HTTP 请求;对于 GET、HEAD、 OPTIONS、 TRACE 这四种请求不用检查 csrf_token,会直接跳到最后执行 self._accept(request) 方法。但是我们常用的如 POST、PUT 以及 DELETE 等请求会进行特别的处理;来看针对 POST、PUT 以及 DELETE 的特殊处理,要注意两处代码:request_csrf_token 值的获取:对于 POST 请求,我们要从请求参数中获取,这个值正是表单中隐藏的随机 csrf_token,也是我们在第一张图中看到的值,每次请求都会刷新该值;而且对于其它的请求,该值则是从 request.META 中获取;校验 csrf_token 值是否正确。如果是不正确的 csrf_token 值,则会直接返回 403 错误;if not _compare_salted_tokens(request_csrf_token, csrf_token): return self._reject(request, REASON_BAD_TOKEN)可以看到,这里校验的是两个值:一个是我们从 cookie 中获取的,另一个是前端表单中隐藏的那个随机数。现在我们大致心里有个数了,Django 的校验方法竟然是用 cookie 中的值和页面上的随机值进行校验,这两个值都是64位的,你必须同时拿到这两个正确 token 值才能通过 Django 的 csrf 中间件校验。比较原理,2个 token,一个放到 cookie 中,另一个放到表单中,会一直变得那种。接下来就是对这两个 token 进行对比。我们继续追踪 _compare_salted_tokens() 方法,可以在 csrf.py 中找到如下两个方法,它们分别对应着 csrf_token 值的生成和解码:# 源码位置:django/middleware/csrf.py# ...def _salt_cipher_secret(secret): """ Given a secret (assumed to be a string of CSRF_ALLOWED_CHARS), generate a token by adding a salt and using it to encrypt the secret. """ salt = _get_new_csrf_string() chars = CSRF_ALLOWED_CHARS pairs = zip((chars.index(x) for x in secret), (chars.index(x) for x in salt)) cipher = ''.join(chars[(x + y) % len(chars)] for x, y in pairs) return salt + cipherdef _unsalt_cipher_token(token): """ Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt the second half to produce the original secret. """ salt = token[:CSRF_SECRET_LENGTH] token = token[CSRF_SECRET_LENGTH:] chars = CSRF_ALLOWED_CHARS pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt)) secret = ''.join(chars[x - y] for x, y in pairs) # Note negative values are ok return secret# ...来看这两个函数,首先是 _salt_cipher_secret() 方法,需要传入一个长度为 32 的 secret,就可以得到一个64位的随机字符。这个 secret 值在使用时也是随机生成的32个字符:# 源码位置:django/middleware/csrf.pydef _get_new_csrf_string(): return get_random_string(CSRF_SECRET_LENGTH, allowed_chars=CSRF_ALLOWED_CHARS)# 源码位置:django/utils/crypto.pydef get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): """ Return a securely generated random string. The default length of 12 with the a-z, A-Z, 0-9 character set returns a 71-bit value. log_2((26+26+10)^12) =~ 71 bits """ if not using_sysrandom: # This is ugly, and a hack, but it makes things better than # the alternative of predictability. This re-seeds the PRNG # using a value that is hard for an attacker to predict, every # time a random string is required. This may change the # properties of the chosen random sequence slightly, but this # is better than absolute predictability. random.seed( hashlib.sha256( ('%s%s%s' % (random.getstate(), time.time(), settings.SECRET_KEY)).encode() ).digest() ) return ''.join(random.choice(allowed_chars) for i in range(length))在 _salt_cipher_secret() 方法中我们可以看到,传入32位的密钥 secret,最后的 csrf_token 的生成是 salt + cipher,前32位是 salt,后32位是加密字符串。解密的过程差不多就是 _salt_cipher_secret() 的逆过程了,最后得到 secret。我们可以在 Django 的 shell 模式下使用下这两个函数:(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from django.middleware.csrf import _get_new_csrf_token, _unsalt_cipher_token>>> x1 = _get_new_csrf_token()>>> x2 = _get_new_csrf_token()>>> x3 = _get_new_csrf_token()>>> print('x1={}\nx2={}\nx3={}'.format(x1, x2, x3))x1=dvK3CRLiyHJ6Xgt0B6eZ7kUjxXgZ5CKkhl8HbHq8CKR0ZXMOxYnigzDTIZIdk3xZx2=TMazqRDst3BSiyxIAI1XDiFKdbmxu8nKRVvMogERiZi6IG6KNhDSxcgEOPTqU0qFx3=gy998wPOCZJiXHo7HYQtY3dfwaevPHKAs2YXPAeJmWUaA5vV2xdXqvlidLR4XM1T>>> _unsalt_cipher_token(x1)'e0yOJ0P0edi4cRtY62jtjpTKlcCopBXP'>>> _unsalt_cipher_token(x2)'8jvn8zbzZ6RoAiJcnJM544L4LOH3A2d5'>>> _unsalt_cipher_token(x3)'mEZYRez5U7l2NyhYvJxECCidRLNJifrt'>>> 了解了上述这些方法后,现在来思考前面提出的问题:为什么每次刷新表单中的 csrf_token 值会一直变化,而 cookie 中的 csrf_token 值却一直不变呢?首先我们看在页面上生成随机 token 值的代码,也就是将标签 {{ csrf_token }} 转成 64位随机码的地方:# 源码位置: django/template/defaulttags.py@register.tagdef csrf_token(parser, token): return CsrfTokenNode()class CsrfTokenNode(Node): def render(self, context): csrf_token = context.get('csrf_token') if csrf_token: if csrf_token == 'NOTPROVIDED': return format_html("") else: return format_html('<input type="hidden" name="csrfmiddlewaretoken" value="{}">', csrf_token) else: # It's very probable that the token is missing because of # misconfiguration, so we raise a warning if settings.DEBUG: warnings.warn( "A {% csrf_token %} was used in a template, but the context " "did not provide the value. This is usually caused by not " "using RequestContext." ) return ''可以看到 csrf_token 值是从 context 中取出来的,而在 context 中的 csrf_token 值又是由如下代码生成的:# 源码位置:django/template/context_processors.pyfrom django.middleware.csrf import get_token# ...def csrf(request): """ Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if it has not been provided by either a view decorator or the middleware """ def _get_val(): token = get_token(request) if token is None: # In order to be able to provide debugging info in the # case of misconfiguration, we use a sentinel value # instead of returning an empty dict. return 'NOTPROVIDED' else: return token return {'csrf_token': SimpleLazyObject(_get_val)}可以看到,最后 csrf_token 值还是由 csrf.py 文件中的 get_token() 方法生成的。来继续看这个 get_token() 方法的代码:# 源码位置:django/middleware/csrf.pydef get_token(request): """ Return the CSRF token required for a POST form. The token is an alphanumeric value. A new token is created if one is not already set. A side effect of calling this function is to make the csrf_protect decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' header to the outgoing response. For this reason, you may need to use this function lazily, as is done by the csrf context processor. """ if "CSRF_COOKIE" not in request.META: csrf_secret = _get_new_csrf_string() request.META["CSRF_COOKIE"] = _salt_cipher_secret(csrf_secret) else: csrf_secret = _unsalt_cipher_token(request.META["CSRF_COOKIE"]) request.META["CSRF_COOKIE_USED"] = True return _salt_cipher_secret(csrf_secret)注意!注意!最关键的地方来了,这个加密的 secret 的值是从哪里来的?正是从请求头中的 cookie 信息中来的,如果没有将生成一个新的密钥,接着把该密钥生成的 token 放到 cookie 中。最后使用 _salt_cipher_secret() 方法生成的 csrf_token 和 cookie 中的 csrf_token 具有相同的密钥。同时拿到了这两个值,就可以进行校验和判断,下面我们在 ``_salt_cipher_secret()方法中加上一个print()` 语句,然后执行下看看是否如我们所说。22可以看到每次生成 token 时加密的秘钥都是一样的。我们从上面生成的 csrf_token 中选一个进行解密,得到的结果和 cookie 中的正是一样的密钥:>>> from django.middleware.csrf import _unsalt_cipher_token# 两个 token 解密是相同的,这才是正确的>>> _unsalt_cipher_token('2Tt8StiU4rZcvCrTb2KqJwTTOTCP0WvJhp7GyTj58RGv97IvJInxyrAN4DKCdt1M')'pGOIQAbleARtOFrMIQNhZ5R4qUiXnHGd'>>> _unsalt_cipher_token('VI68m6xT1JczSsnuJvxqtcr0L0EvCN1DaeKG2wy459TSwXE6hbaxi78U1KMiPkxG')'pGOIQAbleARtOFrMIQNhZ5R4qUiXnHGd'现在大部分代码我们也算清楚了,csrf_token 的校验原理我们也知道了。那么如果想自己生成 csrf_token 并通过 Django 的校验也非常简单,只需要通过那个密钥生成一个 csrf_token 或者直接输入使用密钥都可以通过校验。我们首先使用密钥在 shell 模式下随机生成一个 csrf_token 值:>>> from django.middleware.csrf import _salt_cipher_secret>>> _salt_cipher_secret('pGOIQAbleARtOFrMIQNhZ5R4qUiXnHGd')'ObaC9DEZfn4seXbOhgBCph2Y5PMjm0Eo3HOaP3FajNLLSssqPWeJecJSlzU6zxar接下来在看我的演示,第一此我随机改动 csrf_token 的字符,这样校验肯定通不过;接下来我是用自己生成的 token 值以及直接填写密钥再去提交都能通过校验。为了方便演示结果,我们在 csrf.py 的 process_view() 函数中添加几个 print() 方法,方便我们理解执行的过程。23
- 10 BeatifulSoup 的使用 Python 爬虫常用知识点解析
every相关搜索
-
e preventdefault
e4a
each
each的用法
easter
easter day
easyui
easyui 官网
echarts
eclipse
eclipse 64位下载
eclipse android
eclipse tomcat
eclipse 教程
eclipse 快捷键
eclipseadt
eclipse安装教程
eclipse插件
eclipse插件下载
eclipse教程