Flask-WTF中csrf-token的生成和验证源码分析

Flask-WTF源码分析中关于CSRF_TOKEN的生成和验证有问题,这里重新分析一下这个流程

  • csrf_token的生成

1.生成一个csrf对象,这个对象主要用于生成和校验csrf_token的
2.生成UnboundField(CSRFTokenField)对象,插入到我们自定义Field列表中
3.在BaseForm中调用process方法,进而调用Field中的process方法生成current_token的值
4.wiget的call方法在渲染input标签时,会将input的value属性赋值current_token

...
#wtforms.form.BaseForm
 extra_fields = []
        #根据配置,是否需要生成csrf_token
        if meta.csrf:
            #生成一个csrf对象,这个对象用于生成和校验csrf_token的
            self._csrf = meta.build_csrf(self)
            #初始化一个CSRFTokenField对象,并加入到extra_fields这个列表中
            extra_fields.extend(self._csrf.setup_form(self))
...
  #wtforms.meta.DefaultMeta
 def build_csrf(self, form):
         #csrf_class在DefaultMeta是None,但是在其子类中有实现是_FlaskFormCSRF这个私有类,这个if条件是满足的,不为None
        if self.csrf_class is not None:
            #返回这个_FlaskFormCSRF的对象
            return self.csrf_class()
        #上一篇文章分析错误出现在这里,误以为返回的是SessionCSRF这个对象
        from wtforms.csrf.session import SessionCSRF
        return SessionCSRF()
#wtforms.csrf.core.CSRF
field_class = CSRFTokenField
 def setup_form(self, form):
        meta = form.meta
      #得到field_name=csrf_token
        field_name = meta.csrf_field_name
        #建立CSRFTokenField的对象,但是这里返回的应该是UnboundField对象
        unbound_field = self.field_class(
            label='CSRF Token',
            csrf_impl=self
        )
        #返回一个列表[('csrf_token,UnboundField')]
        #后续会调用UnboundField的bind方法建立CSRFTokenField对象
        return [(field_name, unbound_field)]
#wtforms.csrf.core.CSRFTokenField
def process(self, *args):
        super(CSRFTokenField, self).process(*args)
        #self.csrf_Impl=_FlaskFormCSRF
        #也就是调用_FlaskFormCSRF的generate_csrf_token()生成csrf_token
        self.current_token = self.csrf_impl.generate_csrf_token(self)
#flask_wtf.csrf.generate_csrf
def generate_csrf(secret_key=None, token_key=None):
   secret_key = _get_config(
        secret_key, 'WTF_CSRF_SECRET_KEY', current_app.secret_key,
        message='A secret key is required to use CSRF.'
    )
    field_name = _get_config(
        token_key, 'WTF_CSRF_FIELD_NAME', 'csrf_token',
        message='A field name is required to use CSRF.'
    )
   
    if field_name not in g:
        if field_name not in session:
             #同时放到session中一份
            session[field_name] = hashlib.sha1(os.urandom(64)).hexdigest()
         #使用URLSafeTimedSerializer对象对这个csrf_token进行签名并放到全局对象g中
        s = URLSafeTimedSerializer(secret_key, salt='wtf-csrf-token')
        setattr(g, field_name, s.dumps(session[field_name]))

    return g.get(field_name)

至此,csrf_token的值生成完毕!

  • csrf_token的验证

1.调用表单的validate_on_submit方法,来开始校验
2.判断表单中是否有自定义的校验
3.没有则遍历Form中所有的Field的validate()方法进行校验

#表单提交时调用这个方法
 if form.validate_on_submit():
#wtforms.form.Form
def validate(self):
        extra = {}
        for name in self._fields:
            #是否有自定义的Field验证
            inline = getattr(self.__class__, 'validate_%s' % name, None)
            if inline is not None:
                extra[name] = [inline]
        #调用父类BaseForm的validate方法
        return super(Form, self).validate(extra)
#wtforms.form.BaseForm
def validate(self, extra_validators=None):
        self._errors = None
        success = True
        for name, field in iteritems(self._fields):
            if extra_validators is not None and name in extra_validators:
                extra = extra_validators[name]
            else:
                extra = tuple()
            #调用CSRFTokenField的validate校验,进而调用Field的validate方法
            if not field.validate(self, extra):
                success = False
        return success
#wtforms.fields.core.Field
def validate(self, form, extra_validators=tuple()):
        self.errors = list(self.process_errors)
        stop_validation = False
        # Call pre_validate
        try:
            #调用CSRFTokenField的pre_validate方法
            self.pre_validate(form)
        except StopValidation as e:
            if e.args and e.args[0]:
                self.errors.append(e.args[0])
            stop_validation = True
        except ValueError as e:
            self.errors.append(e.args[0])

        # Run validators
        if not stop_validation:
            chain = itertools.chain(self.validators, extra_validators)
            stop_validation = self._run_validation_chain(form, chain)

        # Call post_validate
        try:
            #调用CSRFTokenField的pre_validate方法
            self.post_validate(form, stop_validation)
        except ValueError as e:
            self.errors.append(e.args[0])
        return len(self.errors) == 0
#wtforms.csrf.core.CSRFTokenField
 def pre_validate(self, form):
        # self.csrf_impl=flask_wtf.csrf._FlaskFormCSRF
        self.csrf_impl.validate_csrf_token(form, self)

#flask_wtf.csrf.validate_csrf
def validate_csrf(data, secret_key=None, time_limit=None, token_key=None):
    secret_key = _get_config(
        secret_key, 'WTF_CSRF_SECRET_KEY', current_app.secret_key,
        message='A secret key is required to use CSRF.'
    )
    field_name = _get_config(
        token_key, 'WTF_CSRF_FIELD_NAME', 'csrf_token',
        message='A field name is required to use CSRF.'
    )
    #csrf_token的有效期
    time_limit = _get_config(
        time_limit, 'WTF_CSRF_TIME_LIMIT', 3600, required=False
    )
   #csrf_token丢失
    if not data:
        raise ValidationError('The CSRF token is missing.')
    #csrf_token没在session中
    if field_name not in session:
        raise ValidationError('The CSRF session token is missing.')
    
    s = URLSafeTimedSerializer(secret_key, salt='wtf-csrf-token')
    #判断csrf_token是否过期或是是否有效
    try:
        token = s.loads(data, max_age=time_limit)
    except SignatureExpired:
        raise ValidationError('The CSRF token has expired.')
    except BadData:
        raise ValidationError('The CSRF token is invalid.')

    if not safe_str_cmp(session[field_name], token):
        raise ValidationError('The CSRF tokens do not match.')

码字不易,喜欢就留下个小心心吧!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容