文章目录
- csrf跨站请求伪造
- csrf的定义
- csrf的分类
- csrf的攻击过程
- csrf的攻击条件
- 举例说明
- Django提供的解决策略
- csrf相关装饰器
- FBV
- CBV
- 方法一(直接在类中的某个方法上添加)
- 方法二(直接在类名上添加并指定方法)
- 方法三(重写dispatch方法并添加作用于类中所有的方法)
- auth认证模块
- auth认证模块是什么?
- auth模块方法大全
- 扩展默认的auth_user表
- 基于django中间件设计项目功能
csrf跨站请求伪造
csrf的定义
CSRF(Cross-site request forgery)跨站请求伪造,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。
可以这样来理解:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。
csrf的分类
- HTML CSRF攻击
- JSON HiJacking攻击
- Flash CSRF攻击
csrf的攻击过程
- 假设用户A登录银行的网站进行操作,同时也访问了攻击者预先设置好的网站。
- 用户A点击了攻击者网站的某一个链接,这个链接是http://www.bank.com/xxx指向银行,银行服务器根据这个链接携带的参数会进行转账操作。
- 银行服务器在执行转账操作之前会进行Session验证,但是由于用户A已经登录了银行网站,攻击者的链接也是www.bank.com,所以攻击的链接就会携带session到服务器。
- 由于session id是正确的,所以银行会判断操作是由本人发起的,执行转账操作。
csrf的攻击条件
- 登录受信任网站A,生成可信的Session
- 在不登出A的情况下,访问危险网站B,网站B伪造网站A的请求
举例说明
1.简介
钓鱼网站:假设是一个跟银行一模一样的网址页面 用户在该页面上转账
账户的钱会减少 但是受益人却不是自己想要转账的那个人
2.模拟
一台计算机上两个服务端不同端口启动 钓鱼网站提交地址改为正规网站的地址
真正的网页:
views.py
def real(request):
if request.method == 'POST':
username = request.POST.get('username')
money = request.POST.get('money')
target_user = request.POST.get('target_user')
print(f'用户{username}往账户{target_user}转账{money}')
return render(request, 'real.html')
html
<body>
<h1>这是真正的网页</h1>
<form action="" method="post">
<p>username:
<input type="text" name="username">
</p>
<p>target_user:
<input type="text" name="target_user">
</p>
<p>money:
<input type="text" name="money">
</p>
<input type="submit">
</form>
</body>
钓鱼网页
views.py
def func(request):
return render(request, 'func.html')
html
<body>
<h1>这是钓鱼网页</h1>
<form action="http://127.0.0.1:8000/real/" method="post">
<p>username:
<input type="text" name="username">
</p>
<p>target_user:
<input type="text">
<input type="text" name="target_user" value="jason" style="display: none">
</p>
<p>money:
<input type="text" name="money">
</p>
<input type="submit">
</form>
</body>
预防:
csrf策略:通过在返回的页面上添加独一无二的标识信息从而区分正规网站和钓鱼网站的请求
Django提供的解决策略
针对csrf相关的校验有很多种方式 django只是提供了一些而已
- form表单
<body>
<h1>这是真正的网页</h1>
<form action="" method="post">
{% csrf_token %}
<p>username:
<input type="text" name="username">
</p>
<p>target_user:
<input type="text" name="target_user">
</p>
<p>money:
<input type="text" name="money">
</p>
<input type="submit">
</form>
</body>
- ajax
方式1:
页面任意位置先写{% csrf_token %} 之后获取数据
'csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val()
方式2:模板语法直接获取
'csrfmiddlewaretoken':{{ csrf_token }}
方式3:通用方式(js脚本)
也只能适用于ajax提交,form表单还是需要额外指定
csrf相关装饰器
思考:
1.当整个网站默认都不校验csrf 但是局部视图函数需要校验 如何处理
2.当整个网站默认都校验csrf 但是局部视图函数不需要校验 如何处理
csrf_protect:校验csrf
csrf_exempt:不校验csrf
FBV
导入模块
:from django.views.decorators.csrf import csrf_protect,csrf_exempt
举例说明:
@csrf_protect # 该函数校验csrf
@csrf_exempt # 该函数不校验csrf
def home(request):
return HttpResponse('哈哈哈')
CBV
需要借助于模块
:from django.utils.decorators import method_decorator
针对CBV不能直接在方法上添加装饰器 需要借助于专门添加装饰器的方法
方法一(直接在类中的某个方法上添加)
@method_decorator(csrf_protect) # 方式1:指名道姓的添加
def post(self, request):
return HttpResponse('Home Post view')
方法二(直接在类名上添加并指定方法)
@method_decorator(csrf_protect, name='get')
class MyLoginView(views.View):
def get(self, request):
return HttpResponse("from CBV get view")
方法三(重写dispatch方法并添加作用于类中所有的方法)
@method_decorator(csrf_protect) # 方式3:影响类中所有的方法
def dispatch(self, request, *args, **kwargs):
super(MyHome, self).dispatch(request, *args, **kwargs)
强调:
针对csrf_exempt只有方式3有效,针对其他装饰器上述三种方式都有效
auth认证模块
auth认证模块是什么?
Auth模块是Django自带的用户认证模块:
在开发网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码、校验、验证用户是否登录等功能。
Django作为一个完美主义者的终极框架,它内置了强大的用户认证系统——auth,它默认使用 auth_user 表来存储用户数据。
auth模块方法大全
auth.authenticate()方法
提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username、password两个关键字参数。
如果认证成功(用户名和密码正确有效),便会返回一个User对象。(如果认证不成功,则会返回None。)
authenticate()会在该 User对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。
username = request.POST.get('username')
password = request.POST.get('password')
# 拿到前端传输回来的数据后,要进行数据校验,但是发现无法获取到数据库数据
user_obj = auth.authenticate(request, username=username, password=password)
auth.login(request,user_obj)
该函数接受一个HttpRequest对象,以及一个经过认证的User对象。
该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据。
用法:
只要执行了login(request, user)方法,就可以在任意地方通过request.user获得当前登录的用户对象。
username = request.POST.get('username')
password = request.POST.get('password')
# 拿到前端传输回来的数据后,要进行数据校验,但是发现无法获取到数据库数据
user_obj = auth.authenticate(request, username=username, password=password) # 存在的话返回的是一个对象,否则返回的是一个None
if user_obj:
request.session['username'] = str(user_obj) # 在服务端记录一份
auth.login(request, user_obj) # 保存用户登录的状态
is_authenticated
用来判断当前请求是否通过了认证。可以用来判断用户是否登陆过,返回的是布尔值。
def home(request):
if not request.user.is_authenticated:
return redirect(...)
login_requierd() 装饰器
auth 给我们提供的一个装饰器工具,用来快捷的给某个视图添加登录校验。
from django.contrib.auth.decorators import login_required
@login_required(login_url='/login/') # 局部配置
def set_password(request):
若用户没有登录,也没有自定义url路径,则会跳转到django默认的 登录URL '/accounts/login/ ’
并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。
如果需要自定义登录的URL,除了局部配置,也可以在settings.py文件中通过LOGIN_URL进行修改。
LOGIN_URL = '/login/' # 这里配置成你项目登录页面的路由
check_password(password)
auth 提供的一个检查密码是否正确的方法,需要提供当前请求用户的密码。
密码正确返回True,否则返回False。
可以用于密码的重置中校验原密码
is_right = request.user.check_password(old_pwd)
set_password(password)
auth 提供的一个修改密码的方法,接收 要设置的新密码 作为参数。注意:设置完一定要调用用户对象的save方法!!!
用法:
request.user.set_password(new_pwd)
request.user.save()
修改功能代码示例:
@login_required # 判断用户登录状况的装饰器
def set_password(request):
# print(request.user.is_authenticated) # 判断该用户是否已经登录
if request.method == 'POST':
old_pwd = request.POST.get('old_pwd')
new_pwd = request.POST.get('new_pwd')
confirm_pwd = request.POST.get('confirm_pwd')
if new_pwd == confirm_pwd:
is_right = request.user.check_password(old_pwd)
if is_right:
request.user.set_password(new_pwd)
request.user.save()
return HttpResponse('修改成功')
return render(request, 'set_pwd.html')
auth.logout(request)
该函数接受一个HttpRequest对象,无返回值。
当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
用法:
from django.contrib.auth import logout
def logout_view(request):
auth.logout(request)
# Redirect to a success page.
create_user()
auth 提供的一个创建新用户的方法,需要提供必要参数(username、password)等。
用法:
from django.contrib.auth.models import User
user = User.objects.create_user(username='用户名',password='密码',...)
create_superuser()
auth 提供的一个创建新的超级用户的方法,需要提供必要参数(username、password)等。注意:创建超级用户的时候必须要传邮箱
用法:
from django.contrib.auth.models import User
user = User.objects.create_superuser(username='用户名',password='密码',email='邮箱',...)
扩展默认的auth_user表
这内置的认证系统这么好用,但是auth_user表字段都是固定的那几个,在项目中可能没法拿来直接使用。
思考:
比如,我想要加一个存储用户手机号的字段,怎么办?
你可能会想到新建另外一张表然后通过一对一和内置的auth_user表关联,这样虽然能满足要求但是有没有更好的实现方式呢?
办法:
我们可以通过继承内置的 AbstractUser 类,来定义一个自己的Model类。
这样既能根据项目需求灵活的设计用户表,又能使用Django强大的认证系统了。
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
nid = models.AutoField(primary_key=True)
phone = models.BigIntegerField(default=123, unique=True)
def __str__(self):
return self.username
前提条件:
1.继承之前没有执行过数据库迁移命令
auth_user没有被创建,如果当前库被创建了,就需要重新换一个库
2.继承的类里不要覆盖AbstractUser里面的字段
3.需要在配置文件中告诉django要用userinfo替换auth_user
AUTH_USER_MODEL = "app名.userinfo"
补充:
如果继承了AbstractUser,
那么执行数据库迁移命令时,auth_user就不会被创建出来了,
而userinfo表中会出现auth_user表中的所有字段外加扩展字段
基于django中间件设计项目功能
importlib模块
可以通过字符串的形式导入模块
常规导入
from ccc import b
print(b) # <module 'ccc.b' from '/Users/jiboyuan/PycharmProjects/day61_1/ccc/b.py'>
print(b.name)
字符串导入
import importlib
module_path = 'ccc.b'
res = importlib.import_module(module_path)
print(res.name)
字符串只能到py文件层次
基于中间件思想编写项目示例
函数版
def send_qq(content):
print('QQ消息通知':content)
def send_weixin(content):
print('weixin消息通知':content)
def send_msg(content):
print('msg消息通知':content)
def send(content):
send_qq(content)
send_weixin(content)
send_msg(content)
if __name__ == '__main__'
send('国庆放假七天,也要加油')
配置文件插拔式
需要自己创建一个settings文件和启动文件start文件
setting.py
MIDDLEWARE = [
'bag.notify.msg.Msg',
'bag.notify.weixin.Weixin',
'bag.notify.qq.QQ',
]
还需要自己创建目录如下:
注意:当我们导一个包的名字的时候,其实是向包里的双下init要名称数据
双下init.py
import importlib
import settings
def send_all(content):
for i in settings.MIDDLEWARE: # 'bag.notify.msg.Msg'
module_path, class_str_name = i.rsplit('.', maxsplit=1) # 'bag.notify.msg' 'Msg'
module = importlib.import_module(module_path) # 'msg.py'
class_name = getattr(module, class_str_name) # 是个类,通过反射判断py文件中是否存在方法,存在则将对应的值赋值过去
obj = class_name() # 产生一个对象
obj.send(content)
qq.py
class QQ(object):
def __init__(self):
pass
def send(self, content):
print('qq消息发送:', content)
weixin.py
class Weixin(object):
def __init__(self):
pass
def send(self, content):
print('weixin消息发送:', content)
msg.py
class Msg(object):
def __init__(self):
pass
def send(self, content):
print('msg消息发送:', content)