首页 > 技术文章 > csrf模块与Auth模块

mengqingqiang 2021-12-08 20:14 原文

今日内容详情

  • crsf跨站请求伪造
  • crsf检验
  • csrf相关装饰器(CMV、MBV)
  • CMV、MBV添加装饰器的三种方法
  • crsf模块补充
  • Auth模块
  • Auth模块的一些方法
  • auth_user表扩展

csrf跨站请求伪造

'''
钓鱼网站:
	自己搭建一个跟正规的网站一模一样的界面(中国银行)
	用户进入我们自己搭建的网站,给别人转账
	转账的钱确实提交给中国银行的系统上,用户的钱也减少了
	但是给转账的账户变成别的账户
	
内部本质;
	我们在钓鱼网站的页面,针对对方用户,只给用户提供一个没有name属性的input框
	然后我们自己内部隐藏一个已经写好name和value的input框
'''

# 比如我们自己模拟一下
<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" value="提交">
</form>
</body>

# views.py
def transfer(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        target_user = request.POST.get('target_user')
        money = request.POST.get('money')
        print('%s给%s转了%s元' % (username, target_user, money))
    return render(request, 'transfer.html')

'''
网页提交转账请求:meng给zhang转了10000元
'''

# 钓鱼网站
<body>
<h1>我是钓鱼的网站</h1>
# 给正经的网站发送请求
<form action="http://127.0.0.1:8000/transfer/" method="post">
    <p>username:<input type="text" name="username"></p>
    <p>target_user<input type="text"></p>
    # 在转账对象使用input框,修改转账用户,并且隐藏让用户看不见
    <input type="text" name="target_user" value="liu" style="display: none">
    <p>money<input type="text" name="money"></p>
    <input type="submit" value="提交">
</form>
</body>
'''
本想给张转账,结果变成了给刘转账
网页提交转账请求:meng给liu转了20000元
'''

# 问题:如果解决这个问题?
	csrf跨站请求伪造校验
    网站在给用户返回一个具有提交数据功能页面的时候会给这个页面加上一个唯一标识,
    当这个页面向后端发送post请求时候,会先校验唯一标识。
    如果唯一标识不对直接拒绝会报403,成功正常执行。

csrf检验

'''
可以向后端提交请求方式有两种:
	1.form表单
	2.ajax
'''
# form表单如何符合校验
方法一:
	{% csrf_token %}  # 添加csrf_token校验
    <input type="hidden" name="csrfmiddlewaretoken" 
    value="DOvbCOPEAWfQPKPvKcpFEJpPyBce5gNd4vNQv2eWC63GVxVJS94h7z91KVW8fFoy">
	# 浏览器页面会出现随机字符串,并会发送到后端校验
    
    
<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" value="提交">
</form>
</body>

csrf校验之ajax

# 第一种:值通过标签查找
    <button id="d1">ajax请求</button>
    <script>
        $('#d1').on('click',function () {
            $.ajax({
                url:'',
                type:'post',
                # 第一种利用标签查找获取页面上的随机字符串
                data:{
                    'username':'meng',
                    'csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()
                },
                # 值通过标签查找
                success:function () {
                }
            })
        })
    </script>
    '''
    这种书写太麻烦
    'csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()
    '''
    
# 第二种:直接利用模板语法提供的快捷方式
	<button id="d1">ajax请求</button>
    <script>
        $('#d1').on('click',function () {
            $.ajax({
                url:'',
                type:'post',
            # 第二种:直接利用模板语法提供的快捷方式
            data:{'username':'meng','csrfmiddlewaretoken':'{{ csrf_token }}'}, 
                success:function () {
                }
            })
        })
    </script>
	'''
	这种只适合利用前后端没有分离
	'csrfmiddlewaretoken':'{{ csrf_token }}'
	'''
    
# 第三种:通过导用js代码完成校验   
	{% load static %}  # 动态获取
    <script src="{% static 'js/mysetup.js' %}"></script>  导入js文件
    '''
    拷贝下面代码做成js文件,页面直接引用即可
    通用模板:推荐使用
    '''

js文件:官方文档推荐使用这种方式

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);
    }
  }
});

官方文档:https://docs.djangoproject.com/en/1.11/ref/csrf/

crsf相关装饰器

FBV:是基于函数的视图

CMV:是基于类的视图函数

FBV添加装饰器

'''
需求:
	1.网站整体不校验csrf,就单单几个视图函数需要校验
	2.网站整体校验csrf,就单单几个视图函数不校验
'''

from django.views.decorators.csrf import csrf_protect,csrf_exempt  # 导入功能
'''
csrf_protect:需要校验
	针对csrf_protect符合我们给CBV添加三种装饰器的规则
	
csrf_exempt:不需要校验
	csrf_exempt只能给dispatch方法添加才有效
'''

@csrf_exempt   # 加上它,唯独下面这个功能不校验csrf
@csrf_protect  # 加上它,唯独下面需要校验csrf
def transfer(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        target_user = request.POST.get('target_user')
        money = request.POST.get('money')
        print('%s给%s转了%s元' % (username, target_user, money))
    return render(request, 'transfer.html')

CBV添加装饰器

# 前提:导入模块
    from django.views import View
    from django.utils.decorators import method_decorator
    
# csrf_protect第一种方式:
    from django.views import View
    from django.utils.decorators import method_decorator
    class MyCsrfToken(View):
        def get(self, request):
            return HttpResponse('get')

        @method_decorator(csrf_protect)  # 针对csrf_protect,第一种方式可以
        def post(self, request):
            return HttpResponse('post')
        
# csrf_protect第二种方式:
    from django.views import View
    from django.utils.decorators import method_decorator  # 前提导入这两个模块
    @method_decorator(csrf_protect,name='post')  # 针对csrf_protect,第二种方式可以
    class MyCsrfToken(View):
        def get(self, request):
            return HttpResponse('get')

        def post(self, request):
            return HttpResponse('post')
        
# csrf_protect第三种方式:
    class MyCsrfToken(View):
        @method_decorator(csrf_protect)   # 针对csrf_protect,第三种方式可以
        def dispatch(self, request, *args, **kwargs):
            return super(MyCsrfToken,self).dispatch(request, *args, **kwargs)
        
        def get(self, request):
            return HttpResponse('get')

        def post(self, request):
            return HttpResponse('post')
        
'''
总结:
	针对csrf_protect校验,添加装饰器三种方法都可以。
	1.第一种方式是在post请求上面添加
'''


# csrf_exempt第三种方式:
    class MyCsrfToken(View):
        @method_decorator(csrf_exempt)  # 针对csrf_exempt只有第三种方式可以
        def dispatch(self, request, *args, **kwargs):
            return super(MyCsrfToken,self).dispatch(request, *args, **kwargs)

        def get(self, request):
            return HttpResponse('get')

        def post(self, request):
            return HttpResponse('post')
        
'''
总结:
	针对csrf_exempt只有第三种方式可以,只能加给dispatch
	其余两种方式不可以
'''

补充模块

importlib模块
import importlib
res = 'meng.b'
ret = import.importlib_module(res)  # 跟from meng import b一个意思
# 但是这个方法最小只能到py文件名

Auth模块

'''
在我们在创建好一个Django项目之后,执行数据库迁移命令会自动生成很多表
auth_user:就是auth模块需要用到的表
django在启动后有一个admin路由,需要输入账户和密码,数据参考的是auth_user表,
并且还必须是管理员用户才进入
'''

创建超级用户(管理员):
	# 命令行创建
        python3 manage.py createsuperuser
        tools---->run----->createsuperuser   # 还可以这样
    # 代码创建
    	 User.objects.create_superuser(username=username, email='123@qq.com', password=password)  # 邮箱是必填的

        
        
# 依赖于auth_user实现注册登录功能
# 验证是否登录:
	# 局部内置:用户没有登录跳转到后面网址
        from django.contrib.auth.decorators import login_required  # 导入模块
        @login_required(login_url='/login/') 
    
    # 全局配置:没有登录调到执行的页面,在settings.py中配置
        LOGIN_URL = '/login/'
    
    '''
    局部和全局都有,优先级:局部>全局。
    局部和全局的优缺点:
        全局的优点:不需要重复写代码,但是跳转的页面却很单一
        局部的优点:不同的视图函数在用户没有登录的情况下,可以跳转到不同的页面
    '''

中间件版登录注册功能

总路由URL

from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 登录功能
    url(r'^login/',views.login),
    # 首页
    url(r'^home/', views.home),

    # 修改密码
    url(r'^set_password/', views.set_password),
    # 注销功能
    url(r'^logout/', views.logout),
    # 注册功能
    url(r'^register/', views.register),
]

登录功能

from django.shortcuts import render, redirect, HttpResponse
from django.contrib import auth  # auth模块

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 使用auth模块比对密码
        user_obj = auth.authenticate(request, username=username, password=password)
        # print(user_obj)  # 用户对象,可以根据点的方式取出用户名和密码
        '''
        1.自动查找auth_user标签
        2.自动给密码加密在比对
        注意事项:
            括号内必须同时传入用户名和密码
        '''
        
        # 判断当前用户是否存在
        if user_obj:
            # 保存用户状态
            auth.login(request, user_obj)  # 类似于request.session[key] = user_obj
          # 主要执行了该方法,你就可以在任何地方通过request.user获取到当前登录的用户对象
            return redirect('/home/')

    return render(request, 'login.html')

'''
auth模块提供的方法:
1、使用auth模块比对密码
	auth.authenticate(request, username=username, password=password)
	
2、保存用户状态
    auth.login(request, user_obj)
'''

验证登录功能装饰器

from django.contrib.auth.decorators import login_required   # 登录认证模块

登录之后才可以访问,全局配置过了
# @login_required(login_url='/login/')  # 局部内置:用户没有登录跳转到后面网址
@login_required  # 全局配置
def home(request):
    print(request.user)  # 拿到了用户对象  AnonymousUser匿名用户
    # 判断用户是否登录
    print(request.user.is_authenticated())
    # 自动去django_session里面查找对应用户对象给你封装到request.user中
    return HttpResponse('ok')

'''
auth模块提供的方法
    1、验证登录
        @login_required

    2、判断用户是否登录
        request.user.is_authenticated()
        
    3、获取当前登录用户
        print(request.user)
'''

修改密码

@login_required
def set_password(request):
    if request.method == 'POST':
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')
        # 判断两次密码是否一致
        if new_password == confirm_password:
            # 校验原密码是否正确
            is_right = request.user.check_password(old_password)  # 自动加密比对密码,有一个返回值布尔值
            # 判断密码是否正确
            if is_right:
                # 修改密码
                request.user.set_password(new_password)
                # 保存密码
                request.user.save()  # 提交到数据库
        return redirect('/login/')
    return render(request,'set_password.html',locals())


'''
auth模块提供的方法:
	1、自动加密比对密码,有一个返回值布尔值
        is_right = request.user.check_password(old_password)
        
    2、修改密码
        request.user.set_password(new_password)  仅是在修改属性
        request.user.save()  # 提交到数据库
'''

注销功能

@login_required  # 登录验证
def logout(request):
    # 类似于request.session.flush()删除会话数据
    auth.logout(request)
    return redirect('/login/')

注册功能

from django.contrib.auth.models import User  # 操作auth_user表模块

def register(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 操作auth_user表
        # User.objects.create(username=username,password=password)  # 这种方法创建的数据,用户密码为明文
        # 创建普通用户
        # User.objects.create_user(username=username, password=password)
        # 创建超级用户(只了解),邮箱必填
        User.objects.create_superuser(username=username, email='123@qq.com', password=password)
    return render(request, 'register.html')

'''
    User.objects.create_superuse()
    一般创建普通用户即可
'''

总结方法

1、比对用户名和密码是否正确
    # 使用auth模块比对密码	
    user_obj = auth.authenticate(request, username=username, password=password)
    '''括号内必须同时传入用户名和密码'''
    # print(user_obj)  # 用户对象,可以根据点的方式取出用户名和密码
    
2、保存用户状态
    # 保存用户状态
    auth.login(request, user_obj)  # 类似于request.session[key] = user_obj
    # 主要执行了该方法,你就可以在任何地方通过request.user获取到当前登录的用户对象
    
3、判断用户是否登录
	print(request.user.is_authenticated())
     # 自动去django_session里面查找对应用户对象给你封装到request.user中
        
4、获取当前登录用户
	print(request.user)
    # 有就是拿到了用户对象 ,没有就是AnonymousUser匿名用户
    
5、校验用户是否登录装饰器
    from django.contrib.auth.decorators import login_required   # 登录认证模块
    # 局部内置:用户没有登录跳转到后面网址
        @login_required(login_url='/login/')  
    
    # 全局配置:没有登录调到执行的页面
        LOGIN_URL = '/login/'
        @login_required  # 全局配置
	'''
	如果全局和局部都有的话,优先级:局部>全局
	全局的优点:不需要重复写代码,但是跳转页面单一
	局部的优点:在于不同的视图函数在用户没有登录的情况下跳转到不同的页面
	'''
        
        
6、校验比对原密码
    is_right = request.user.check_password(old_password)  
    # 自动加密比对密码,有一个返回值是布尔值
    
7、保存密码
    # 修改密码
    request.user.set_password(new_password)
    # 保存密码
    request.user.save()  # 提交到数据库
    
8、注销
	auth.logout(request)
    
9、注册
from django.contrib.auth.models import User  # 操作auth_user表模块

    # 操作auth_user表,这种方法创建的数据,用户密码为明文
    # User.objects.create(username=username,password=password)
    # 创建超级用户(只了解),邮箱必填
    # User.objects.create_superuser(username=username, email='123@qq.com', password=password)
    创建普通用户
    User.objects.create_user(username=username, password=password)
    '''一般创建普通用户即可'''

auth_user表扩展

# 利用面向对象继承
class UserInfo(AbstractUser):
    '''
    前提:
        1.在继承之前没有执行过数据库迁移命令(auth_user没有被创建),创建了换一个数据库
        2.继承的类里面不要覆盖AbstractUser里面的字段名,只扩展自己需要扩展的字段
        3.需要配置文件中修改用UserInfo替换auth_user
            AUTH_USER_MODEL = 'app01.UserInfo'
                                应用名.表名
    '''
    phone = models.BigIntegerField()
    
'''
总结:
	auth_user变成UserInfo表
	如果继承了AbstractUser,那么在执行数据库迁移命令的时候auth_user表就不会创建出来了
	而UserInfo中会出现auth_user表所有的字段,外加自己扩展的字段
	好处:在于自己点击自己的表更加快速完成扩展
'''

推荐阅读