flask_jinja2模板渲染_过滤器_解决csrf攻击

jinja2模板引擎

渲染模板函数

  • Flask提供的render_template函数封装了该模块引擎
  • render_template函数的第一个参数是文件名,后面的参数都是键值对,就是模板中的 变量

jinja2提示设置


模板基本使用

  1. 创建模板文件夹templates
  2. 创建template_folder参数
  3. 在视图函数中设置模板数据
from flask import Flask
from flask import render_template


app = Flask(import_name=__name__, template_folder='templates')

class Config(object):
    DEBUG = True

app.config.from_object(Config)

@app.route('/')
def index():
    title = 'index'
    return render_template('index.html', title=title)

app.run()

输出变量

{{}} 来表示变量 
Jinja2 模版中的变量代码块可以是任意 Python 类型或者对象,只要它能够被 Python 的 `__str__` 方法或者str()转换为一个字符串就可以,比如,可以通过下面的方式显示一个字典或者列表中的某个元素:

视图代码

#!/usr/bin/env python 
# -*- coding:utf-8 -*-
from flask import Flask,render_template
app = Flask(__name__)
@app.route('/')
def index():
    name = 'bobo'
    habby = ['sport','sing','smoke']
    score = {'math':100,'English':120}

    return render_template('test.html',name=name,habby=habby,score=score)
if __name__ == "__main__":
    app.run(debug=True)

模板代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ name }}</title>
</head>
<body>
    <h1>{{name}}</h1>
    <h1>{{habby}}</h1>
    <h1>{{score['math']}}</h1>
    <h1>{{habby.0}}</h1>
    <h1>{{score.English}}</h1>
    <h1>{{habby.0}}</h1>
    <h1>{{habby[0]}}</h1>
</body>
</html>

注释

{# {{name}} #}

模板中特有的变量和函数

你可以在自己的模板中访问一些Flask默认内置的函数和对象

request

{{ request.url }}
http://127.0.0.1

session

{{session}}

g变量

在视图函数中设置g变量的值,然后在模板中使用

from flask import g
def index():
    g.name = 'gelong'
    return render_template('index.html')
# index.html

{{g.name}}

url_for()

url_for会根据传入的路由器函数名,返回该路由对应的URL,在模板中始终使用url_for()就可以安全的修改路由绑定的URL,则不比担心模板中渲染出错的链接:

{{url_for('home')}}

流程控制

if / elif  / else / endif
for / endfor

if语句

视图代码:

#!/usr/bin/env python 
# -*- coding:utf-8 -*-
from flask import Flask,render_template,g
app = Flask(__name__)

@app.route('/')
def index():
    book_list = [
        {"id": 1, "price": 78.50, "title": "javascript入门"},
        {"id": 2, "price": 88.50, "title": "python入门"},
        {"id": 3, "price": 98.50, "title": "django项目实战"},
        {"id": 4, "price": 71.50, "title": "javascript入门1"},
        {"id": 5, "price": 63.50, "title": "python入门2"},
        {"id": 6, "price": 58.50, "title": "django项目实战3"},
    ]
    total_price = 0
    return render_template('test.html',book_list=book_list,total_price=total_price)
if __name__ == "__main__":
    app.run(debug=True)

模板代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table border="3">
    <tr>
        <td>标号</td>
        <td>价格</td>
        <td>名称</td>
    </tr>
    {% for book in book_list %}
        <tr>
            <td>{{ book.id }}</td>
            <td>{{ book.price }}</td>
            <td>{{ book.title }}</td>
        </tr>
    {% endfor %}

    {% if total_price > 0 %}
        总价格大于0
        {% elif total_price < 0%}
        总价格小于0
        {% else %}
        总价格等于0
    {% endif %}
</table>
</body>
</html>

循环语句

  • 在一个for循环中你可以访问这些特殊的变量
变量 描述
loop.index 当前循环迭代的次数(从 1 开始)
loop.index0 当前循环迭代的次数(从 0 开始)
loop.revindex 到循环结束需要迭代的次数(从 1 开始)
loop.revindex0 到循环结束需要迭代的次数(从 0 开始)
loop.first 如果是第一次迭代,为 True 。
loop.last 如果是最后一次迭代,为 True 。
loop.length 序列中的项目数。

过滤器

过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。

使用方式:

  • 过滤器的使用方式为:变量名 | 过滤器。
{{variable | filter_name(args1,args2,....)}}
  • 如果没有任何参数传给过滤器,则可以把括号省略掉
{{variable | filter_name }}

在 jinja2 中,过滤器是可以支持链式调用的,示例如下:

{{ "hello world" | reverse | upper }}

常见的内建过滤器

字符串操作

  • safe:禁用转义
<p>{{ '<em>hello</em>' | safe }}</p>
  • capitalize:把变量值的首字母转成大写,其余字母转小写
<p>{{ 'hello' | capitalize }}</p>
  • lower:把值转成小写
<p>{{ 'HELLO' | lower }}</p>
  • upper:把值转成大写
<p>{{ 'hello' | upper }}</p>
  • title:把值中的每个单词的首字母都转成大写
<p>{{ 'hello' | title }}</p>
  • reverse:字符串反转
<p>{{ 'olleh' | reverse }}</p>
  • format:格式化输出
<p>{{ '%s is %d' | format('name',17) }}</p>
  • striptags:渲染之前把值中所有的HTML标签都删掉

    如果内容中,存在大小于号的情况,则不要使用这个过滤器,容易误删内容。

<p>{{ '<em>hello</em>' | striptags }}</p>
<p>{{ "如果x<y,z>x,那么x和z之间是否相等?" | striptags }}</p>
  • truncate: 字符串截断
<p>{{ 'hello every one' | truncate(9)}}</p>

列表操作

  • first:取第一个元素
<p>{{ [1,2,3,4,5,6] | first }}</p>
  • last:取最后一个元素
<p>{{ [1,2,3,4,5,6] | last }}</p>
  • length:获取列表长度
<p>{{ [1,2,3,4,5,6] | length }}</p>
  • sum:列表求和
<p>{{ [1,2,3,4,5,6] | sum }}</p>
  • sort:列表排序
<p>{{ [6,2,3,1,5,4] | sort }}</p>

语句块过滤

{% filter upper %}
    #一大堆文字#
    abcdefg
{% endfilter %}
```

```
#一大推文字# ABDWADA
```



### 自定义过滤器

过滤器的本质是函数。当模板内置的过滤器不能满足需求,可以自定义过滤器。自定义过滤器有两种实现方式:

- 一种是通过Flask应用对象的 **add_template_filter** 方法
- 通过装饰器来实现自定义过滤器

**重要:自定义的过滤器名称如果和内置的过滤器重名,会覆盖内置的过滤器。**



需求:添加列表反转的过滤器

方式一

通过调用应用程序实例的 add_template_filter 方法实现自定义过滤器。该方法第一个参数是函数名,第二个参数是自定义的过滤器名称:

```python
# 自定义过滤器
def do_list_reverse(old_list):
    # 通过list新建一个列表进行操作,就不会影响到原来的数据
    new_list = list(old_list)
    new_list.reverse()
    return new_list

# 注册过滤器
app.add_template_filter(do_list_reverse, "lrev")
```

方式二

用装饰器来实现自定义过滤器。装饰器传入的参数是自定义的过滤器名称。

```python
@app.template_filter('lrev')
def do_list_reverse(old_list):
    # 因为字典/列表是属于复合类型的数据,所以改动数据的结构,也会应该能影响到原来的变量
    # 通过list新建一个列表进行操作,就不会影响到原来的数据
    new_list = list(old_list)
    new_list.reverse()
    return new_list
```

- 主程序中创建和注册过滤器

```python
"""
两种方式:
    1. flask对象的add_template_filter
    2. 装饰器
"""

from flask import Flask, render_template

app = Flask(__name__)
# 配置终端脚本运行项目
from flask_script import Manager

manager = Manager(app)

# 自定义过滤器
def do_list_reverse(old_list):
    # 通过list新建一个列表操作,就不会影响到原来的数据
    new_list = list(old_list)
    new_list.reverse()
    return new_list

# 注册过滤器
app.add_template_filter(do_list_reverse, 'lrev')


# 使用装饰器自定义过滤器
@app.template_filter('ustr')
def do_str_upper(old_str):
    new_str = old_str.upper()
    return new_str

@app.route('/test')
def test():
    old_list = [1,2,3,4,5]
    old_str = 'love'
    return render_template('test.html', old_list=old_list, old_str=old_str)

if __name__ == '__main__':
    manager.run()

# python xx.py runserver
```

- html调用过滤器

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {{ old_list | lrev }}
    {{ old_str | ustr }}
</body>
</html>
```



- 运行结果

```
[5, 4, 3, 2, 1] LOVE
```



#### 案例:给手机进行部分屏蔽

```
185****5215
```



```python
from flask import Flask,render_template,request
from settings.dev import Config
from flask_script import Manager
"""创建flask应用"""
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
"""加载配置"""
app.config.from_object(Config)

@app.template_filter("mobile")
def do_mobile(data,string):
    return data[:3]+string+data[7:]

@app.route("/")
def index():
    data = {}
    data["user_list"] = [
        {"id":1,"name":"张三","mobile":"13112345678"},
        {"id":2,"name":"张三","mobile":"13112345678"},
        {"id":3,"name":"张三","mobile":"13112345678"},
        {"id":4,"name":"张三","mobile":"13112345678"},
    ]
    return render_template("index2.html",**data)

if __name__ == '__main__':
    manage.run()
```

index2.html,模板代码:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table border="1" align="center" width="680">
    <tr>
        <th>ID</th>
        <th>姓名</th>
        <th>手机</th>
    </tr>
    {% for user in user_list %}
    <tr>
        <td>{{ user.id }}</td>
        <td>{{ user.name }}</td>
        <td>{{ user.mobile | mobile(string="****") }}</td>
    </tr>
    {% endfor %}

</table>
</body>
</html>
```

效果:

![](https://img2020.cnblogs.com/blog/2148219/202102/2148219-20210217140957146-1520562448.png)




## 模板继承

一个严谨专业的网站往往需要统一的结构,这样看起来比较“整洁”。

比如说,一个网站有多个子页面,每个页面中都要有标题、内容显示、底部等几个部分。如果在每一个网页中都进行这几部分的编写,那么这个网站将会有很多冗余部分,浪费时间还不美观。

在模板中,可能会遇到以下情况:

- 多个模板具有完全相同的顶部和底部内容
- 多个模板中具有相同的模板代码内容,但是内容中部分值不一样
- 多个模板中具有完全相同的 html 代码块内容

案例:

在static文件夹下新建一个style.css文件存放样式:

```css
*{margin: 0; padding: 0;}
ul{list-style: none;}
li{width:100px; float: left;}
.cl_btoh{clear: both;}

#head{width:800px; height:50px; line-height: 50px; margin:0 auto; background-color: #303641;}
#head a{color:#ECEFF1;}
#head ul{line-height: 50px;}
#head li{text-align: center;}

#main{height:400px; width:800px; background-color: yellowgreen; margin: 0 auto; color:#FFFFFF;
text-align: center; line-height: 400px;}

#footer{height:40px; width:800px; margin: 0 auto; background-color: #3B666B; color:#FFFFFF;
text-align: center; line-height: 40px;}
```



index.html:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="{{ url_for('static',filename='style.css') }}">
    <meta charset="UTF-8">
    <title>Title</title>
</head>
	<body>
        <div id="head">
            <ul>
                <li><a href="#">首页</a></li>
                <li><a href="#">养生新闻</a></li>
                <li><a href="#">人群养生</a></li>
                <li><a href="#">两性养生</a></li>
                <li><a href="#">生活养生</a></li>
                <li><a href="#">饮食养生</a></li>
                <li><a href="#">中医养生</a></li>
            </ul>
        </div>
        <div class="cl_btoh"></div>

        <div id="main">
            <h4>这是index页面主体部分</h4>
        </div>

        <div id="footer">
            <h4>网站底部内容</h4>
        </div>
	</body>

</html>
```

list.html

```html
<!DOCTYPE html>
<html>
	<head>
    <link rel="stylesheet" href="{{ url_for('static',filename='style.css') }}">
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
        <div id="head">
            <ul>
                <li><a href="#">首页</a></li>
                <li><a href="#">养生新闻</a></li>
                <li><a href="#">人群养生</a></li>
                <li><a href="#">两性养生</a></li>
                <li><a href="#">生活养生</a></li>
                <li><a href="#">饮食养生</a></li>
                <li><a href="#">中医养生</a></li>
            </ul>
        </div>
        <div class="cl_btoh"></div>

        <div id="main">
            <h4>这是list页面主体部分</h4>
        </div>

        <div id="footer">
            <h4>网站底部内容</h4>
        </div>
	</body>
</html>
```

app.py

```python
@app.route('/index')
def my_index():
    return render_template('index.html')
@app.route('/list')
def my_list():
    return render_template('list.html')
```

这两个运行结果,除了中间部分不一样,头部、底部都一样,其实新浪,网易、豆瓣等网站每个页面的头尾部也都是一样的。

既然我们知道网站头部、底部都一样,哪我们是不是可以把网站头部、底部代码抽取出来,放到公共页面中,下次需要我们在继承或者调用就OK了

模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板(base.html)中,子模板直接继承,而不需要重复书写。

##### 继承语法

{% extends “模板名称” %}

##### 块的概念

父模板里包含了网站里基本元素的基本骨架,但里面有一些空的或不完善的块(block)需要用子模板来填充。

在父模块中:

```
{% block block的名称 %}
{% endblock %}
```

在子模块中:

```
{% block block的名称 %}
子模板中的代码
{% endblock %}
```

##### 模板继承应用

我们在templates目录下新建一个base.html文件,这个文件(base.html)可以称为父模板或基模板,其代码如下:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{{ url_for('static',filename='style.css') }}">
</head>
<body>
    {% block head %}
         <div id="head">
            <ul>
                <li><a href="#">首页</a></li>
                <li><a href="#">养生新闻</a></li>
                <li><a href="#">人群养生</a></li>
                <li><a href="#">两性养生</a></li>
                <li><a href="#">生活养生</a></li>
                <li><a href="#">饮食养生</a></li>
                <li><a href="#">中医养生</a></li>
            </ul>
        </div>
        <div class="cl_btoh"></div>
    {% endblock %}
 
    {% block main %}
         <div id="main">
            <h4>这是base页面主体部分</h4>
        </div>
    {% endblock %}
 
    {% block footer %}
         <div id="footer">
            <h4>网站底部内容</h4>
        </div>
    {% endblock %}
</body>
</html>
```

我们在父模板中定义好了接口后,子模板(index.html 跟 list.html)的代码可以删除了。

在index.html文件中,我们通过extends语句来导入父模板,然后就能在子模板中衍生父模板定义的接口

```
{% extends "base.html" %}
```

修改index中主体:

```
{% extends "base.html" %}
 
{% block main %}
     <div id="main">
         <h4>这是index页面主体部分</h4>
     </div>
{% endblock %}
```

super():在子模板中使用父类已有内容

```
{% extends "base.html" %}
{% block main %}
    {{ super() }}
     <div id="main">
         <h4>这是list页面主体部分</h4>
     </div>
{% endblock %}
```

模板继承使用时注意点:

1. 不支持多继承

2. 为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。

3. 不能在一个模板文件中定义多个相同名字的block标签。

4. 当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。

   



## 在 Flask 项目中解决 CSRF 攻击

- `CSRF`全拼为`Cross Site Request Forgery`,译为跨站请求伪造。
- `CSRF`指攻击者盗用了你的身份,以你的名义发送恶意请求。包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......
- CSRF造成的问题:个人隐私泄露以及财产安全。
- CSRF流程:

​       ①用户正常登录A银行网站,

​        ②A网站返回cookie信息给用户,浏览器保存cookie信息

​        ③在A网站没有退出登录的情况下(或者说cookie信息没过期), 登录了恶意网站B

​        ④恶意网站B,提前准备好转账表单或者其它请求 ,将其隐藏. 把提交到A网站的按钮设置为一个"领取优惠券"的图片链接.用户 点击链接

​        ⑤在用户主观未知的情况下,访问A网站,此时浏览器会自动携带cookie信息

​        ⑥A网站识别到cookie信息,默认为是用户本人做出的请求,根据请求做出相应的操作.

​        ⑦用户收到损失.

![](https://img2020.cnblogs.com/blog/2148219/202102/2148219-20210217140914167-773993808.png)



原理其实很简单。举个简单的例子。A是一个很有钱的人,B是专门帮人托管钱的人,相当于银行的角色,C是偷窃者。A和B约定(Cookie),有人叫你给钱你就给钱。A把钱放在B那里。平时A就去问B拿钱,B就直接给钱给他。某一天,C也问一下B拿钱,B也直接给钱给C了,C这时候就相当于偷了A的钱。这就是csrf的攻击原理。

解决办法的原理也很简单,就是A和B约定一下暗号,C来问B拿钱时,对不上暗号,自然就拿不到钱了。



在 Flask 中, Flask-wtf 扩展有一套完善的 csrf 防护体系,对于我们开发者来说,使用起来非常简单

```
pip install flask_wtf
```

index.html

```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    uname:<input type="text" name="uname">
    password:<input type="password" name="pwd">
    <input type="submit" name="提交">
</form>
</body>
</html>
```

server

```python
@app.route('/login',methods=['POST'])
def login()
    print(request.form)
    return '%s您已登录!'%request.form.get('uname')
```

问题:有可能index.html中的表单不是我们的是被黑客伪造的,存在安全风险。

所以,我们必须确认表单是我们自己的,可以准确识别出表单无误即可。

##### csrf_token 校验原理,具体操作步骤有以下几步:

1.后端生成 csrf_token 的值,在前端请求登录或者注册界面的时候将值传给前端

2.在前端发起请求时,在表单或者在请求头中带上指定的 csrf_token

3.后端在接受到请求之后,取到前端发送过来的 csrf_token,与第1步生成的 csrf_token 的值进行校验

4.如果校验对 csrf_token 一致,则代表是正常的请求,否则可能是伪造请求,不予通过

##### 实现

后端生成csrf_token 发送给前端

```python
from flask import Flask,render_template,request
from flask_wtf import CSRFProtect
app = Flask(__name__)

class Config(object):
    DEBUG = True
    #需要为CSRF保护设置一个密钥,但通常情况下,和Flask应用的SECRET_KEY是一样的
    SECRET_KEY = "dsad32DASSLD*13%^32"
app.config.from_object(Config)

#能够让所有的视图受到CSRF保护
CSRFProtect(app)

@app.route('/index')
def index():
    return render_template('正规途径的登录界面.html')

@app.route('/login',methods=['POST'])
def login():
    print(request.form)
    return 'ok'
if __name__ == "__main__":
    app.run()
```

在表单中使用 CSRF 令牌:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="/login" method="post">
    uname:<input type="text" name="uname">
    password:<input type="password" name="pwd">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    <input type="submit" name="提交">
</form>
</body>
</html>
```
posted @ 2021-02-17 14:10  死里学  阅读(73)  评论(0)    收藏  举报