Django-查漏补缺(二)

本文主要为自己复习Django框架时所做的笔记,为个人迅速查询和复习所用

五. memcached

1. 安装和启动

Windows:

安装:memcached.exe -d install
启动:memccached.exe -d start

Linux:

安装:sudo apt install memcached
启动: cd /usr/local/memecached/bin
	  ./memcached -d start
或者默认参数快捷启动:service memcached start

参数:

  • -d:后台运行
  • -m:指定占用多少内存,单位为M,默认为64M
  • -p:指定占用的端口,默认为11211
  • -l:外部机器可以通过哪些IP地址链接到memcached,如果通过快捷方式启动则只有本机能够链接到memcached
/usr/bin/memcached -m 1024 -l 127.0.0.1 -u memcache -d start

2. talent操作memcached

  • 连接:

    talent 127.0.0.1 11211 
    
    
  • 添加数据

    • set:如果key已经存在那么就会替换,否则就是添加

      语法:set key compress(是否需要压缩) timeout(过期时间) stringlength(字符长度)
      示例:set zhangsan 0 60 8
      存储:STORED
      
    • add:如果key与已经存在则会报错NOT_STORED

      语法:add key compress(是否需要压缩) timeout(过期时间) stringlength(字符长度)
      示例:add zhangsan 0 60 8
      存储:STORED
      
  • 获取数据

    • get:从memcached中获取

      语法:get key
      示例:get username
      
  • 删除数据

    • delete:删除memcached中的一个键值对

      语法:delete key
      示例:delete username
      
    • flush_all:删除memcached中的所有数据

  • 修改数据

    • incr:给数据做加法,只能是数字类型

      set age 0 120 2
      18
      STORED
      incr age 2
      
    • decr:给数据做减法,只能是数字类型

  • 查看memcached状态

    • stats:查看memcached中执行了的各种操作和状态
    • stats items:查看所有items
    • stats cachedump items_id 0:访问某个items下的所有key

3. python操作memcached

  • 安装

    pip install python-memcached
    
  • 连接

    import memcache
    mc = memcache.Client(['127.0.0.1:11211','分布式服务的其他ip'],debug=True)
    
  • 设置数据

    mc.set('username','zhangsan',time=60*5) #设置单个数据
    mc.set_multi({'email':'123@qq.com','telephone':'123456789'},time=60*5)
    
  • 获取数据

    mc.get('username')
    
  • 删除数据

    mc.delete('username')
    
  • 自增长

    mc.incr('read_num',delta=10) #delta设置增加的步长
    
  • 自减少

    mc.decr('read_count',delta=10)
    

4. memcached的安全机制

  • memcached的操作不需要任何用户名和密码,只需要知道memcached服务器的ip地址和端口号即可,因此使用时要格外注意其安全性,解决方案大致如下

    • 使用 -l 参数设置为只有本地可以连接

    • 使用防火墙关闭11211端口,那么外界也就不能访问了

      ufw default deny #关闭所有未被使用的端口
      utf allow 端口号 #开启某个端口
      utf deny 端口号 #关闭某个端口
      utf enable #开启防火墙功能
      utf -disable #关闭防火墙功能
      utf -status #查看端口开闭状态
      

5. Django操作memcached

  • 配置

    #settings.py
    CACHES = {
        'default':{
            'BACKEND':'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION':'12.0.0.1:11211',
            'KEY_FUNCTION':KEY_FUNCTION, #自定义缓存前缀和版本号,方便不同的django服务器访问同一个缓存key
        }
    }
    def KEY_FUNCTION(key,key_prefix,version):
        return '自定义前缀'+'自定义版本号'+key
    
  • 操作

    • 导入

      from django.core.cache import cache
      
    • 使用

      def index(request):
          cache.set('username','zhiliao',100)
          username = cache.get('username')
          return HttpResponse(username)
      

六. cookie和session

1. 设置cookie

  • 设置对象和方法:response.set_cookie

  • 方法相关参数:

    • key:设置cookie的key

    • valuecookievalue

    • max_age:cookie的生命周期,单位秒

    • expires:过期时间,跟max_age作用相似,只是需要传递一个复合日期格式具体的日期。如果同时设置了expiresmax_age,那么将以expires的值为准

      expires = datetime(year=2019,month=1,day=1,hour=20,minute=0,second=0)
      
    • path:对域名下生效的路径,默认为对所有路径都生效

    • domain:针对哪个域名生效,默认为主域名下都有效,需要设置子域名生效时可以设置这个属性

    • secure:设置是否只能在https协议下生效

    • httponly:默认为False,若为True,那么客户端不能通过js进行操作

      response.set_cookie('user_cookie','zhiliao',expires=expires,path='/article/')
      return response
      

2. 删除cookie

  • 删除的实质:实际上是将指定的cookie的值设为空的字符串,然后将它的过期时间设置为0,那么浏览器关闭后cookie就立即过期
  • 方法:delete_cookie

3. 获取cookie

  • 获取:request.COOKIE.get('')

4. 操作session

  • 获取:request.session.get('username')
  • 设置:request.session['username']='zhangsan'
  • 其他常用方法:
    • pop:删除一个值并返回request.session.pop('username')
    • keys:获取所有的键
    • items:获取所有的值
    • clear:清除当前请求用户的所有session数据
    • flush:除了清除当前请求用户的所有session数据还会删除在浏览器中存储的session_id,常用于注销
    • set_expiry(value):设置过期时间,value将有以下三种情况
      • 整形:代表秒数,即多少秒后过期
      • 0:浏览器关闭即过期
      • None:使用全局的session配置,在settings.py中设置SESSION_COOKIE_AGE=来配置全局的过期时间,默认为1209600秒即两周
    • clear_expired:清除过期的sessiondjango并不会定期清除数据库中的session数据,也可以在终端使用python manage.py clearsession来达到同样的效果

5. 修改session的存储机制

默认情况下,session存储到配置的数据库中,也可以通过设置SESSION_ENGINE来更改session来存储位置,其中包括以下集中方法

  • (默认)数据库:django.contrib.sessions.backends.db
  • 文件:django.contrib.sessions.backends.file
  • 缓存系统:django.contrib.sessions.backends.cache,注意不能使用纯内存,而是需要配置好memcached
  • 先缓存再数据库:django.contrib.sessions.backends.cached_db,可以防止数据丢失
  • 存储到浏览器的cookiedjango.contrib.sessions.backends.signed_cookies,注意这种方式虽然可以减轻服务器压力,但不够安全可靠,因此需要配置SESSION_COOKIE_HTTPONLY=True防止浏览器通过js来操作session数据,并且一定要对SECRET_KEY保密;另一方面,使用这种存储方法时,存储的数据不能超过4K

七. 中间件模块

1. 自定义中间件

  • 创建和存放:无固定位置,只要处于项目中即可,如果中间件属于某个app就可以在该应用下创建一个middleware.py文件来存放,也可以创建一个python包来存放所有的中间件。最后记住哦欸之在settings.py

  • 创建方式:使用函数的中间件

    def example_middleware(get_response): 
    	#[这个区域为中间件执行的初始化的代码]
        def middleware(request): 
    		#[这个区域为request到达的视图函数前的执行代码]
            response = get_response(request) 
    		#[这个区域为response到达客户端前的执行代码]
            return response
        return middleware
    
  • 创建方式:使用类的中间件

    class Front_middleware(object):
        def __init__(self,get_response):
            #[这个区域为中间件执行的初始化的代码]
        	self.get_response = get_response
        def __call__(self,request):
    		#[这个区域为request到达的视图函数前的执行代码]
            response = self.get_response(request) 
    		#[这个区域为response到达客户端前的执行代码]
            return response
    
  • 创建方式:利用Mixin多重继承(不建议)

    from django.utils.deprecation import MiddlewareMixin
    class Front_middlewareMixin(MiddlewareMixin):
        def __init__(self,get_response):
            #[这个区域为中间件执行的初始化的代码]
            super(MiddlewareMixin,self).__init__(get_response)       
        def process_request(self,request):
            #[这个区域为request到达的视图函数前的执行代码]
        def process_response(self,request,response):
    		#[这个区域为response到达客户端前的执行代码]
            return response
    

八. Web安全

1. CRSF跨站请求伪造

  • 定义:跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的

  • 开启CRSF中间件django.middleware.csrf.CsrfViewMiddleware,

  • 一般用法为:在前端使用表单提交数据时加载{% csrf_token %}

    <form action="/add-name-v2" method="post">
            {% csrf_token %}
            <input type="text" name="name">
            <input type="submit" value="提交">
    </form>
    
  • 在一个项目中,如果注册起用了CsrfViewMiddleware中间件,则项目中所有的视图函数和视图类在执行过程中都要进行CSRF验证。

  • 此时想使某个视图函数或视图类不进行CSRF验证,则可以使用csrf_exempt装饰器装饰不想进行CSRF验证的视图函数

    from django.views.decorators.csrf import csrf_exempt
    
    @csrf_exempt  
    def index(request):  
        pass
    

    也可以把csrf_exempt装饰器直接加在URL路由映射中,使某个视图函数不经过CSRF验证

    from django.views.decorators.csrf import csrf_exempt  
      
    from users import views  
     
    urlpatterns = [  
        url(r'^admin/', admin.site.urls),  
        url(r'^index/',csrf_exempt(views.index)),  
    ]
    
  • 如果在一个Django项目中,没有注册起用CsrfViewMiddleware中间件,但是想让某个视图函数进行CSRF验证,则可以使用csrf_protect装饰器

    from django.views.decorators.csrf import csrf_exempt  
    
    @csrf_protect  
    def index(request):  
        pass
    

    也可以把csrf_exempt装饰器直接加在URL路由映射中,使某个视图函数不经过CSRF验证

    from django.views.decorators.csrf import csrf_protect  
      
    from users import views  
     
    urlpatterns = [  
        url(r'^admin/', admin.site.urls),  
        url(r'^index/',csrf_protect(views.index)),  
    ]
    
  • 若视图是基于CBV的,则可以使用基于dispatch的装饰方法进行装饰

    from django.views.decorators.csrf import csrf_exempt
    from django.utils.decorators import method_decorator
    
    class UserAuthView(View):
    
        @method_decorator(csrf_exempt)
        def dispatch(self, request, *args, **kwargs):
            return super(UserAuthView,self).dispatch(request,*args,**kwargs)
        
    @method_decorator(csrf_exempt,name='dispatch')
    class UserAuthView(View):
    

    也可以直接装饰到URL路径中

    from django.views.decorators.csrf import csrf_exempt
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^auth/', csrf_exempt(views.UserAuthView.as_view())),
    ]
    

2. XSS跨站脚本攻击

  • 定义:跨站脚本攻击(XSS),是最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中(通常是JavaScript脚本),当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户、发放广告等目的。

  • 解决方案:

    • 对于非富文本,只需利用好Django本身的自动转义功能,再配合上escapesafemake_safeautoescape等方法或标签综合使用即可

    • 对于富文本,则可利用一个第三方库bleach,该库可以自定义哪些标签和属性是安全的,是不需要转义的;哪些标签是不安全的(如JavaScript标签),是需要转义的

      import bleach
      from bleach.sanitizer import ALLOWED_TAGS,ALLOW_ATTRIBUTES
      
      def add_comment(request):
          comment = request.POST.get('comment')
          tags = ALLOWED_TAGS +['img'] #在bleach默认的安全标签以外增加某个安全的标签,如img
          attributes = {**ALLOWED_ATTRIBUTES,'img':['src']} #给某个标签增加需要的安全属性
          cleaned_data = bleach.clean(content,tags=tags,\
                                      attributes=attributes)
          Comment.objects.create(content=cleaned_data)
          return redirect(reverse('index'))
      

3. Clickjacking 点击劫持攻击

  • 定义:点击劫持(ClickJacking)是一种视觉上的欺骗手段。大概有两种方式,一是攻击者使用一个透明的iframe,覆盖在一个网页上,然后诱使用户在该页面上进行操作,此时用户将在不知情的情况下点击透明的iframe页面做出错误的决定,如转账、关注等;二是攻击者使用一张图片覆盖在网页,遮挡网页原有位置的信息;

  • 解决方案:使用X-Frame-Options 来给浏览器指示允许一个页面可否在 <frame></iframe> 或者 <object> 中展现的标记。网站可以使用此功能,来确保自己网站的内容没有被嵌到别人的网站中去,也从而避免了点击劫持 的攻击。

  • Django配置X-Frame-Options:Django的项目中默认设置了XFrameOptionsMiddleware的中间件,这个设置将对于X-Frame-Options的配置设置成了DENY

    • X-Frame-Options 有三个值:

      • DENY :表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许
      • SAMEORIGIN :表示该页面可以在相同域名页面的 iframe 中展示
      • ALLOW-FROM url :表示该页面可以在指定来源的 iframe 中展示 ,若将uri设置为origin则表示允许任何网页通过iframe进行加载
    • 全局配置:

      X_FRAME_OPTIONS = 'SAMEORIGIN'
      
    • 指定视图配置

      from django.http import HttpResponse
      from django.views.decorators.clickjacking import xframe_options_exempt
      from django.views.decorators.clickjacking import xframe_options_deny
      from django.views.decorators.clickjacking import xframe_options_sameorigin
      
      
      @xframe_options_exempt
      def index(request):
      	return HttpResponse("This page is safe to load in a frame on any site.")
      
      
      @xframe_options_deny
      def index2(request):
      	return HttpResponse("I won't display in any frame!")
      
      @xframe_options_sameorigin
      def index3(request):
      	return HttpResponse("Display in a frame if it's from the same origin as me.")
      

4. SQL注入

  • SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
  • 解决方案:
    • 通过正则表达式、限制长度、对引号等字符进行转换等方法对用户的输入进行校验
    • 永远不要动态拼接sql语句
    • 永远不要使用管理员权限连接数据库,为每个应用使用单独的权限有限的数据库连接
    • 不要把机密信息直接存放到数据库。应该加密或hash存放到数据库
    • 应用的异常信息应该尽可能给出少的提示,最好使用自定义的错误信息对原始错误信息进行包装
    • DJANGO中尽量使用orm进行数据库操作

九.验证、授权和权限

1. User模型

  • 创建用户

    from django.contrib.auth.models import User
    def index(request):
        user = User.objects.create_user(username='',email='',password='',……) #创建一般用户
        user = User.objects.create_superuser(username='',email='',password='',……) #创建超级用户
    

    超级用户的创建也可以在控制台进行python manage.py createsuperuser

  • 修改密码set_password

    user = User.objects.get(pk=1)
    user.set_password('123456789')
    user.save()
    
  • 检查密码check_password

    user = User.objects.filter(username=username).first()
    if user:
        is_correct = user.check_password(password)
    
  • 登陆验证authenticate

    from django.contrib.auth import authenticate
    user.authenticate(username='xxx',password='yyy')
    if user:
        #登陆成功后的代码
    else:
    	#登陆失败后的代码
    
  • 判断登陆is_authenticated

    if request.user.is_authenticated:
        pass
    else:
        pass
    
  • 扩展User模型

    • 仅扩展部分方法(采用代理模型方式)

      class Person(User):
          class Meta:
              proxy = True #标名这个模型仅仅是User模型的一个代理模型。不会影响原User模型的所有结构
          
          def get_non_active(self):
              return self.object.filter(is_active=False) #该方法可以获取所有非激活用户
      

      注意:此时user.objects.all()Person.objects.all()是等价的

    • 仅扩展部分字段(采用一对一外键和监听方式)

      from django.db import models
      from djangp.contrib.auth.model import User
      from django.dispatch import receiver #接收器:接收某个信息
      from django.db.models.signals import post_save #信号:存储信号
      
      
      class  UserExtension(model.Model):
          user = model.OneToOneField(User,on_delete=models.CASCADE)
          telephone = model.CharField(max_length)
          
      @receiver(post_save,sender=User) #sender:信号源
      def handler_user_extension(sender,instance,created,**kwargs): #instamce表示User实例
          if created: #created表示创建
             UserExtension.objects.create(user=instance)
          else: #否则表示更新
          instance.extension.save()    
      
    • 扩展和修改字段(采用继承AbstractUser类)

      from django.contrib.auth.models import AbstractUser
      
      class MyUser(AbstractUser):
          telephone = model.CharField(max_length=11,unique=True)
          
          #指定telephone为作为'username'验证字段,以后authenticate验证时将采用telephone字段
          USERNAME_FIELD = 'telephone'
      

      之后在settings.py中设置新的User字段AUTH_USER_MODEL='app.models.MyUser'

    • 扩展、删除和修改字段(采用继承AbstractBaseUser模型)

      该方法不太建议使用,因为AbstractBaseUser类相当于一个白板,需要开发者添加的字段很多(仅有password/last_login/is_active等很少一部分字段)

      from django.contrib.auth.base_user import AbstractUser
      from django.contrib.auth.models import PermissionsMixin #处理权限相关(is_superuser)
      
      class MyUser(AbstractBaseUser,PermissionsMixin):
          email = email.EmailField(unique=True)
          username = model.CharField(max_length=10)
          is_actice = model.BooleanFild(default=True)
      

      之后同样在settings.py中设置新的User字段AUTH_USER_MODEL='app.models.MyUser'

    • 补充:

      • 直接获得settings.py配置好的user的模型,避免导入出错

        from django.contrib.auth import get_user_model
        
        class Article(model.Model):
            user = model.ForeignKey(get_user_model(),on_delete=models.CASCADE)
        
      • get_session_auth_hash:获取模型字段passwordHMAC,在密码更改时可使当前用户的登陆状态失效

      • set_unusable_password:标记用户尚未设置密码

      • has_usable_password:检测用户是否尚未设置密码

2. 权限

权限存储在auth_permissionn表中,默认权限一般为增、删、查、改

id content_type_id codename name
主键,无实际意义 链接content_type 权限 权限描述
  • 自定义权限

    • 通过自定义模型添加权限

      class Article(Model.Model):
          title = model.CharField(max_length=20)
          class Meta:
              permissions = [('view_article','阅读文章'),('','') ] 
      
    • 通过实例化permission添加权限

      from django.contrib.auth.models import Permission,ContentType
      from .models import Article
      
      content_type = ContentType.objects.get_for_model(Article)
      permission = Permission.objects.create(name='阅读文章',codename='view_article',\
                                            content_type=content_type)
      
  • 用户与权限:权限本身不具备效力,必须与用户绑定后才能够起到作用

    • user.has_perm:判断是否具有某个权限(多个权限使用列表)

      user.has_perm('article.add_article') #参数为app_name.codename
      
    • user.get_all_permissions():获取当前用户的所有权限

    • 增加或删除权限:

      from django.contrib.auth.models import Permission,ContentType
      from .models import MyUser
      user = MyUser.objects.get(username='张三')
      content_type = ContentType.objects.get_for_model(Article)
      p1 = Permission.objects.get(codename='add_article') #单条权限
      p2 = Permission.object.filter(content_type=content_type) #某个模型的全部权限
      
      user.user_permissions.add(p1) #添加某个权限,或使用*p批量增加,p为可迭代对象
      user.user_permissions.remove(p1) #删除某条权限,或使用*p批量删除,p为可迭代对象
      
      user.user_permissions.set(p2) #批量增加权限
      user.user_permissions.clear() #清空当前用户的全部权限
      
  • 权限限定装饰器:permission_required

    from django.contrib.auth.decorators import permission_required
    
    @permission_required(['add_article','view_aricle','...'],login_url='/login/',raise_exception=True) 
    def add_article(request):
        pass
        return 
    

    该装饰器首先会判断是否登陆,没有登陆则跳转到login_url,若登陆了则会继承判断是否拥有指定的权限,单个权限使用字符串格式,多个权限使用列表形式,并且必须符合所有权限要求才可通过权限验证,不通过则抛出错误raise_exception=True,之后重定向到403页面

3. 分组

from django.contrib.auth.models import Permission,ContentType,Group
  • 创建分组:group = Group.object.create(group_name)

  • 分组权限:group.permissions:某个分组的权限,多对多关系

    • group.permissions.add():添加权限
    • group.permissions.remove():删除权限
    • group.permissions.clear():清除权限
    • user.get_group_permissions():获取用户所属分组的权限
  • 用户组:将用户分配到拥有各自权限的分组中

    user.MyUser.objects.get(username='zhangsan')
    group = Group.objects.get(id=1)
    user.groups.add(group) #用户添加到分组
    user.groups.remove(group) #用户脱离分组
    user.groups.clear()
    
posted @ 2020-05-08 11:45  言兴  阅读(102)  评论(0)    收藏  举报