框架第十二课---csrf跨站请求伪造,csrf相关校验策略, CBV添加装饰器的多种方式,auth认证模块,BBS项目需求分析

昨日内容回顾

  • django中间件三个需要了解的方法

    process_view
    	路由匹配成功之后执行视图函数\类之前自动触发
    process_exceptoon
    	视图函数\类执行报错之后自动触发
    process_template_response
    	返回的HttpResponse对象含有render属性并且对应一个方法是自动触发
    
  • 基于django中间件实现功能插拔式设计

    1.importlib
    	通过字符串导入模块
    2.从右开始切割字符串
    	rsplit
    3.反射
    	将模块名看成是对象通过反射获取该模块中某个字符串对应的名字
    4.面向对象的多态性
    
  • cookie与session简介

    cookie
    	保存在客户端与用户状态相关的数据
    session
    	保存在服务端与用户状态相关的数据
    ps:以后我们还会接触到token、jwt等各种技术 目的都只有一个>>>:记录某种状态
    
  • django操作cookie

    HttpResponse对象.set_cookie(key,value)  # 添加cookie信息到对象,再返回对象到前端,浏览器就会帮我们保存这样的KV键值对,每次浏览器向后端发送请求数据的时候,都会把cookie的数据,放在请求头里面!!!注意
    HttpResponse对象.COOKIES.get(key)
    
    
    HttpResponse对象.set_signed_cookie(key,
                            value,
                            salt='加密盐',       # 给value的数据加盐
                            max_age=None,        # 失效时间
                            expires=None         # 失效时间 针对IE浏览器的
                             ...
                                    )
    HttpResponse对象.delete_cookie(key)     # 删除cookie  用户的注销登录会用到!!!
    
    ''' 所谓退出登录本质其实就是:将客户端的标识用户身份的cookie删除!!! '''
    
  • django操作session

    1.默认必须要有django_session表   !!!
    2.django默认的session失效时间14天  !!!
    
    request.session[key] = value       # 设置session
    	1.针对key生成一个加密之后的字符串
    	2.在django_session表中存储加密字符串与加密value数据的记录
    	3.并且将该加密字符串返回给客户端保存
    
    request.session.get(key)          # 获取session
    	1.自动获取客户端携带的加密字符串
    	2.自动去django_session表比对是否有对应记录
    	3.如果有自动获取加密的数据并解密到request.session中
    
    补充知识
    	 # 将所有Session失效日期小于当前日期的数据删除
        request.session.clear_expired()
        # 检查会话session的key在数据库中是否存在
        request.session.exists("session_key")
    

  # 删除当前会话的所有Session数据
  request.session.delete()
  # 删除当前的会话数据并删除会话的Cookie。
  request.session.flush()

  # 设置会话Session和Cookie的超时时间
  request.session.set_expiry(t)
    * 如果t是个整数,session会在多少秒数后失效。
    * 如果t是个datatime日期对象或timedelta时间间隔,session就会在这个时间后失效。
    * 如果t是0,用户关闭浏览器session就会失效。
    * 如果t是None,session会依赖全局session失效策略。

> # 今日内容概要

* csrf跨站请求伪造
* csrf相关校验策略
* CBV添加装饰器的多种方式
* auth认证模块
* BBS项目需求分析

> # 今日内容详细

### csrf跨站请求伪造(就是钓鱼网站)
Cross-site request forgery  跨站请求伪造
```python
钓鱼网站:模仿一个正规的网站,让用户在该网站上做操作,但是操作的结果会影响到用户正常的网站账户,但是其中有一些猫腻。
  eg:英语四六级考试需要网上先缴费,但是你会发现卡里的钱扣了,但是却交到了一个莫名其妙的账户,并不是真正的四六级官方账户
--------------------------------------
在假网站的转账页面上输入用户名与密码后,假网站会拿这这些数据朝真网站服务端发送请求,假如你用户名密码输对了,真网站就会正常给你扣钱了,但是假网站却从中把转账的收款人改掉了,应该转给官方账户的结果转给了陌生账户去了!!! 然后你会发现,你的钱也扣了,也是真网站做的处理,但是收款人却不对了,这就是钓鱼网站

钓鱼网站套路就是:先做一个假网站,再做一个和真网站一摸一样的页面,用户在这个假页面上操作的请求也是发给真网站,只不过在发给真网站的过程中,假网站把其中某些重要的数据做了修改,比如把转账的收款人改掉,或者转账的金额改掉等等!!用户的账户信息是存在真网站的,所以肯定要朝真网站发送请求!!
--------------------------------------
模拟钓鱼网站案例:转账案例
  内部隐藏标签

思考:如何区分真假网站页面发送的请求

可以看到在假网站上提交的jackma给zhaowei转1万块钱的操作,但是收款人那标签有问题,显示在页面上的收款人框没有任何功能,真正起作用的是隐藏的标签,所以实际变成了jackma给黑客jason转1万块钱,而且因为请求是朝真网站提交的,导致在真网站的后台拿到的是假网站提交来的数据,假如真网站拿着假网站的数据,到数据库里找jackma这个人,然后将他的账户钱扣了10000块钱,然后又朝
黑客jason转了10000块钱,这样一来,扣钱转钱的操作全是真网站做的,但是钱确没有转到zhaowei的账户去!!!!
image
.
注意假网站也是有服务端的,也是要先启动服务端的,假设假网址的网址做的和真网址的网址很像,页面样式也一样,用户就很难发现是假网站了,但是假网站的页面的post请求不是朝自己网站发送的,而是朝真网站的发送post请求的!!!
.
真网站的页面
image
image
.
.
注意假网站的网站已经不一样了,但是假网站的post请求确朝真网站发了!!!
image
image
.
image
.
造成这种现象的原因实际上就一点:真网站区分不出来,朝它发请求的到底真网站发过来的,还是从钓鱼网站发过来的!!!因为钓鱼网站的页面也能朝真网站的后台发送请求!!!
.
.

csrf校验策略

真网站在给用户发送一些比较核心的页面时,比如说这个页面是转账页面这种比较重要的页面的时候,在发给用户的时候会在页面上添加一个唯一标识!然后等页面朝后端发请求的时候,后端会先校验页面有没有这个标识,如果没有,说明这个页面根本不是原来后端给的页面,有问题,直接拒绝掉请求。
这样假网站搞出的假网页携带数据朝真网站发送请求时,真网站一看页面上没有标识,或者标识是错的,立刻就知道不是一个正常的请求!!!从而把假网站的请求给拒绝掉!!!

在提交数据的位置添加唯一标识
--------------------------------
MIDDLEWARE = [
    # 'django.middleware.csrf.CsrfViewMiddleware',
]

django中经常被 我们注掉的一个中间件的作用就是:添加唯一标识,校验唯一标识工作的人!!!
--------------------------------

form表单与ajax 针对csrf 的策略

token  令牌 标记(n)

1.form表单csrf策略
	form表单内部利用模板语法添加一行代码         {% csrf_token %}
	就会自动产生一个唯一标识,由于该标识写在form表单的里面,当提交post请求的时候,唯一标识也会跟着请求提交到服务端,服务端先拿到标识判断,然后再决定处不处理该请求!!!

------------------------------------------
2. ajax请求csrf策略
// 方式1:自己动手取值 较为繁琐
	data:{'csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val()},

{% csrf_token %}在页面上会渲染出input标签出来,所以该标签的查找肯定能找到,点value肯定能拿到唯一标识的串!!!
------------

// 方式2:利用模板语法自动获取(一定要用引号引起来)
	data:{ 'csrfmiddlewaretoken':'{{ csrf_token }}','username':'jason'},
------------

// 方式3:直接引入一个js脚本即可(官网提供的)

	参考:https://www.cnblogs.com/Dominic-Ji/p/9234099.html
------------

可以看到{% csrf_token %}模板语法会在页面上自动生成一个input标签,标签的name=csrfmiddlewaretoken value=是一个随机生成的字符串这就是唯一的标识,每刷新一次,这个串都会变,永远都是独一无二的,同一个网址连续请求,每次唯一标识的串都会变,这样就保证了时效性了,一个串只能用一次,用完就失效了。
也就是说比如登录页面,当输入网址或跳转网址进入登录页面时,中间件会给登录页面添加一个唯一标识,只有带该唯一标识的登录页面提交post请求时,中间件检查标识没有问题才能让请求的数据往后面走!!也就是说要中间件先给串,再提交请求的时候再拿着给的串作为凭证,才能通过中间件的校验。
假网址的网页是不可能有正网站中间件给的唯一标识的串的,所以提交post请求数据,在csrf中间这一定过不了!!!请求一定会被csrf中间件拒绝掉的!!!
image
image
.
.

方式3:直接引入一个js脚本
在静态文件的static文件夹里面新建一个js文件比如叫myjs.js,然后把如下代码粘贴进去,在html页面上通过导入该文件(  <script src="/static/myjs.js"></script>  )即可自动帮我们解决ajax提交post数据时校验csrf_token的问题,(导入该配置文件之前,需要先导入jQuery,因为这个配置文件内的内容是基于jQuery来实现的)

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');


function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
  beforeSend: function (xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});

.
.
.

csrf相关装饰器

decorator 装饰者,装饰器(n)

两种特殊情况:
整个django项目都校验csrf 但是某些个视图函数或者视图类不想校验
整个django项目都不校验csrf 但是某些个视图函数或者视图类需要校验

就要用到两个装饰器
@csrf_exempt    这个是当中间件开启了预防跨站请求伪造后,想让某个视图函数不校验,就给装上

@csrf_protect  这个是当中间件关闭了预防跨站请求伪造后,想让某个视图函数开启csrf校验,就给装上

--------------------------------------------
FBV添加装饰器的方式(与正常函数添加装饰器一致)
from django.views.decorators.csrf import csrf_exempt, csrf_protect
# @csrf_exempt
@csrf_protect
def transfer_func(request):pass
---------------------------------------------
CBV添加装饰器的方式(与正常情况不一样 需要注意)
主要有三种方式:
-----------------------------------------------------------
from django.utils.decorators import method_decorator

class MyView(views.View):
    @method_decorator(csrf_protect)      # 方式1:给类里面的函数单独添加,单独生效
    def post(self, request):
        return HttpResponse('from cbv post view')
-----------------------------------------------------------

# 方式2: 给类添加装饰器并指明对哪个函数  单独生效   这句话就是对类里面的post函数生效
@method_decorator(csrf_protect, name='post')
class MyView(views.View):
    def post(self, request):
        return HttpResponse('from cbv post view')
-------------------------------------------------------------
在类里面写dispatch方法顶掉父类里面的dispatch方法,但是又用super()方法子类重新调用父类里面的dispatch方法。

class MyView(views.View):
    @method_decorator(csrf_protect)     # 方式3:整个类中所有函数都生效
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    def post(self, request):
        return HttpResponse('from cbv post view')

    def get(self, request):
        return HttpResponse('from cbv get view')
-------------------------------------------------------
注意有一个装饰器是特例:只能有一种添加方式>>>:csrf_exempt
	只有在dispatch方法添加才会生效
注意当把中间件csrf打开,想要演示:只有在dispatch方法上面加装饰器才使类里面的方法避免被校验效果时,一定要注意html页面里面原来的{% csrf_token %} 要注掉,不然你会发现不加装饰器也能通过,因为你已经有令牌了!!!必须把令牌的这个{% csrf_token %}代码注掉!!!

# @method_decorator(csrf_exempt, name='post')
class Myview(views.View):

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    # @method_decorator(csrf_exempt)
    def post(self, request):
        return HttpResponse('from cbv post view')
-------------------------------------------------------------------

.
.
.
.

auth认证模块

auth认证模块就是基于auth_user表,然后该模块提供了一些方法,通过这些方法可以,非常快速的操作这张表,将来写用户相关的业务就省事了,表不用创,密码自动加密解密,cookie与session也能帮我们操作了,一条龙服务。

前戏:django自带一个admin路由 但是需要我们提供管理员账号和密码
如果想要使用admin后台管理 需要先创建表 然后创建管理员账号
直接执行数据库迁移命令,即可产生默认的  auth_user表  !!!
该表就是admin后台管理默认的认证表!!!
也就是说如果想等到admin后台管理里面,就需要用到auth_user表里面的数据 !!!
----------------------------------------

1.创建超级管理员

1.创建超级管理员
	python38 manage.py createsuperuser

基于auth_user表编写用户相关的各项功能
	登录、校验用户是否登录、修改密码、注销登录等

image
.
image
.
这个就是django的后台管理,由于现在没有在后台管理里面做任何操作,所以页面看上去很简单,其实已经有很多功能了!!
image
.
.
.
auth模块可以提供一条龙服务,将来写用户相关的业务,且并不是那么复杂,用auth模块是最方便的

auth认证相关模块及操作

authenticate 鉴定;证明……是真实的(v)
contrib 有贡献的意思

from django.contrib import auth     # 导入auth模块
from django.contrib.auth.models import User      # User相当于就是auth_user表
---------------------------------------------------------------

判断用户名是否存在
res = User.objects.filter(username=username)
if res:
    return HttpResponse('用户名已存在')
----------------------------------------------------------------

1.用户注册功能
    User.objects.create_user(username=username, password=password)  # 会自动对密码加密然后讲所有数据放到auth_user表里面去!!!

    # 注意User.objects.create(username=username,password=password)该方法不行,因为密码的加密无法实现,填加到表里面的密码没有加密
-----------------------------------------------------------------

2.判断用户名和密码是否正确
	user_obj = auth.authenticate(request,
                       username=username,
                       password=password)

# 会自动将密码加密,然后拿用户名与加密后的密码到auth_user表里面去比对,如果比对一致,会返回用户对象!!!
# 用用户对象就可以点出对象里面含有的数据

------------------------------------------------------------------

3.判断用户是否登录
	request.user.is_authenticated
------------------------------------------------------------------
------------------------------------------------------------------

4.校验用户是否登录的 装饰器(两种情况)
from django.contrib.auth.decorators import login_required
第一种情况:
@login_required(login_url='/login/')        # 局部配置
def index_func(request):
    return HttpResponse('index页面 只有登录的用户才可以查看')

装饰器对视图函数装上后,在访问该视图函数的时候,就会去校验你请求头的数据里面有没有显示你已经登录的cookie随机字符串,如果没有标识用户登录的随机字符串,说明用户还没有登录,可以让用户的想访问该视图函数的请求自动跳到转指定的路由去,比如此处我们让没登录的用户想访问该视图函数时,自动跳转到登录的路由去!!!
装饰器还悄悄的干了:拿到想要访问的路由,利用问号携带数据的方法将想要访问的路由挂在指定跳转的登录的路由的屁股后,这样我们在登录的视图函数里通过request.GET.get('next')就能拿到登陆前想 访问的路由,在登录成功后,还是可以控制再跳到登录前的页面去!!!
if target_path:
    obj = redirect(target_path)  # 4. 如果信息存在,就跳转到登陆前想要访问的路由去!!!
else:
    obj = redirect('/home/')
request.session.set_expiry(2419200)       # 也可以设置session  的保存时间
return obj
------------------------------------------------------
在配置文件中写一个全局配置   LOGIN_URL = '/login/'

第二种情况:
from django.contrib.auth.decorators import login_required
@login_required  # 全局配置  这个时候用户没登录访问该视图函数也会自动跳到login路由去
def index_func(request):
    return HttpResponse('index页面 只有登录的用户才可以查看')

-------------------------------------------------------------------
5. auth.login(request, user_obj)
执行完该操作之后,我们可以通过request.user直接获取当前登录的用户对象
request.user是AuthenticationMiddleware类里面函数里面的代码。
-------------------------------------------------------------------

6. 获取登录用户对象
	request.user

-------------------------------------------------------------------

7. 校验原密码是否正确
	request.user.check_password(原密码)
-------------------------------------------------------------------

8. 修改密码
	request.user.set_password(新密码)
	request.user.save()
--------------------------------------------------------------------

9. 退出登录(注销登录)
	auth.logout(request)
--------------------------------------------------------------------

.

相关代码:
urlpatterns = [
    path('admin/', admin.site.urls),
    # auth用户注册
    path('register/', views.register_func),
    # auth用户登录
    path('login/', views.login_func),
    # 网站首页
    path('home/', views.home_func),
    # index页面 只有登录的用户才可以看
    path('index/', views.index_func),
    # auth用户修改密码
    path('set_pwd/', views.set_pwd_func),
    # auth用户退出登录
    path('logout/', views.logout_func)
]
-------------------
from django.contrib import auth
from django.contrib.auth.models import User

def register_func(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')

        # 1. 校验一下当前的用户用户名是否已存在
        res = User.objects.filter(username=username)
        if res:
            return HttpResponse('用户名已存在')
        # 注册该用户
        # User.objects.create(username=username,password=password)  # 该方法不行,因为密码的加密无法实现
        User.objects.create_user(username=username, password=password)

    return render(request, 'registerPage.html')
-----------------------------------------

def login_func(request):
    print(request.user)  # 如果用户已经登录过了结果就是用户对象,如果用户没有登录过结果就是匿名用户AnonymousUser
    print(request.user.is_authenticated)  # 判断当前所发请求的用户是否已登录,是就返回True,没登陆过就返回False
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 1. 校验用户名与密码是否正确,自己无法比对密码,必须要使用auth模块提供的方法才可以
        user_obj = auth.authenticate(request, username=username, password=password)
        if user_obj:
            # 用户登录成功后返回给客户端登录的随机字符串凭证就是cookie
            # auth.login是auth包文件里面的__init__文件里面的login函数
            # 不需要自己request.session操作session表了,auth模块的auth.login()会自动帮我们到django_session表里面创建一个空键值对
            # 然后把用户对象加密存到django_session表里面的session_data下(作为值),然后返回给前端浏览器一个随机的字符串(作为键)
            auth.login(request, user_obj)
            # 自动操作session表,用户对象加密存到session_data下,生成一个随机的字符串存到session_key下,并将该字符串给前端浏览器作为cookie
            """
            当执行完上述的操作之后,我们可以通过request.user直接获取当前登录的用户对象
            """
            target_path = request.GET.get('next')
            if target_path:
                obj = redirect(target_path)  # 4. 如果信息存在,就跳转到想要访问的路由去!!!
            else:
                obj = redirect('/home/')
            request.session.set_expiry(2419200)
            return obj
            # return HttpResponse('登录成功!!')
    return render(request, 'loginPage.html')
--------------------------------

def home_func(request):
    return render(request, 'homePage.html', locals())
-------------------------------

from django.contrib.auth.decorators import login_required

@login_required(login_url='/login/')  # 可以明确指定用户没有登录之后跳转到哪个地址
def index_func(request):
    return HttpResponse('index页面 只有登录的用户才可以查看')
-------------------------------

@login_required(login_url='/login/')
def set_pwd_func(request):
    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 not new_pwd == confirm_pwd:
            return HttpResponse('两次密码不一致')
        # 1. 判断原密码是否正确
        is_right = request.user.check_password(old_pwd)
        if not is_right:
            return HttpResponse('原密码不正确')
        # 2. 修改密码
        request.user.set_password(new_pwd)
        request.user.save()  # 一定要保存密码,否则不会修改

    return render(request, 'setPwdPage.html')
-------------------------------

@login_required(login_url='/login/')
def logout_func(request):
    # 删除session表里面相关数据
    auth.logout(request)  # 会自动到session表里面,找到request.user对应的用户数据,然后把对应的用户数据删掉
    return HttpResponse('退出登录')

-------------------------------
homePage页面代码(实现如果用户是登录状态,则显示用户名,否则显示登录与注册按钮)
<body>
    {% if request.user.is_authenticated %}
        <h1>{{ request.user.username }}</h1>
    {% else %}
        <a href="">注册</a>
        <a href="">登录</a>
    {% endif %}
</body>
-------------------------------
loginPage页面代码(简单的用户输入框)
<body>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" method="post">
                {% csrf_token %}
                <p>username:
                    <input type="text" name="username" class="form-control">
                </p>
                <p>password:
                    <input type="text" name="password" class="form-control">
                </p>
                <input type="submit" value="登录" class="btn btn-primary btn-block">
            </form>
        </div>
    </div>
</div>
</body>
-------------------------------------
registerPage页面代码同上差不多
-------------------------------------
setPwdPage页面代码(也和上面差不多)
<body>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" method="post">
                {% csrf_token %}
                <p>原密码:
                    <input type="text" name="old_pwd" class="form-control">
                </p>
                <p>新密码:
                    <input type="text" name="new_pwd" class="form-control">
                </p>
                <p>确认密码:
                    <input type="text" name="confirm_pwd" class="form-control">
                </p>
                <input type="submit" value="提交" class="btn btn-primary btn-block">
            </form>
        </div>
    </div>
</div>
</body>
----------------------------------

.
.
.

扩展auth_user表

还想使用auth模块的功能 并且又想扩展auth_user表的字段
思路1:一对一字段关联(比较繁琐)
----------------------------------
思路2:替换auth_user表
---------
步骤1:模型层编写模型类继承AbstractUser
from django.contrib.auth.models import AbstractUser

class UserInfo(AbstractUser):
    # 填写AbstractUser表中没有的字段
    phone = models.BigIntegerField(null=True)
    desc = models.TextField(null=True)
---------
步骤2:一定要在配置文件中声明替换关系
AUTH_USER_MODEL = 'app01.UserInfo'
---------
ps:替换还有一个前提 就是数据库迁移没有执行过(auth相关表没有创建)
如果数据库之前已经被模型层映射过了,你再映射的时候会报错!!!
---------
如果真的决定要替换AbstractUser表,一定我们写了一个类继承了AbstractUser
那就相当于准备要把auth_user替换掉,所以想做这一步操作,一定要确保从没没有执行过数据库迁移命令,如果当前数据库之前已经做过数据 库迁移命令了,直接用mysql创一个新的库,然后连好后,再执行数据库迁移命令!!!这样生成的表里面就没有auth_user表了,取而代之的就是app01_userinfo表了,之前所有的和auth模块相关的操作功能,就基于这张你自己创的表上面去了
---------

作业

1.利用auth模块编写一个网站导航条相关的功能(用自己创建的表)
	也可以直接在图书管理系统中切入用户相关的功能
		导航条右侧 用户没有登录显示 登录 和 注册
		用户登录之后 现实当前用户名及登录之后的一些操作 修改密码 退出登录
2.整理今日内容及博客
3.复习本周内容
posted @   tengyifan  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示