Django admin login page verification code (1): common character and arithmetic verification code

1 Introduction

The login interface of django only has user name and password input boxes by default, and there is no additional security protection. If it is used directly in a production environment without a login verification code, it is very dangerous, because an attacker can use a specific program to continuously try to log in until the Get the correct login password, so you must add a login verification code to increase the cost of website attacks.

2. Customize the login page

2.1 Create django project

Use Pycharm to create a Django project, the project structure is as follows:

After the creation is complete, remember to run in the directory where manage.py is located:

python manage.py migrate

2.2 Download plugin

First download the verify.js front-end login verification plug-in:

jQuery verification code plug-in verify.js_home of jQuery-freely share jQuery, html5, css3 plug-in libraries

Unzip to get the following directory structure:

2.3 Copy djano login page template

Create the admin directory in templates ( under the same level directory as manage.py ) :

 Use the following command to view the django package location:

 python -c "import django; print(django.__path__)

Then find contrib\admin\templates\admin\login.html in this directory and copy it to templates/admin .

2.4 Introduce the authentication plug-in

First create a static/admin folder in the same directory as manage.py to save global static files related to management, and then create three directories under this folder: js , css and image .

Find verify.css  in the decompressed plug-in folder , and copy it to the newly created static/admin/css . Then copy all the js files under the js folder of the plug-in folder to static/admin/js . Copy the two sample images to static/admin/image :

 Modify the static file settings in settings :

STATICFILES_DIRS = [
    BASE_DIR / "static",
    "static/admin"
]

Such plugins can be imported in the following ways:

<link rel="stylesheet" href="{% static "css/verify.css"%}">
<script type="text/javascript" src="{% static "js/jquery-1.11.0.min.js"%}"></script>
<script type="text/javascript" src="{% static "js/verify.js" %}"></script>

3. Custom login template

In order to be able to use different verification codes flexibly, Django 's template inheritance (see template inheritance ) is used here to define a login skeleton template and abstract the verification code into a block to achieve flexible replacement.

3.1 Define the login skeleton template

Open the previously copied login.html , find the location shown in the figure, and add a verify_code block:

Then override the extrahead block of the base admin template and import the Verify.js plugin:

 Then change the name of this template to login_base.html as the login skeleton template:

3.2 Add verification code

Create a new login.html template in the templates/admin directory as the real page login template:

In this template override the verify_code block we defined earlier:

login.html

{#继承基础登录模板#}
{% extends "admin/login_base.html" %}
{% load i18n static %}


{#覆盖基础登录模板中的验证码块verify_code#}
{% block verify_code %}
    <div class="form-row">
        <label class="required">验证码:</label>
        <div id="mpanel2"></div>
    </div>
    <script type="text/javascript">
        $('#mpanel2').codeVerify({
            //1为普通字符验证码,2为算数验证码
            type: 1,
            fontSize: '30px',
            codeLength: 6,
            btnId: 'check-btn',
            ready: function () {
            },
            success: function () {
                alert('验证匹配!');
            },
            error: function () {
                alert('验证码不匹配!');
            }
        });
    </script>
{% endblock %}

Effect

 Found that the interface style is confusing, modify and open verify.js , find:

Modify the width in the default parameter to 99% . find again

Modify the variable panelHtml to

  var panelHtml = '<div class="cerify-code-panel"><div class="verify-code"></div><div class="verify-code-area"><input type="text" class="varify-input-code" /><a class="verify-change-code">换一张</a></div></div>';
            

 Open verify.css and find

  modify it to

.cerify-code-panel {
    height: 100%;
    overflow: hidden;
}

.verify-code-area {
    width: 100%;
    justify-items: stretch;
}
.varify-input-code {
    width: 80%;
    height: 25px;
}

.verify-change-code {
    width: 20%;
    color: #337AB7;
    cursor: pointer;
	margin-left: 10px;
    text-align: center;
}

Modified effect

Notice 

When debugging a web page, you must first close the network cache , otherwise refreshing the page will always display the previously cached css file, making debugging difficult. Turn off the network cache in chrome and check Disable cache under the Network Tab  in the debug window, and the same is true for Edge . Remember to close it after debugging, otherwise it will take a lot of traffic.

4. Increase verification code interference

4.1 Security issues

The ordinary verification code of verify.js is very clear and easy to be recognized by OCR , and the content of the verification code can be read through HTML tags, so it is still very insecure, so it is best to change the verification code to use canvas to draw:

4.2 Modify verify.js source code

(1) Modify the constructor

 (2) Modify loadDom

 const panelHtml = '<div class="vertify-code-panel">' +
                '<div class="verify-code-area">' +
                '<canvas class="verify-code" width="200" height="60"  id="verify-code"></canvas>' +
                '<a class="verify-change-code">换一张</a>' +
                '</div>' +
                '<input type="text" class="varify-input-code" />' +
                '</div>';
......

 (3) Add drawing function

Add randNum, randColor, drawbg, drawLine, drawCircle, drawExpression methods to the Code class:

 //定义Code的方法
    Code.prototype = {
        init: function () {
           ......
        },

        //加载页面
        loadDom: function () {
           ......
        },
        /**
         * 产生一个随机数
         * @param min 最小值
         * @param max 最大值
         * @returns {*}
         */
        ranNum: function (min, max) {
            return Math.random() * (max - min) + min;
        },
        /**
         * 返回一个随机颜色 可设置颜色区间
         * @param {number} min [颜色下限]
         * @param {number} max [颜色上限]
         * @return {string} [随机颜色]
         */
        ranColor: function (min, max) {
            const r = this.ranNum(min, max);
            const g = this.ranNum(min, max);
            const b = this.ranNum(min, max);
            return `rgb(${r},${g},${b})`;
        },
        //绘制背景
        drawBg: function (min, max) {
            // 绘制canvas背景
            this.ctx.fillStyle = this.ranColor(min, max);
            // 填充颜色
            this.ctx.fillRect(0, 0, this.ctxW, this.ctxH);
        },
        /**
         * 绘制干扰 圆点
         * @param {number} num [绘制的数量]
         * @param {number} r [圆点半径]
         * @param {number} min [下限]
         * @param {number} max [上线]
         */
        drawCircle: function (num, r, min, max) {
            for (let i = 0; i < num; i++) {
                // 开始绘制 (拿起笔)
                this.ctx.beginPath();
                // context.arc(x,y,r,sAngle,eAngle,counterclockwise); (绘制)
                // x 圆的中心的 x 坐标。
                // y 圆的中心的 y 坐标。
                // r 圆的半径。
                // sAngle 起始角,以弧度计。(弧的圆形的三点钟位置是 0 度)。
                // eAngle 结束角,以弧度计。
                // counterclockwise 可选。规定应该逆时针还是顺时针绘图。False = 顺时针,true = 逆时针。
                this.ctx.arc(this.ranNum(0, this.ctxW), this.ranNum(0, this.ctxH), r, 0, 2 * Math.PI);
                // 填充颜色
                this.ctx.fillStyle = this.ranColor(min, max);
                // 填充
                this.ctx.fill();
                // 闭合绘制 (放开笔)
                this.ctx.closePath();
            }
        },

        /**
         * 绘制干扰 线段
         * @param {number} num [绘制的数量]
         * @param {number} min [下限]
         * @param {number} max [上线]
         */

        drawLine: function (num, min, max) {
            for (let i = 0; i < num; i++) {
                // 开始绘制 (拿起笔)
                this.ctx.beginPath();
                // 绘制开始点
                this.ctx.moveTo(this.ranNum(0, this.ctxW), this.ranNum(0, this.ctxH));
                // 绘制结束点
                this.ctx.lineTo(this.ranNum(0, this.ctxW), this.ranNum(0, this.ctxH));
                this.ctx.strokeStyle = this.ranColor(min, max);
                this.ctx.stroke();
                this.ctx.closePath();
            }
        },
        //绘制算数表达式
        drawExpression: function (expression) {
            const fs = this.randNum(20, 50);
            this.ctx.font = fs + "px Verdana";
            this.ctx.fillStyle = this.randColor(0, 100);
            // x 添加到水平坐标(x)上的值
            // y 添加到垂直坐标(y)上的值
            // 偏移
            for (let i = 0; i < expression.length; i++) {
                const fs = this.randNum(20, 50);
                this.ctx.font = fs + "px Verdana";
                this.ctx.fillStyle = this.randColor(0, 100);
                // 保存绘制的状态
                this.ctx.save();
                // x 添加到水平坐标(x)上的值
                // y 添加到垂直坐标(y)上的值
                // 偏移
                this.ctx.translate(this.ctxW / expression.length * i + this.ctxW / 20, 0);
                // 变换角度
                this.ctx.rotate(this.randNum(-30, 30,) * Math.PI / 180);
                // text 规定在画布上输出的文本。
                // x 开始绘制文本的 x 坐标位置(相对于画布)。
                // y 开始绘制文本的 y 坐标位置(相对于画布)。
                // maxWidth 可选。允许的最大文本宽度,以像素计。
                this.ctx.fillText(expression[i], 0, (this.ctxH + fs) / 2.5, this.ctxW / expression.length);
                // 返回之前保存过的路径状态和属性
                this.ctx.restore();
            }

        //设置验证码
        setCode: function () {
           .......

        },

       ......
    };

(4) Modify the verification code generation method setCode

 //设置验证码
        setCode: function () {
            // 清空canvas
            this.ctx.clearRect(0, 0, this.ctxW, this.ctxH);
            //绘制背景
            this.drawBg(200, 255);
            //绘制干扰线条
            this.drawLine(20, 0, 255);
            //绘制干扰圆点
            this.drawCircle(20, 5, 200, 255);

            const color1Num = Math.floor(Math.random() * 3);
            const color2Num = Math.floor(Math.random() * 5);

            this.htmlDoms.code.css({'background-color': _code_color1[color1Num], 'color': _code_color2[color2Num]});
            this.htmlDoms.code_input.val('');

            this.code_chose = '';

            if (this.options.type === 1) {
                //添加普通验证码字符
                for (let i = 0; i < this.options.codeLength; i++) {
                    //随机选中一个字符
                    const charNum = Math.floor(Math.random() * 52);
                    let char = _code_chars[charNum]
                    const fs = this.randNum(20, 50);
                    this.ctx.font = fs + "px Verdana";
                    this.ctx.fillStyle = this.randColor(0, 100);
                    // 保存绘制的状态
                    this.ctx.save();
                    // x 添加到水平坐标(x)上的值
                    // y 添加到垂直坐标(y)上的值
                    // 偏移
                    this.ctx.translate(this.ctxW / this.options.codeLength * i + this.ctxW / 20, 0);
                    // 变换角度
                    this.ctx.rotate(this.randNum(-30, 30,) * Math.PI / 180);
                    // text 规定在画布上输出的文本。
                    // x 开始绘制文本的 x 坐标位置(相对于画布)。
                    // y 开始绘制文本的 y 坐标位置(相对于画布)。
                    // maxWidth 可选。允许的最大文本宽度,以像素计。
                    this.ctx.fillText(char, 0, (this.ctxH + fs) / 2.5, this.ctxW / this.options.codeLength);
                    // 返回之前保存过的路径状态和属性
                    this.ctx.restore();
                    //添加到选定的验证码中
                    this.code_chose += _code_chars[charNum];

                }
            } else {		//算法验证码
                let num1 = Math.floor(Math.random() * this.options.figure);
                let num2 = Math.floor(Math.random() * this.options.figure);
                //随机选择一种算数
                if (this.options.arith === 0) {
                    var tmparith = Math.floor(Math.random() * 3);
                }
                //要绘制的序列
                let code = []
                switch (tmparith) {
                    case 1 :
                        //加法
                        this.code_chose = parseInt(String(num1)) + parseInt(String(num2));
                        code.push(String(num1))
                        code.push("+")
                        code.push(String(num2))
                        code.push("=")
                        code.push('?')
                        this.drawExpression(code)
                        break;
                    case 2 :
                        //减法, 确保减法不出现负数
                        if (parseInt(String(num1)) < parseInt(String(num2))) {
                            var tmpnum = num1;
                            num1 = num2;
                            num2 = tmpnum;
                        }
                        this.code_chose = parseInt(String(num1)) - parseInt(String(num2));
                        code.push(String(num1))
                        code.push("-")
                        code.push(String(num2))
                        code.push("=")
                        code.push('?')
                        this.drawExpression(code)
                        break;
                    default :
                        //乘法
                        this.code_chose = parseInt(String(num1)) * parseInt(String(num2));
                        code.push(String(num1))
                        code.push("×")
                        code.push(String(num2))
                        code.push("=")
                        code.push('?')
                        this.drawExpression(code)
                        break;
                }
            }
        },

(5) Modify verify.css

/*常规验证码*/
.verify-code {
    text-align: center;
    cursor: pointer;
    border: 1px solid #ddd;
}

.verify-code-panel {
    height: 100%;
    overflow: hidden;
}

.verify-code-area {
    width: 100%;
    display: flex;
    justify-items: stretch;
    justify-content: flex-start;
    align-items: center;
    flex-direction: row;
    margin-bottom: 5px;
}

.varify-input-code {
    width: 100%;
    height: 25px;
}

.verify-change-code {
    width: 20%;
    color: #337AB7;
    cursor: pointer;
    margin-left: 10px;
    text-align: center;
}
......

4.3 Effect

(1) Ordinary character verification code

(2) Arithmetic verification code

Guess you like

Origin blog.csdn.net/anbuqi/article/details/120575807