富文本編輯框和防止xss攻擊

1、後臺管理頁面構建

 一、建立後臺管理url

urlpatterns = [
    ...
    # 後臺管理url
    re_path("cn_backend/$", views.cn_backend),
    re_path("cn_backend/add_article/$", views.add_article),
    ...
]

二、構造視圖函數

from django.contrib.auth.decorators import login_required  # 裝飾器:login_required()

@login_required
def cn_backend(request):
    """
    後臺管理頁面
    :param request:
    :return:
    """
    # 當前登陸人發佈過得文章列表
    article_list = models.Article.objects.filter(user=request.user)

    return render(request, "backend/backend.html", locals())

@login_required
def add_article(request):
    """
    後臺管理添加文章視圖函數
    :param request:
    :return:
    """
    if request.method == "POST":
        title = request.POST.get("title")
        content = request.POST.get("content")
        # 生成article對象
        models.Article.objects.create(title=title, content=content, user=request.user)
        return redirect("/cn_backend/")

    return render(request, "backend/add_article.html")

三、在templates目錄下建立backend子目錄,構建模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>博客後臺管理 - 博客園</title>
    <!-- 引入 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="/static/blog/bootstrap-3.3.7/css/bootstrap.css">
    <!-- jQuery (Bootstrap 的全部 JavaScript 插件都依賴 jQuery,因此必須放在前邊) -->
    <script src="/static/js/jquery-3.3.1.js"></script>
    <!-- 引入 Bootstrap 核心 JavaScript 文件 -->
    <script src="/static/blog/bootstrap-3.3.7/js/bootstrap.js"></script> <!--依賴jquery-->
    <link rel="stylesheet" href="/static/blog/css/backend.css">
</head>
<body>

<div class="header">
    <p class="title">
        後臺管理

        <a class="info" href="/logout/">註銷</a>
        <span class="info"><span class="glyphicon glyphicon-user"></span>&nbsp;&nbsp;{{ request.user.username }}</span>
    </p>
</div>


<div class="container">
    <div class="col-md-3">
        <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
            <div class="panel panel-default">
                <div class="panel-heading" role="tab" id="headingOne">
                    <h4 class="panel-title">
                        <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"
                           aria-expanded="true" aria-controls="collapseOne">
                            操做
                        </a>
                    </h4>
                </div>
                <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">
                    <div class="panel-body">
                        <!--添加文章url跳轉-->
                        <p><a href="/cn_backend/add_article/">添加文章</a></p>
                    </div>
                </div>
            </div>

        </div>
    </div>
    <div class="col-md-9">

        <div>
            <!-- Nav tabs -->
            <ul class="nav nav-tabs" role="tablist">
                <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab"
                                                          data-toggle="tab">文章</a></li>
                <li role="presentation"><a href="#profile" aria-controls="profile" role="tab"
                                           data-toggle="tab">日記</a></li>
                <li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">眼鏡</a>
                </li>
                <li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">相冊</a>
                </li>
            </ul>

            <!-- Tab panes -->
            <div class="tab-content">
                <div role="tabpanel" class="tab-pane active" id="home">
                    {% block content %}

                    {% endblock %}
                </div>
                <div role="tabpanel" class="tab-pane" id="profile">

                    <img src="/static/blog/img/meinv2.jpg" alt="">
                    <img src="/static/blog/img/meinv3.jpg" alt="">
                    <img class="pull-right" src="/static/blog/img/meinv.jpg" alt="">
                </div>
                <div role="tabpanel" class="tab-pane" id="messages">

                    <img width="180" height="180" src="/static/blog/img/hashiqi2.jpg" alt="">

                    <img width="180" height="180" src="/static/blog/img/dogg4.jpg" alt="">
                    <img width="180" height="180" src="/static/blog/img/linhaifeng.jpg" alt=""><br>
                    <img width="180" height="180" src="/static/blog/img/dogg3.jpeg" alt="">
                    <img width="180" height="180" src="/static/blog/img/dogge2.jpg" alt="">

                    <img width="180" height="180" src="/static/blog/img/dogg5.jpg" alt="">

                </div>
                <div role="tabpanel" class="tab-pane" id="settings">

                </div>
            </div>

        </div>

    </div>
</div>

</body>
</html>
backend/base.html

  擴展模板:javascript

{% extends 'backend/base.html' %}

{% block content %}
    <div class="article_list small">

        <table class="table table-hover table-striped">
            <thead>
            <th>標題</th>
            <th>評論數</th>
            <th>點贊數</th>
            <th>操做</th>
            <th>操做</th>
            </thead>
            <tbody>
            {% for article in article_list %}
                <tr>
                    <td>{{ article.title }}</td>
                    <td>{{ article.comment_count }}</td>
                    <td>{{ article.up_count }}</td>
                    <td><a href="">編輯</a></td>
                    <td><a href="">刪除</a></td>
                </tr>
            {% endfor %}

            </tbody>
        </table>
    </div>
{% endblock %}
backend/backend.html
{% extends 'backend/base.html' %}

{% block content %}
    {# 提交form表單 #}
    <form action="" method="post">
        {% csrf_token %}
        <div class="add_article">
            <div class="alert-success text-center">添加文章</div>

            <div class="add_article_region">
                <div class="title form-group">
                    <label for="">標題</label>
                    <div>
                        <input type="text" name="title">
                    </div>
                </div>

                <div class="content form-group">
                    <label for="">內容(Kindeditor編輯器,不支持拖放/粘貼上傳圖片) </label>
                    <div>
                        <textarea name="content" id="article_content" cols="30" rows="10"></textarea>
                    </div>
                </div>

                <input type="submit" class="btn btn-default">

            </div>
        </div>
    </form>
    <script charset="utf-8" src="/static/blog/kindeditor/kindeditor-all.js"></script>

    <script>
        KindEditor.ready(function (K) {
            window.editor = K.create('#article_content', {   // 找到這個標籤就會引入文本編輯器
                width: "800",
                height: "600",
                resizeType: 0,
                uploadJson: "/upload/",
                extraFileUploadParams: {
                    csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
                },
                filePostName: "upload_img"
            });
        });
    </script>
{% endblock %}
backend/add_article.html

  在/static/blog/css/backend.css構建模板樣式:php

* {
    margin: 0;
    padding: 0;
}

.header {
    width: 100%;
    height: 60px;
    background-color: #333333;
    line-height: 60px;
}

.header .title {
    color: white;
    font-size: 20px;
    margin-left: 20px;
    font-weight: 100;
}

.header .title .info {
    float: right;
    margin-right: 20px;
    font-size: 14px;
    text-decoration: none;
    color: white;

}

#accordion {
    margin-top: 20px;
}

.article_list {
    padding: 30px;
}

.alert-success {
    margin: 8px 0;
}

[name='title'] {
    width: 100%;
}

.tab-pane {
    margin-top: 40px;
    margin-left: 40px;
}

.tab-pane img {

    border-radius: 80%;
    margin: 20px 20px;
}
backend.css

四、展現效果

  

2、編輯器引入和使用

一、kindeditor編輯器

  kindeditor編輯器官網和文檔:http://kindeditor.net/doc.phpcss

  kindeditor編輯器下載頁面: http://www.kindsoft.net/down.phphtml

(1)引入編輯器java

  在頁面中添加如下腳本python

<script charset="utf-8" src="/editor/kindeditor.js"></script>
<script charset="utf-8" src="/editor/lang/zh-CN.js"></script>
<script>
        KindEditor.ready(function(K) {
                window.editor = K.create('#editor_id');
        });
</script>

  注意將下載好的文件中的kindeditor.js文件放入項目目錄中引入。在K.create()查找的標籤引入文本編輯器。jquery

(2)配置編輯器參數數據庫

  經過K.create函數的第二個參數,能夠對編輯器進行配置,具體參數請參考 編輯器初始化參數 。django

  width:編輯器的寬度,能夠設置px或%,比textarea輸入框樣式表寬度優先度高。json

  height:編輯器的高度,只能設置px,比textarea輸入框樣式表高度優先度高。

  resizeType:2或1或0,2時能夠拖動改變寬度和高度,1時只能改變高度,0時不能拖動。

  uploadJson:指定上傳文件的服務器端程序。

  extraFileUploadParams:上傳圖片、Flash、視音頻、文件時,支持添加別的參數一併傳到服務器。

  filePostName:指定上傳文件form名稱。

二、在博客項目中引入編輯器

  在backend/add_article.html中添加編輯器:

{% extends 'backend/base.html' %}

{% block content %}
    {# 提交form表單 #}
    <form action="" method="post">
        ...
    </form>
<script charset="utf-8" src="/static/blog/kindeditor/kindeditor-all.js"></script> <script> KindEditor.ready(function (K) { window.editor = K.create('#article_content', { // 找到這個標籤就會引入文本編輯器 width: "800", height: "600", resizeType: 0, uploadJson: "/upload/", extraFileUploadParams: { csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() }, filePostName: "upload_img" }); }); </script> {% endblock %}

  編輯器顯示效果:

  

三、編輯器上傳功能

(1)添加upload路由

urlpatterns = [
    ...
    path('upload/', views.upload),
    ...
]

(2)直接使用編輯器的文件上傳功能,發現forbidden報錯。

  這是因爲文本編輯器發送文件時使用的POST請求,被django攔截,所以須要在數據中攜帶一個csrf_token。

  這裏使用extraFileUploadParams(上傳圖片、Flash、視音頻、文件時,支持添加別的參數一併傳到服務器)來實現攜帶csrf_token參數。

<script>
    KindEditor.ready(function (K) {
        window.editor = K.create('#article_content', {   // 找到這個標籤就會引入文本編輯器
            width: "800",
            height: "600",
            resizeType: 0,
            uploadJson: "/upload/",
            extraFileUploadParams: {
                csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
            }
        });
    });
</script>

  在這裏它添加的是一對鍵值,鍵是csrfmiddlewaretoken,值是取的頁面中屬性name爲csrfmiddlewaretoken對應的值。

(3)添加upload視圖函數

def upload(request):
    print(request.FILES)  
    return HttpResponse("ok")

  request.FILES中的鍵來自於表單中的<input type="file" name="" />的name值,request.FILES中的值均爲UploadedFile類文件對象。

  上傳文件後輸出以下:

<MultiValueDict: {'imgFile': [<InMemoryUploadedFile: dogg4.jpg (image/jpeg)>]}>

(4)指定修改上傳文件的鍵

  filePostName:指定上傳文件form名稱

<script>
    KindEditor.ready(function (K) {
        window.editor = K.create('#article_content', {   // 找到這個標籤就會引入文本編輯器
            width: "800",
            height: "600",
            resizeType: 0,
            uploadJson: "/upload/",
            extraFileUploadParams: {
                csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
            },
            filePostName: "upload_img"
        });
    });
</script>

  修改後,上傳文件輸出以下:

<MultiValueDict: {'upload_img': [<InMemoryUploadedFile: dogg4.jpg (image/jpeg)>]}>

四、用戶上傳文件處理

(1)以前將用戶上傳的頭像是存放在/media/avatars目錄下,如今再建立一個/media/add_article_img目錄來存放上傳的圖片文件。

(2)用戶上傳的文件指定到了upload視圖函數,構建upload來下載圖片到本地服務器

def upload(request):
    print(request.FILES)  # <MultiValueDict: {'upload_img': [<InMemoryUploadedFile: dogg4.jpg (image/jpeg)>]}>
    img = request.FILES.get("upload_img")  # 文件對象
    print(img.name)  # 文件名

    path = os.path.join(settings.MEDIA_ROOT, "add_article_img", img.name)
    with open(path, "wb") as f:
        for line in img:
            f.write(line)

    return HttpResponse("ok")

  上傳完成後,在服務器查看下載好的圖片文件:

  

五、處理用戶上傳完成後信息顯示

  完成圖片上傳後,瀏覽器顯示的信息以下:

  

  須要在文本編輯器中顯示上傳的圖片,須要繼續構建upload函數,下載完文件後把對應的路徑交給文本編輯器:

def upload(request):
    print(request.FILES)  # <MultiValueDict: {'upload_img': [<InMemoryUploadedFile: dogg4.jpg (image/jpeg)>]}>
    img = request.FILES.get("upload_img")  # 文件對象
    print(img.name)  # 文件名

    path = os.path.join(settings.MEDIA_ROOT, "add_article_img", img.name)
    with open(path, "wb") as f:
        for line in img:
            f.write(line)

    # 下載完文件後,把對應的路徑交給文本編輯器,文本編輯器構建img標籤,咱們就可以看到這個圖片了。
    response = {
        "error": 0,
        "url": "/media/add_article_img/%s" % img.name,   # 可以預覽這張圖片的路徑
    }

    # 將這個字典交還給文本編輯器,作json序列化
    import json
    return HttpResponse(json.dumps(response))

  注意:

(1)顯示圖片的思路是:下載完文件後,把對應的路徑交給文本編輯器,文本編輯器構建img標籤,就可以看到這個圖片了。

  構建的字典有兩對鍵值:error=0表明沒有發生任何錯誤;

  url對應的路徑不是圖片存儲的絕對路徑而是可以訪問查看的路徑:127.0.0.1:8000/media/add_article_img/dog3.jpg

(2)編輯器要求返回的數據必須是json數據,所以須要將構建好的字典作json序列化後回傳

# 將這個字典交還給文本編輯器,作json序列化
import json
return HttpResponse(json.dumps(response))

(3)檢查驗證:

  

  檢查html:

  

3、文章摘要的保存

一、修改add_article視圖,截取文章內容做爲文章摘要

@login_required
def add_article(request):
    """
    後臺管理添加文章視圖函數
    :param request:
    :return:
    """
    if request.method == "POST":
        title = request.POST.get("title")
        content = request.POST.get("content")

        desc = content[0:150]  # 截取文章內容做爲摘要

        # 生成article對象
        models.Article.objects.create(title=title, desc=desc, content=content, user=request.user)
        return redirect("/cn_backend/")

    return render(request, "backend/add_article.html")

  這種狀況下,若是文章內容是純文本,摘要內容是會正常顯示的。但若是文章內容是標籤字符串,包含有各類樣式,那就會出現問題。

二、截取摘要問題分析

(1)index.html中文章摘要添加safe過濾器

  若是不添加safe過濾器,那首頁顯示的文章摘要將以標籤字符串的形式展現:

  

  添加過濾器後再也不對字符串進行轉義:

<span class="media-right">
    {# 文章摘要 #}
    {{ article.desc|safe }}
</span>

(2)添加|safe過濾器後,摘要樣式產生了錯亂

   

  這是因爲截取的150的標籤字符串沒有閉合致使。所以不該該是截取content,而是應該截取content中的文本前150個。

三、Beautiful Soup庫(bs4)介紹

  HTML文件其實就是由一組尖括號構成的標籤組織起來的,每一對尖括號形式一個標籤,標籤之間存在上下關係,造成標籤樹;所以能夠說Beautiful Soup庫是解析、遍歷、維護「標籤樹」的功能庫

  安裝Beautiful Soup:

pip3 install bs4

  Beautiful Soup支持Python標準庫中的HTML解析器,還支持一些第三方的解析器,這裏使用Python默認支持的解析器:html.parser。

  使用方法:

from bs4 import BeautifulSoup

s = "<h1>hello</h1><span>123</span>"

soup = BeautifulSoup(s, "html.parser")

print(soup.text)   # 輸出:hello123

  能夠看到輸出的是標籤內的文本。

四、利用bs4來解決摘要截取問題

from bs4 import BeautifulSoup

@login_required
def add_article(request):
    """
    後臺管理添加文章視圖函數
    :param request:
    :return:
    """
    if request.method == "POST":
        title = request.POST.get("title")
        content = request.POST.get("content")

        soup = BeautifulSoup(content, "html.parser")   # 標籤字符串是content

        # 構建摘要數據,獲取標籤字符串的文本前150個符號
        desc = soup.text[0:150]+"..."

        # 生成article對象
        models.Article.objects.create(title=title, desc=desc, content=content, user=request.user)
        return redirect("/cn_backend/")

    return render(request, "backend/add_article.html")

  再次添加文章,注意要將編輯器切換到html標籤預覽模式,再粘貼標籤字符串。

  首頁摘要顯示效果:

  

五、利用bs4模塊防護xss攻擊

(1)利用bs4過濾非法標籤:

from bs4 import BeautifulSoup

s = "<h1>hello</h1><span>123</span><script>alert(123)</script>"
# Python默認支持的解析器:html.parser
soup = BeautifulSoup(s, "html.parser")
# print(soup.text)

print(soup.find_all())   # 獲取全部的標籤對象:[<h1>hello</h1>, <span>123</span>, <script>alert(123)</script>]

for tag in soup.find_all():
    print(tag.name)
    """
    h1
    span
    script
    """
    if tag.name == "script":
        # 非法標籤
        tag.decompose()   # 從soup中刪除標籤

print(str(soup))   # <h1>hello</h1><span>123</span>

  注意:

 1)每一個tag都有本身的名字,經過 .name 來獲取;

 2)Tag.decompose() 方法將當前節點移除文檔樹並徹底銷燬。

 3)find_all( name , attrs , recursive , string , **kwargs ),find_all() 方法搜索當前tag的全部tag子節點,並判斷是否符合過濾器的條件.

(2)應用在add_article視圖函數中

from bs4 import BeautifulSoup

@login_required
def add_article(request):
    """
    後臺管理添加文章視圖函數
    :param request:
    :return:
    """
    if request.method == "POST":
        title = request.POST.get("title")
        content = request.POST.get("content")

        soup = BeautifulSoup(content, "html.parser")   # 標籤字符串是content

        # 防止xss攻擊,過濾script標籤
        for tag in soup.find_all():
            print(tag.name)
            if tag.name == "script":
                tag.decompose()

        # 構建摘要數據,獲取標籤字符串的文本前150個符號
        desc = soup.text[0:150]+"..."

        # 生成article對象
        models.Article.objects.create(title=title, desc=desc, content=str(soup), user=request.user)
        return redirect("/cn_backend/")

    return render(request, "backend/add_article.html")

  注意保存的content再也不是原始的content而是處理以後的標籤字符串。

(3)檢查驗證

  建立新文章:

  

  檢查script標籤是否清除:

  

  數據庫查看content內容:

  

相關文章
相關標籤/搜索