flask_jinja2模板渲染_过滤器_解决csrf攻击
jinja2模板引擎
渲染模板函数
- Flask提供的render_template函数封装了该模块引擎
- render_template函数的第一个参数是文件名,后面的参数都是键值对,就是模板中的 变量
jinja2提示设置


模板基本使用
- 创建模板文件夹
templates - 创建
template_folder参数 - 在视图函数中设置模板数据
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>
```
效果:

## 模板继承
一个严谨专业的网站往往需要统一的结构,这样看起来比较“整洁”。
比如说,一个网站有多个子页面,每个页面中都要有标题、内容显示、底部等几个部分。如果在每一个网页中都进行这几部分的编写,那么这个网站将会有很多冗余部分,浪费时间还不美观。
在模板中,可能会遇到以下情况:
- 多个模板具有完全相同的顶部和底部内容
- 多个模板中具有相同的模板代码内容,但是内容中部分值不一样
- 多个模板中具有完全相同的 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信息,默认为是用户本人做出的请求,根据请求做出相应的操作.
⑦用户收到损失.

原理其实很简单。举个简单的例子。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>
```
本文来自博客园,作者:长情不羁的五年,转载请注明原文链接:https://www.cnblogs.com/grlend/articles/14408766.html

浙公网安备 33010602011771号