1. 程式人生 > 實用技巧 >https協議+三種資料庫實現限流+安全防護

https協議+三種資料庫實現限流+安全防護

1 http 和 https

1.1 http 和 https 的區別

  • http 明文(報文明文)可以篡改
  • https 加密(報文密文)

1.2 如何訪問https

  • pip install django-sslserver
  • 終端中 python manage.py runsslserver 啟動專案

2 專案中的限流操作

  • 用來儲存存ip地址,明確使用者身份,可以用來防爬蟲
  • 使用場景:釘釘機器人傳送簡訊驗證,不能在5秒內,向服務端發起三次以上的請求

2.1 mysql實現

  • mysql中存兩個資料,ip+時間戳
2.2.1 裝飾器
  • 裝飾器實際上是一個閉包,延伸了作用域的方法,可以再在內部訪問一個方法外部的變數(外部的變數是全域性變數)。
  • 用法:語法常
from django.http import HttpResponse
from userapp.models import InfoVisit, BlackList


#  the Decorator to make sure the only one ip to visit us.(mysql)

def visit_info_mysql_decorator(func):
    def inner(request, *args, **kwargs):
        # try:

        # don't think about that nginx may course 127.0.0.1:8000 at this time.

        #     if request.META['HTTP_X_FORWARDED_FOR']:
        #         ip = request.META['HTTP_X_FORWARDED_FOR']
        # except Exception as e:
        #      print('ok')

        ip = request.META['REMOTE_ADDR']
        obj = InfoVisit.objects.filter(net_ip=ip, is_delete=False).first()
        if obj:
            visit_time = obj.visit_time
            import datetime
            timediff = datetime.datetime.now() - visit_time
            if timediff.total_seconds() > 5:
                InfoVisit.objects.create(net_ip=ip, is_delete=False)
                obj.is_delete = True
                obj.save()
            else:
                return HttpResponse('惡意訪問')
        else:
            print('first visit')
            InfoVisit.objects.create(net_ip=ip, is_delete=False)
        return func(request, *args, **kwargs)
    return inner
2.2.2 中介軟體
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin
from userapp.models import InfoVisit, BlackList


# 此頁面所有攔截都是針對釘釘機器人,使用者小於5秒訪問第二次的!

# mysql -----------------middleware

class VisitInfoMysqlMiddleWare(MiddlewareMixin):
    def process_request(self, request):
        path = request.path_info
        print(123,path)
        if path in ('/user/dingding/'):
            ip = request.META['REMOTE_ADDR']
            print(234, ip)
            obj = InfoVisit.objects.filter(net_ip=ip, is_delete=False).first()
            if obj:
                visit_time = obj.visit_time
                import datetime
                timediff = datetime.datetime.now() - visit_time
                if timediff.total_seconds() > 5:
                    InfoVisit.objects.create(net_ip=ip, is_delete=False)
                    obj.is_delete = True
                    obj.save()
                else:
                    return HttpResponse('惡意訪問')
            else:
                print('first visit')
                InfoVisit.objects.create(net_ip=ip, is_delete=False)

2.2 redis 實現

  • redis 存key-values,判斷key存不存在,不需要存value(存記憶體)
2.2.1 裝飾器
from django.http import HttpResponse
from userapp.models import InfoVisit

#  the Decorator to make sure the only one ip to visit us.(redis)

import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=5)
def visit_info_redis_decorator(func):
    def inner(request, *args, **kwargs):
        # try:
        # don't think about that nginx may course 127.0.0.1:8000 at this time.

        #     if request.META['HTTP_X_FORWARDED_FOR']:
        #         ip = request.META['HTTP_X_FORWARDED_FOR']
        # except Exception as e:
        #      print('ok')

        ip = request.META['REMOTE_ADDR']
        value = r.get(ip)
        if value:
            return HttpResponse('惡意訪問')
        else:
            r.set(ip, 'value')
            r.expire(ip, 5)
            # print('first visit')
        return func(request, *args, **kwargs)
    return inner
2.2.2 中介軟體
# -*- coding: utf-8 -*-
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin
from userapp.models import InfoVisit
# 此頁面所有攔截都是針對釘釘機器人,使用者小於5秒訪問第二次的!


# redis ---------------------middleware
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=5)
class VisitInfoRedisMiddleWare(MiddlewareMixin):
    def process_request(self, request):
        path = request.path_info
        # print(123,path)
        if path in ('/user/dingding/'):
            ip = request.META['REMOTE_ADDR']
            value = r.get(ip)
            if value:
                return HttpResponse('惡意訪問')
            else:
                r.set(ip, 'value')
                r.expire(ip, 5)

2.3 mongodb 實現

  • mongodb中的過期索引,只能實現60秒一次的檢測功能,所以並不準確,是mongodb對限流操作實現的一大弊端,可以通過以下程式碼自行檢驗
  • 還遇到了一個坑,就是翻牆軟體開啟的時候,不能用代理下載,訪問國外網站!
  • 安裝的時候,注意安全軟體關閉或者給予許可權
  • windows + r =========> services.msc 可以檢視啟動
# 指定bin目錄下檢測啟動
C:\Program Files\MongoDB\Server\4.0\bin>mongo
MongoDB shell version v4.0.4
connecting to: mongodb://127.0.0.1:27017
Implicit session: session { "id" : UUID("e22f6748-06d6-446b-b3dc-46bc7b9e1cde") }
MongoDB server version: 4.0.4
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
        http://docs.mongodb.org/
Questions? Try the support group
        http://groups.google.com/group/mongodb-user
Server has startup warnings:
2020-12-18T23:33:51.408+0800 I CONTROL  [initandlisten]
2020-12-18T23:33:51.408+0800 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2020-12-18T23:33:51.408+0800 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2020-12-18T23:33:51.408+0800 I CONTROL  [initandlisten]
---
Enable MongoDB's free cloud-based monitoring service, which will then receive and display
metrics about your deployment (disk utilization, CPU, operation statistics, etc).

The monitoring data will be available on a MongoDB website with a unique URL accessible to you
and anyone you share the URL with. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.

To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
# 出現 < 代表安裝成功
# 可以進行簡單的計算
> 2+3
5
# 存在runoob即使用,不存在就建立(詳細參考P2的mongodb攻略)
> use runoob
switched to db runoob
> db
runoob
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
> db
runoob
  • 因為不夠準確,推薦不使用過期索引!以下程式碼採用了過期索引,僅供參考
2.2.1 裝飾器
# -*- coding: utf-8 -*-
# 此頁面所有攔截都是針對釘釘機器人,小於5秒訪問第二次的!

from django.http import HttpResponse
from userapp.models import InfoVisit

#  the Decorator to make sure the only one ip to visit us.(mongodb)

import pymongo
client = pymongo.MongoClient(host='localhost', port=27017)
db = client['runoob']
set = db['sub']

def visit_info_mongo_decorator(func):

    import datetime
    def inner(request, *args, **kwargs):
        ip = request.META['REMOTE_ADDR']
        value = set.find_one({'ip': ip})
        print(123,value)
        if value:
            return HttpResponse('惡意訪問')
        else:
            timestamp = datetime.datetime.now()
            print(123123)
            set.ensure_index('Date', expireAfterSeconds=10)
            set.insert({'ip': ip, 'Date': timestamp})
            print('first visit')
        return func(request, *args, **kwargs)
    return inner
2.2.2 中介軟體
# -*- coding: utf-8 -*-
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin
from userapp.models import InfoVisit
# 此頁面所有攔截都是針對釘釘機器人,使用者小於5秒訪問第二次的!


# mongo ---------------------middleware
import pymongo
client = pymongo.MongoClient(host='localhost', port=27017)
db = client['runoob']
set = db['sub']
class VisitInfoMongoMiddleWare(MiddlewareMixin):
    def process_request(self, request):
        path = request.path_info
        if path in ('/user/dingding/'):
            import datetime
            ip = request.META['REMOTE_ADDR']
            value = set.find_one({'ip': ip})
            print(123, value)
            if value:
                return HttpResponse('惡意訪問')
            else:
                timestamp = datetime.datetime.now()
                print(123123)
                set.ensure_index('Date', expireAfterSeconds=10)
                # 沒辦法實時監測,因為一分鐘才檢查一次
                set.insert({'ip': ip, 'Date': timestamp})
                print('first visit')

2.4 總結

  • 限流最好用的就是redis,和mysql結合實現安全防護,詳看本章 4

3 在函式中呼叫的方式

3.1 裝飾器

from django.utils.decorators import method_decorator
class DingDingView(APIView):
    @method_decorator(visit_info_mysql_decorator, name='post')
    @method_decorator(visit_info_redis_decorator, name='post')
    @method_decorator(visit_info_mongo_decorator, name='post')
    def post(self, request):
        username = request.data.get('username')
        # 這裡實現一個場景,就是隻有在登入的情況下才可以傳送驗證碼(本來想使用token,但是累覺沒必要,前端輸入的同時,表單繫結記錄下來就好了!何必難為自己T.T,做一次小測試,具體情況具體分析就可以了)
        num = message_code()
        dingding_robot(num)
        request.session['num'] = num
        request.session.set_expiry(300)
        if not request.session.session_key:
            request.session.create()
        return Response(
            {'msg': '傳送成功', 'code':200, 'num':num, 'session_key': request.session.session_key}
        )

3.2 中介軟體

  • settings中註冊
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # mysql middleware
    'userapp.middlewares.VisitInfoMysqlMiddleWare',
    # redis middleware
    'userapp.middlewares.VisitInfoRedisMiddleWare',
    # mongo middleware
    'userapp.middlewares.VisitInfoMongoMiddleWare'
    ]

4 安全防護

  • 安全防護是基於限流操作的,為了防止爬蟲,我們要設定安全防護。但是也要注意到的是,同一臺電腦,可以由多個使用者訪問,所以設定黑名單的時候,要設定使用者名稱
  • 在同一個區域網內,可以通過ip訪問不同的伺服器,啟動程式碼是
python manage.py runserver 0.0.0.0:8000
# 區域網
  • 設計表結構的時候,採用了唯一索引:唯一索引作用是排重,同時減少select語句的io操作,針對username做唯一索引,ip一樣,使用者名稱一樣才針對查詢。

4.1 黑名單表設計

  • 此處採用了 django 表結構的設計
class BlackList(models.Model):
    username = models.CharField(max_length=30)
    net_ip = models.CharField(max_length=40)
    lock_time = models.IntegerField(default=3600)

    class Meta:
        index_together = ['username', 'net_ip']
        unique_together = ['username', 'net_ip']
        db_table = 'black_list'
        
# 聯合索引必須一起新增,否則要報錯

4.2 redis + mysql實現操作

  • redis 進行限流
  • mysql儲存黑名單資訊
4.2.1 middleware
# redis ---------------------middleware
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=5, decode_responses=True)
class VisitInfoRedisMiddleWare(MiddlewareMixin):
    def process_request(self, request):
        path = request.path_info
        if path in ('/user/dingding/'):
            username = request.data.get('username')
            ip = request.META['REMOTE_ADDR']
            # 為空要報錯哦
            if BlackList.objects.filter(username=username, net_ip=ip):
                return HttpResponse('賬號已經被停用')
            else:
                value = r.get(ip)
                print('this is value', value)
                # value------->type:str
                if value is not None and int(value) > 2:
                    BlackList.objects.create(username=username, net_ip=ip)
                    return HttpResponse('惡意訪問,您即將被封號')
                else:
                    r.incrby(ip, 1)
                    # 這裡我思考了一個問題,就是自增會不會重新整理過期時間,實際上是不會的,查詢資料發現
                    # 要重新整理過期時間還需要新的解決方案,符合邏輯!
                    r.expire(ip, 10)
4.2.2 director
#  the Decorator to make sure the only one ip to visit us.(redis)

import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=5, decode_responses=True)
def visit_info_redis_decorator(func):
    def inner(request, *args, **kwargs):
        username = request.data.get('username')
        ip = request.META['REMOTE_ADDR']
        # 為空要報錯哦
        if BlackList.objects.filter(username=username, net_ip=ip):
            return HttpResponse('賬號已經被停用')
        else:
            value = r.get(ip)
            print('this is value', value)
            # value------->type:str
            if value is not None and int(value) > 2:
                BlackList.objects.create(username=username, net_ip=ip)
                return HttpResponse('惡意訪問,您即將被封號')
            else:
                r.incrby(ip, 1)
                # 這裡我思考了一個問題,就是自增會不會重新整理過期時間,實際上是不會的,查詢資料發現
                # 要重新整理過期時間還需要新的解決方案,符合邏輯!
                r.expire(ip, 10)
        return func(request, *args, **kwargs)
    return inner