Django组件-用户认证

1 Auth模块

Auth模块是Django自带的用户认证模块:

我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这还真是个麻烦的事情呢。

Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统--auth,它默认使用 auth_user 表来存储用户数据。

2 auth模块常用方法

from django.contrib import auth

authenticate()

提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。

如果认证成功(用户名和密码正确有效),便会返回一个 User 对象。

authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。

用法:

user = authenticate(username='usernamer',password='password')

login(HttpRequest, user)

该函数接受一个HttpRequest对象,以及一个经过认证的User对象。

该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据。

用法:

from django.contrib.auth import authenticate, login
   
def my_view(request):
  username = request.POST['username']
  password = request.POST['password']
  user = authenticate(username=username, password=password)
  if user is not None:
    login(request, user)
    # Redirect to a success page.
    ...
  else:
    # Return an 'invalid login' error message.
    ...

logout(request)

该函数接受一个HttpRequest对象,无返回值。

当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。

用法:

from django.contrib.auth import logout
   
def logout_view(request):
  logout(request)
  # Redirect to a success page.

is_authenticated()

用来判断当前请求是否通过了认证。

用法:

def my_view(request):
  if not request.user.is_authenticated():
    return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))

login_requierd()

auth 给我们提供的一个装饰器工具,用来快捷的给某个视图添加登录校验。

用法:

from django.contrib.auth.decorators import login_required
      
@login_required
def my_view(request):
  ...

若用户没有登录,则会跳转到django默认的 登录URL '/accounts/login/ ' 并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。

如果需要自定义登录的URL,则需要在settings.py文件中通过LOGIN_URL进行修改。

示例:

LOGIN_URL = '/login/'  # 这里配置成你项目登录页面的路由

create_user()

auth 提供的一个创建新用户的方法,需要提供必要参数(username、password)等。

用法:

from django.contrib.auth.models import User
user = User.objects.create_user(username='用户名',password='密码',email='邮箱',...)

create_superuser()

auth 提供的一个创建新的超级用户的方法,需要提供必要参数(username、password)等。

用法:

from django.contrib.auth.models import User
user = User.objects.create_superuser(username='用户名',password='密码',email='邮箱',...)

check_password(password)

auth 提供的一个检查密码是否正确的方法,需要提供当前请求用户的密码。

密码正确返回True,否则返回False。

用法:

ok = user.check_password('密码')

set_password(password)

auth 提供的一个修改密码的方法,接收 要设置的新密码 作为参数。

注意:设置完一定要调用用户对象的save方法!!!

用法:

user.set_password(password='')
user.save()
@login_required
def set_password(request):
    user = request.user
    err_msg = ''
    if request.method == 'POST':
        old_password = request.POST.get('old_password', '')
        new_password = request.POST.get('new_password', '')
        repeat_password = request.POST.get('repeat_password', '')
        # 检查旧密码是否正确
        if user.check_password(old_password):
            if not new_password:
                err_msg = '新密码不能为空'
            elif new_password != repeat_password:
                err_msg = '两次密码不一致'
            else:
                user.set_password(new_password)
                user.save()
                return redirect("/login/")
        else:
            err_msg = '原密码输入错误'
    content = {
        'err_msg': err_msg,
    }
    return render(request, 'set_password.html', content)
一个修改密码的简单示例

User对象的属性

User对象属性:username, password

is_staff : 用户是否拥有网站的管理权限.

is_active : 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录。

3 扩展默认的auth_user表

这内置的认证系统这么好用,但是auth_user表字段都是固定的那几个,我在项目中没法拿来直接使用啊!

比如,我想要加一个存储用户手机号的字段,怎么办?

聪明的你可能会想到新建另外一张表然后通过一对一和内置的auth_user表关联,这样虽然能满足要求但是有没有更好的实现方式呢?

答案是当然有了。

我们可以通过继承内置的 AbstractUser 类,来定义一个自己的Model类。

这样既能根据项目需求灵活的设计用户表,又能使用Django强大的认证系统了。

from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
    """
    用户信息表
    """
    nid = models.AutoField(primary_key=True)
    phone = models.CharField(max_length=11, null=True, unique=True)
    
    def __str__(self):
        return self.username

注意:

按上面的方式扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,我现在使用我新定义的UserInfo表来做用户认证。写法如下:

# 引用Django自带的User表,继承使用时需要设置
AUTH_USER_MODEL = "app名.UserInfo"

再次注意:

一旦我们指定了新的认证系统所使用的表,我们就需要重新在数据库中创建该表,而不能继续使用原来默认的auth_user表了。

用户认证

  • 功能:用session纪录登陆验证状态
  • 前提:用户表:dajngo自带的auth_user
  • 创建超级用户:python3 manage.py createsuperuser
  • 它内置了强大的用户认证系统--auth,它默认使用 auth_user 表来存储用户数据。

一、auth模块方法

from django.contrib import auth

1、authenticate()

  • 提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。
  • 如果认证成功(用户名和密码正确有效),便会返回一个 User 对象。
  • authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。
  • 用法:
user = authenticate(username='theuser',password='thepassword')

2、login(HttpRequest, user)

  • 该函数接受一个HttpRequest对象,以及一个经过认证的User对象。
  • 该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据。
  • 注意:只要使用login(request, user_obj)之后,request.user就能拿到当前登录的用户对象。否则request.user得到的是一个匿名用户对象(AnonymousUser Object)。详细原理请查看 AuthenticationMiddleware 中间件源码。
  • 用法:
from django.contrib.auth import authenticate, login
   
def my_view(request):
  username = request.POST['username']
  password = request.POST['password']
  user_obj = authenticate(username=username, password=password)
  if user_obj:
    login(request, user_obj)
    # Redirect to a success page.
    ...
  else:
    # Return an 'invalid login' error message.
    ...

3、logout(request)

  • 该函数接受一个HttpRequest对象,无返回值。
  • 当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
  • 用法:
from django.contrib.auth import logout
   
def logout_view(request):
  logout(request)
  # Redirect to a success page.

4、is_authenticated()

  • 如果是真正的 User 对象,返回值恒为 True 。 用于检查用户是否已经通过了认证。也可以用装饰器:@login_required
  • 用法:
def my_view(request):
  if not request.user.is_authenticated():
    return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))

5、login_requierd()

  • auth 给我们提供的一个装饰器工具,用来快捷的给某个视图添加登录校验。
  • 用法:
from django.contrib.auth.decorators import login_required
      
@login_required
def my_view(request):
  ...
  • 若用户没有登录,则会跳转到django默认的 登录URL '/accounts/login/ ' 并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。
  • 如果需要自定义登录的URL,则需要在settings.py文件中通过LOGIN_URL进行修改。
  • 示例:
LOGIN_URL = '/login/'  # 这里配置成你项目登录页面的路由

6、create_user()

  • auth 提供的一个创建新用户的方法,需要提供必要参数(username、password)等。
  • 创建用户:user = User.objects.create_user(username='',password='',email='')
  • 用法:
from django.contrib.auth.models import User
user = User.objects.create_user(username='用户名',password='密码',email='邮箱',...)

7、create_superuser()

  • auth 提供的一个创建新的超级用户的方法,需要提供必要参数(username、password)等。
  • 用法:
from django.contrib.auth.models import User
user_obj = User.objects.create_superuser(username='用户名',password='密码',email='邮箱',...)

8、check_password(raw_password)

  • auth 提供的一个检查密码是否正确的方法,需要提供当前请求用户的密码。
  • 密码正确返回True,否则返回False。
  • check_password(passwd):用户需要修改密码的时候 首先要让他输入原来的密码 ,如果给定的字符串通过了密码检查,返回 True
  • 修改密码:user = User.objects.get(username='') user.set_password(password='') user.save 
  • 用法:
ok = user_obj.check_password('密码')
  • 或者直接针对当前请求的user对象校验原密码是否正确:
ok = request.user.check_password(raw_password='原密码')

9、set_password(raw_password)

  • auth 提供的一个修改密码的方法,接收 要设置的新密码 作为参数。
  • 注意:设置完一定要调用用户对象的save方法!!!
  • 用法:
user_obj.set_password('新密码')
user_obj.save()
修改密码示例

二、用户对象的属性

  • user_obj能够拿到认证所用用户表的数据属性,比如username, password等。
  • is_staff : 用户是否拥有网站的管理权限.
  • is_active : 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录。

三、扩展默认的auth_user表

  • 方式1:新建另外一张表然后通过一对一和内置的auth_user表关联
  • 方式2:(推荐)通过继承内置的 AbstractUser 类,来定义一个自己的Model类。
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
    """
    用户信息表
    """
    nid = models.AutoField(primary_key=True)
    phone = models.CharField(max_length=11, null=True, unique=True)
    
    def __str__(self):
        return self.username

注意: 按上面的方式扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,现在使用新定义的UserInfo表来做用户认证。写法如下:

# 引用Django自带的User表,继承使用时需要设置
AUTH_USER_MODEL = "app名.UserInfo"

自定义认证系统默认使用的数据表之后,就可以像使用默认的auth_user表那样使用UserInfo表了。比如:

创建普通用户:

UserInfo.objects.create_user(username='用户名', password='密码')

创建超级用户:

UserInfo.objects.create_superuser(username='用户名', password='密码')

四、使用

from django.shortcuts import render,HttpResponse,redirect
from django.contrib import auth
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required

def login(request):
    if request.method == "POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
    
        user=auth.authenticate(username=user,password=pwd) #if 验证成功返回user对象,否则返回None
        if user:
            auth.login(request,user)   # request.user:当前登录对象 是一个全局变量,在任何视图和模板都可以直接使用。
            next_url=request.GET.get("next","/index/")
            return  redirect(next_url)
    return render(request,"login.html")

@login_required
def index(request):
    #username=request.user.username
    #return render(request,"index.html",{"username":username})
    return render(request,"index.html")

def logout(request):
    auth.logout(request)
    return redirect("/login/")

def reg(request):
    if request.method=="POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        user=User.objects.create_user(username=user,password=pwd) #User.objects.create_user
        return redirect("/login/")
    return render(request,"reg.html")
View Code

1、settings.py配置

AUTH_USER_MODEL="app01.UserInfo"
 
LOGIN_URL="/login/"
 
 
LANGUAGE_CODE = 'en-us'
 
TIME_ZONE = 'Asia/Shanghai'
 
USE_I18N = True
 
USE_L10N = True
 
USE_TZ = False   #======
 
 
STATIC_URL = '/static/'
 
STATICFILES_DIRS=[
    os.path.join(BASE_DIR,"static"),
]
 
# 与用户上传相关的配置
MEDIA_ROOT=os.path.join(BASE_DIR,"media")
MEDIA_URL="/media/"
View Code

2、app01.models.py

#app01.models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
    """
    用户信息
    """
    nid = models.AutoField(primary_key=True)
    telephone = models.CharField(max_length=11, null=True, unique=True)
    avatar = models.FileField(upload_to='avatars/', default="avatars/default.png")
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
    def __str__(self):
        return self.username
View Code

3、app01.views.py

from django.shortcuts import render, HttpResponse, redirect
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.contrib import auth
from app01.Myforms import UserForm
from app01.models import UserInfo
 
def login(request):
    """
    登录视图函数:
       get请求响应页面
       post(Ajax)请求响应字典
    :param request:
    :return:
    """
 
    if request.method == "POST":
        response = {"user": None, "msg": None}
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        rmb =request.POST.get("rmb")
 
        if rmb:
            request.session.set_expiry(60 * 60 * 24 * 30)
 
        check_code = request.POST.get("check_code")
        session_code = request.session.get("check_code")
        if check_code:
            if session_code.upper() == check_code.upper():
                user = auth.authenticate(username=user, password=pwd)
                if user:
                    auth.login(request, user)  # request.user== 当前登录对象
                    response["user"] = user.username
                else:
                    response["msg"] = "用户名或者密码错误!"
            else:
                response["msg"] = "验证码错误!"
        else:
            response["msg"] = "验证码不能为空!"
 
        return JsonResponse(response)
 
    return render(request, "login.html")
 
 
def logout(request):
    """
    注销视图
    :param request:
    :return:
    """
    auth.logout(request)  # request.session.flush()
    return redirect("/login/")
 
 
def check_code(request):
    """
    验证码: 
    字体:static/font/Monaco.ttf
    check_code = request.session.get("check_code")  
    """
    from utils.check_code import create_validate_code
    img_data = create_validate_code(request)
    return HttpResponse(img_data)
 
 
def register(request):
    """
    注册视图函数:
       get请求响应注册页面
       post(Ajax)请求,校验字段,响应字典
    :param request:
    :return:
    """
 
    if request.is_ajax():
        form = UserForm(request,request.POST)
        response = {"user": None, "msg": None}
        if form.is_valid():
            response["user"] = form.cleaned_data.get("user")
 
            # 生成一条用户纪录
            user = form.cleaned_data.get("user")
 
            pwd = form.cleaned_data.get("pwd")
            email = form.cleaned_data.get("email")
 
            UserInfo.objects.create_user(username=user, password=pwd, email=email,)
 
        else:
            print(form.cleaned_data)
            print(form.errors)
            response["msg"] = form.errors
 
        return JsonResponse(response)
 
    form = UserForm(request)
    return render(request, "register.html", {"form": form})
 
@login_required
def index(request):
    pass
View Code

4、app01.Myforms.py

from django import forms
 
from django.forms import widgets
 
from blog.models import UserInfo
from django.core.exceptions import  ValidationError
 
 
 
class BaseForm(object):
    def __init__(self, request, *args, **kwargs):
        self.request = request
        super(BaseForm, self).__init__(*args, **kwargs)
 
class UserForm(BaseForm,forms.Form):
 
    user=forms.CharField(min_length=4,
                         max_length=32,
                         error_messages={"required":"用户名不能为空"},
                         label="用户名",
                         widget=widgets.TextInput(attrs={"class":"form-control"},)
                         )
    pwd=forms.CharField(min_length=4,
                        max_length=32,
                        error_messages={'required': '密码不能为空.'},
                        label="密码",
                        widget=widgets.PasswordInput(attrs={"class":"form-control"},)
                        )
    re_pwd=forms.CharField(min_length=4,
                           max_length=32,
                           error_messages={'required': '密码不能为空.'},
                           label="确认密码",
                           widget=widgets.PasswordInput(attrs={"class":"form-control"},)
                           )
    email=forms.EmailField(max_length=32,
                           error_messages={'required': '邮箱不能为空.'},
                           label="邮箱",
                           widget=widgets.EmailInput(attrs={"class":"form-control"},)
                            )
 
    check_code=forms.CharField(
                        error_messages={'required': '验证码不能为空.'},
                        label="验证码",
                        widget=widgets.TextInput(attrs={'class':'form-control'},)
    )
 
    def clean_check_code(self):
        input_code = self.cleaned_data['check_code']
        session_code = self.request.session.get('check_code')
        if input_code.upper() == session_code.upper():
            return input_code
        raise ValidationError(message='验证码错误', code='invalid')
 
 
    def clean_user(self):
        val=self.cleaned_data.get("user")
 
        user=UserInfo.objects.filter(username=val).first()
        if not user:
            return val
        else:
            raise ValidationError("该用户已注册!")
 
 
    def clean(self):
        pwd=self.cleaned_data.get("pwd")
        re_pwd=self.cleaned_data.get("re_pwd")
 
        if pwd and re_pwd:
            if pwd==re_pwd:
                return self.cleaned_data
            else:
                # raise ValidationError("两次密码不一致!")
                self.add_error("re_pwd", ValidationError('两次密码不一致'))
        else:
            return self.cleaned_data
View Code

5、自定义验证码 utils.check_code.py

字体文件:"static/font/Monaco.ttf"

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from io import BytesIO
 
_letter_cases = "abcdefghjkmnpqrstuvwxy"  # 小写字母,去除可能干扰的i,l,o,z
_upper_cases = _letter_cases.upper()  # 大写字母
_numbers = ''.join(map(str, range(3, 10)))  # 数字
init_chars = ''.join((_letter_cases, _upper_cases, _numbers))
 
# PIL
def create_validate_code(request,size=(120, 30),
                         chars=init_chars,
                         img_type="GIF",
                         mode="RGB",
                         bg_color=(255, 255, 255),
                         fg_color=(0, 0, 255),
                         font_size=18,
                         font_type="static/font/Monaco.ttf",
                         length=4,
                         draw_lines=True,
                         n_line=(1, 2),
                         draw_points=True,
                         point_chance=2):
    """
    @todo: 生成验证码图片
    @param size: 图片的大小,格式(宽,高),默认为(120, 30)
    @param chars: 允许的字符集合,格式字符串
    @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG
    @param mode: 图片模式,默认为RGB
    @param bg_color: 背景颜色,默认为白色
    @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF
    @param font_size: 验证码字体大小
    @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf
    @param length: 验证码字符个数
    @param draw_lines: 是否划干扰线
    @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效
    @param draw_points: 是否画干扰点
    @param point_chance: 干扰点出现的概率,大小范围[0, 100]
    @return: [0]: PIL Image实例
    @return: [1]: 验证码图片中的字符串
    """
 
    width, height = size  # 宽高
    # 创建图形
    img = Image.new(mode, size, bg_color)
    draw = ImageDraw.Draw(img)  # 创建画笔
 
    def get_chars():
        """生成给定长度的字符串,返回列表格式"""
        return random.sample(chars, length)
 
    def create_lines():
        """绘制干扰线"""
        line_num = random.randint(*n_line)  # 干扰线条数
 
        for i in range(line_num):
            # 起始点
            begin = (random.randint(0, size[0]), random.randint(0, size[1]))
            # 结束点
            end = (random.randint(0, size[0]), random.randint(0, size[1]))
            draw.line([begin, end], fill=(0, 0, 0))
 
    def create_points():
        """绘制干扰点"""
        chance = min(100, max(0, int(point_chance)))  # 大小限制在[0, 100]
 
        for w in range(width):
            for h in range(height):
                tmp = random.randint(0, 100)
                if tmp > 100 - chance:
                    draw.point((w, h), fill=(0, 0, 0))
 
    def create_strs():
        """绘制验证码字符"""
        c_chars = get_chars()
        strs = ' %s ' % ' '.join(c_chars)  # 每个字符前后以空格隔开
 
        font = ImageFont.truetype(font_type, font_size)
        font_width, font_height = font.getsize(strs)
 
        draw.text(((width - font_width) / 3, (height - font_height) / 3),
                  strs, font=font, fill=fg_color)
 
        return ''.join(c_chars)
 
    if draw_lines:
        create_lines()
    if draw_points:
        create_points()
    strs = create_strs()
 
    # 图形扭曲参数
    params = [1 - float(random.randint(1, 2)) / 100,
              0,
              0,
              0,
              1 - float(random.randint(1, 10)) / 100,
              float(random.randint(1, 2)) / 500,
              0.001,
              float(random.randint(1, 2)) / 500
              ]
    img = img.transform(size, Image.PERSPECTIVE, params)  # 创建扭曲
 
    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)  # 滤镜,边界加强(阈值更大)
 
    f = BytesIO()
    request.session['check_code'] = strs
    img.save(f, 'png')
    data = f.getvalue()
 
    return data
View Code

6、注册页面templates.register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
         
        .error {
            color: red;
        }
 
        .register {
            width: 400px;
            margin-top: 20px;
            margin-left: auto;
            margin-right: auto;
            border: 1px solid #f0f0f0;
            padding: 10px 30px 50px 30px;
            -webkit-box-shadow: 5px 10px 10px rgba(0, 0, 0, .05);
            box-shadow: 5px 10px 10px rgba(0, 0, 0, .05);
        }
 
        .register h3 {
            font-size: 25px;
            text-align: center;
            font-weight: bold;
        }
    </style>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
 
 
</head>
<body>
 
 
<div class="register">
    <h3>用户注册</h3>
 
    <form id="form" novalidate>
        {% csrf_token %}
 
 
        <div class="form-group">
            <label for="id_user">{{ form.user.label }}</label>
            {{ form.user }} <span class="pull-right error">{{ form.user.errors.0 }}</span>
        </div>
        <div class="form-group">
            <label for="id_pwd">{{ form.pwd.label }}</label>
            {{ form.pwd }} <span class="pull-right error">{{ form.pwd.errors.0 }}</span>
        </div>
        <div class="form-group">
            <label for="id_re_pwd">{{ form.re_pwd.label }}</label>
            {{ form.re_pwd }} <span class="pull-right error">{{ form.re_pwd.errors.0 }}</span>
            <!--<span class="pull-right error">{{ errors.0 }}</span>-->
        </div>
        <div class="form-group">
            <label for="id_email">{{ form.email.label }}</label>
            {{ form.email }} <span class="pull-right error">{{ form.email.errors.0 }}</span></div>
 
        <div class="form-group">
            <label for="id_check_code">{{ form.check_code.label }}</label>
 
            <div class="row">
                <div class="col-xs-7">
                    {{ form.check_code }}<span class="pull-right error">{{ form.errors.check_code.0 }}</span>
                </div>
                <div class="col-xs-5">
                    <img id="check" src="/check_code/">
                </div>
            </div>
 
        </div>
 
 
 
        <input type="button" class="btn btn-default reg_btn" value="注册"/>
    </form>
</div>
 
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
 
    $("#check").click(function () {
        old_check = $(this).attr('src');
        $(this).attr('src', old_check + '?');
    });
 
 
    // 基于Ajax提交数据
 
    $(".reg_btn").click(function () {
        //console.log($("#form").serializeArray());
        var formdata = new FormData();
        var request_data = $("#form").serializeArray();
        $.each(request_data, function (index, data) {
            formdata.append(data.name, data.value)
        });
 
 
        $.ajax({
            url: "",
            type: "post",
            contentType: false,
            processData: false,
            data: formdata,
            success: function (data) {
                //console.log(data);
 
                if (data.user) {
                    // 注册成功
                    location.href = "/login/"
                }
                else { // 注册失败
 
                    //console.log(data.msg)
                    // 清空错误信息
                    $("span.error").html("");
                    $(".form-group").removeClass("has-error");
 
                    // 显示提交的错误信息!
                    $.each(data.msg, function (field, error_list) {
                        console.log(field, error_list);
                        if (field == "__all__") {
                            $("#id_re_pwd").next().html(error_list[0]).parent().addClass("has-error");
                        }
                        $("#id_" + field).next().html(error_list[0]);
                        $("#id_" + field).parent().addClass("has-error");
 
 
                    })
 
                }
            }
        })
 
    })
 
 
</script>
 
</body>
</html>
View Code

7、登录页面templates.login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
 
        .error {
            color: red;
        }
 
        .login {
            width: 400px;
            margin-top: 20px;
            margin-left: auto;
            margin-right: auto;
            border: 1px solid #f0f0f0;
            padding: 10px 30px 50px 30px;
            -webkit-box-shadow: 5px 10px 10px rgba(0, 0, 0, .05);
            box-shadow: 5px 10px 10px rgba(0, 0, 0, .05);
        }
 
        .login h3 {
            font-size: 25px;
            text-align: center;
            font-weight: bold;
        }
    </style>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
 
</head>
<body>
 
<div class="login">
    <h3>
        用户登陆
    </h3>
    <form>
        {% csrf_token %}
        <div class="form-group">
            <label for="user">用户名</label>
            <input type="text" class="form-control"  id="user" placeholder="请输入用户名">
        </div>
        <div class="form-group">
            <label for="pwd">密码</label>
            <input type="password" class="form-control"  id="pwd" placeholder="请输入密码">
        </div>
        <div class="form-group">
            <label for="check_code">验证码</label>
 
            <div class="row">
                <div class="col-xs-7">
                    <input type="text" class="form-control"  id="check_code" placeholder="请输入验证码">
                </div>
                <div class="col-xs-5">
                    <img id="check" src="/check_code/">
                </div>
            </div>
 
        </div>
 
        <div class="checkbox">
            <label>
                <input type="checkbox" value="1" name="rmb"> 一个月内自动登陆
            </label>
 
            <div class="pull-right">
                <a href="#">忘记密码?</a>
            </div>
        </div>
 
        <input type="button" class="btn btn-default login_btn" value="登 陆"/><span class="error"></span>
 
    </form>
</div>
 
 
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
 
    // 刷新验证码
    $("#check").click(function () {
        $(this)[0].src += "?"
    });
 
 
    // 登录验证
    $(".login_btn").click(function () {
 
 
        $.ajax({
            url: "",
            type: "post",
            data: {
                user: $("#user").val(),
                pwd: $("#pwd").val(),
                check_code: $("#check_code").val(),
                csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
            },
            success: function (data) {
                console.log(data);
 
                if (data.user) {
                    if (location.search){
                        location.href = location.search.slice(6)
                    }
                    else {
                         location.href = "/index/"
                    }
 
                }
                else {
                    $(".error").text(data.msg).css({"color": "red", "margin-left": "10px"});
                    setTimeout(function(){
                         $(".error").text("");
                    },1000)
 
                }
            }
        })
 
    })
 
</script>
</body>
</html>
View Code

五、geetest验证码

  • pip3 install social-auth-app-django

1、urls.py

from django.contrib import admin
from django.urls import path,re_path
 
from django.views.static import serve
from app01 import views
 
from django.conf import settings
from django.urls import include
 
 
urlpatterns = [
 
    re_path(r'^alogin/$', views.alogin),
    re_path(r'^pc-geetest/register', views.pcgetcaptcha, name='pcgetcaptcha'),
    re_path(r'^pc-geetest/ajax_validate', views.pcajax_validate, name='pcajax_validate'),
 
 
    path('admin/', admin.site.urls),
    path('login/', views.login),
    path('index/', views.index),
    path('logout/', views.logout),
    path('check_code/', views.check_code),
    path('register/', views.register),
    re_path('^$', views.index),
 
    # media配置:
    re_path(r"media/(?P<path>.*)$", serve, {"document_root": settings.MEDIA_ROOT}),
]
View Code

2、utils.geetest.py

#!coding:utf8
import sys
import random
import json
import requests
import time
from hashlib import md5
 
 
if sys.version_info >= (3,):
    xrange = range   
 
VERSION = "3.0.0"
 
 
class GeetestLib(object):
 
    FN_CHALLENGE = "geetest_challenge"
    FN_VALIDATE = "geetest_validate"
    FN_SECCODE = "geetest_seccode"
 
    GT_STATUS_SESSION_KEY = "gt_server_status"
 
    API_URL = "http://api.geetest.com"
    REGISTER_HANDLER = "/register.php"
    VALIDATE_HANDLER = "/validate.php"
    JSON_FORMAT = False
 
    def __init__(self, captcha_id, private_key):
        self.private_key = private_key
        self.captcha_id = captcha_id
        self.sdk_version = VERSION
        self._response_str = ""
 
 
    def pre_process(self, user_id=None,new_captcha=1,JSON_FORMAT=1,client_type="web",ip_address=""):
        """
        验证初始化预处理.
        //TO DO  arrage the parameter
        """
        status, challenge = self._register(user_id,new_captcha,JSON_FORMAT,client_type,ip_address)
        self._response_str = self._make_response_format(status, challenge,new_captcha)
        return status
 
    def _register(self, user_id=None,new_captcha=1,JSON_FORMAT=1,client_type="web",ip_address=""):
        pri_responce = self._register_challenge(user_id,new_captcha,JSON_FORMAT,client_type,ip_address)
        if pri_responce:
            if JSON_FORMAT == 1:
                response_dic = json.loads(pri_responce)
                challenge = response_dic["challenge"]
            else:
                challenge = pri_responce
        else:
            challenge=" "
        if len(challenge) == 32:
            challenge = self._md5_encode("".join([challenge, self.private_key]))
            return 1,challenge
        else:
            return 0, self._make_fail_challenge()
 
    def get_response_str(self):
        return self._response_str
 
    def _make_fail_challenge(self):
        rnd1 = random.randint(0, 99)
        rnd2 = random.randint(0, 99)
        md5_str1 = self._md5_encode(str(rnd1))
        md5_str2 = self._md5_encode(str(rnd2))
        challenge = md5_str1 + md5_str2[0:2]
        return challenge
 
    def _make_response_format(self, success=1, challenge=None,new_captcha=1):
        if not challenge:
            challenge = self._make_fail_challenge()
        if new_captcha:
            string_format = json.dumps(
                {'success': success, 'gt':self.captcha_id, 'challenge': challenge,"new_captcha":True})
        else:
            string_format = json.dumps(
                {'success': success, 'gt':self.captcha_id, 'challenge': challenge,"new_captcha":False})
        return string_format
 
    def _register_challenge(self, user_id=None,new_captcha=1,JSON_FORMAT=1,client_type="web",ip_address=""):
        if user_id:
            register_url = "{api_url}{handler}?gt={captcha_ID}&user_id={user_id}&json_format={JSON_FORMAT}&client_type={client_type}&ip_address={ip_address}".format(
                    api_url=self.API_URL, handler=self.REGISTER_HANDLER, captcha_ID=self.captcha_id, user_id=user_id,new_captcha=new_captcha,JSON_FORMAT=JSON_FORMAT,client_type=client_type,ip_address=ip_address)
        else:
            register_url = "{api_url}{handler}?gt={captcha_ID}&json_format={JSON_FORMAT}&client_type={client_type}&ip_address={ip_address}".format(
                    api_url=self.API_URL, handler=self.REGISTER_HANDLER, captcha_ID=self.captcha_id,new_captcha=new_captcha,JSON_FORMAT=JSON_FORMAT,client_type=client_type,ip_address=ip_address)
        try:
            response = requests.get(register_url, timeout=2)
            if response.status_code == requests.codes.ok:
                res_string = response.text
            else:
                res_string = ""
        except:
            res_string = ""
        return res_string
 
    def success_validate(self, challenge, validate, seccode, user_id=None,gt=None,data='',userinfo='',JSON_FORMAT=1):
        """
        正常模式的二次验证方式.向geetest server 请求验证结果.
        """
        if not self._check_para(challenge, validate, seccode):
            return 0
        if not self._check_result(challenge, validate):
            return 0
        validate_url = "{api_url}{handler}".format(
            api_url=self.API_URL, handler=self.VALIDATE_HANDLER)
        query = {
            "seccode": seccode,
            "sdk": ''.join( ["python_",self.sdk_version]),
            "user_id": user_id,
            "data":data,
            "timestamp":time.time(),
            "challenge":challenge,
            "userinfo":userinfo,
            "captchaid":gt,
            "json_format":JSON_FORMAT
        }
        backinfo = self._post_values(validate_url, query)
        if JSON_FORMAT == 1:
            backinfo = json.loads(backinfo)
            backinfo = backinfo["seccode"]
        if backinfo == self._md5_encode(seccode):
            return 1
        else:
            return 0
 
    def _post_values(self, apiserver, data):
        response = requests.post(apiserver, data)
        return response.text
 
    def _check_result(self, origin, validate):
        encodeStr = self._md5_encode(self.private_key + "geetest" + origin)
        if validate == encodeStr:
            return True
        else:
            return False
 
    def failback_validate(self, challenge, validate, seccode):
        """
        failback模式的二次验证方式.在本地对轨迹进行简单的判断返回验证结果.
        """
        if not self._check_para(challenge, validate, seccode):
            return 0
        validate_result = self._failback_check_result(
            challenge, validate,)
        return validate_result
 
    def _failback_check_result(self,challenge,validate):
        encodeStr = self._md5_encode(challenge)
        if validate == encodeStr:
            return True
        else:
            return False
 
 
 
    def _check_para(self, challenge, validate, seccode):
        return (bool(challenge.strip()) and bool(validate.strip()) and  bool(seccode.strip()))
 
 
 
    def _md5_encode(self, values):
        if type(values) == str:
            values = values.encode()
        m = md5(values)
        return m.hexdigest()
View Code

3、app01.views.py

from django.shortcuts import render, HttpResponse, redirect
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.contrib import auth
from blog.Myforms import UserForm
from blog.models import UserInfo

from utils.geetest import GeetestLib

pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"
pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"


def pcajax_validate(request):
    if request.method == "POST":
        gt = GeetestLib(pc_geetest_id, pc_geetest_key)
        challenge = request.POST.get(gt.FN_CHALLENGE, '')
        validate = request.POST.get(gt.FN_VALIDATE, '')
        seccode = request.POST.get(gt.FN_SECCODE, '')
        status = request.session[gt.GT_STATUS_SESSION_KEY]
        user_id = request.session["user_id"]
        if status:
            result = gt.success_validate(challenge, validate, seccode, user_id)
        else:
            result = gt.failback_validate(challenge, validate, seccode)
        result = {"status": "success"} if result else {"status": "fail"}
        # ================
        if result:
            result = {"status": "success"}

            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
            user = auth.authenticate(username=user, password=pwd)
            if not user:
                result['status'] = "fail"
                result['msg'] = '用户名或者密码错误'
            else:
                auth.login(request, user)  # request.user== 当前登录对象  模板中{{ request.user.username }}

            rmb = request.POST.get("rmb")
            if rmb:
                request.session.set_expiry(60 * 60 * 24 * 30)

        else:
            result = {"status": "fail"}

        # ===================
        return HttpResponse(json.dumps(result))
    return HttpResponse("error")


def pcgetcaptcha(request):
    user_id = 'test'
    gt = GeetestLib(pc_geetest_id, pc_geetest_key)
    status = gt.pre_process(user_id)
    request.session[gt.GT_STATUS_SESSION_KEY] = status
    request.session["user_id"] = user_id
    response_str = gt.get_response_str()
    return HttpResponse(response_str)


def alogin(request):
    return render(request, 'alogin.html')
View Code

4、templates.alogin.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
 
    .login {
    width: 400px;
    margin-top: 100px;
    margin-left: auto;
    margin-right: auto;
    border: 1px solid #f0f0f0;
    padding: 10px 30px 50px 30px;
    -webkit-box-shadow: 5px 10px 10px rgba(0, 0, 0, .05);
    box-shadow: 5px 10px 10px rgba(0, 0, 0, .05);
    }
    .login h3{font-size: 25px; text-align: center;font-weight: bold;}
 
    .alert{
        padding: 6px;
        margin-bottom: 0;
    }
 
    /* 以下遮罩层为demo.用户可自行设计实现 */
        #mask {
            display: none;
            position: fixed;
            text-align: center;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            overflow: auto;
        }
        /* 可自行设计实现captcha的位置大小 */
        .popup-mobile {
            position: relative;
        }
        #popup-captcha-mobile {
            position: fixed;
            display: none;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            -webkit-transform: translate(-50%, -50%);
            z-index: 9999;
        }
 
 
    </style>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
    integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
 
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
 
</head>
<body>
<div class="login">
    <h3>用户登陆</h3>
    <form>
        {% csrf_token %}
        <div class="form-group">
            <label for="user">用户名</label>
            <input type="text" class="form-control"  id="user" placeholder="请输入用户名">
        </div>
        <div class="form-group">
            <label for="pwd">密码</label>
            <input type="password" class="form-control"  id="pwd" placeholder="请输入密码">
        </div>
 
        <div class="checkbox">
            <label>
                <input type="checkbox" value="1" name="rmb"> 一个月内自动登陆
            </label>
 
            <div class="pull-right">
                <a href="#">忘记密码?</a>
            </div>
        </div>
 
        <input id="popup-submit" type="button" class="btn btn-default login_btn" value="登 陆"/><span id="error_msg"></span>
 
    </form>
 
    <div id="popup-captcha"></div>
 
 
</div>
 
<!-- 为使用方便,直接使用jquery.js库,如您代码中不需要,可以去掉 -->
<script src="http://code.jquery.com/jquery-1.12.3.min.js"></script>
<!-- 引入封装了failback的接口--initGeetest -->
<script src="http://static.geetest.com/static/tools/gt.js"></script>
 
 
<script>
    var handlerPopup = function (captchaObj) {
        // 成功的回调
        captchaObj.onSuccess(function () {
            var validate = captchaObj.getValidate();
            $.ajax({
                url: "/pc-geetest/ajax_validate", // 进行二次验证
                type: "post",
                dataType: "json",
                data: {
                    user: $('#user').val(),
                    pwd: $('#pwd').val(),
                    csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
                    geetest_challenge: validate.geetest_challenge,
                    geetest_validate: validate.geetest_validate,
                    geetest_seccode: validate.geetest_seccode
                },
                success: function (data) {
                    if (data && (data.status === "success")) {
                        location.href = "/index/"
                    } else {
{#                        console.log(data.msg);#}
                        $("#error_msg").text(data.msg).css({"color": "red", "margin-left": "10px"});
                            setTimeout(function(){
                                 $("#error_msg").text("");
                            },4000)
 
                    }
                }
            });
        });
        $("#popup-submit").click(function () {
            captchaObj.show();
        });
        // 将验证码加到id为captcha的元素里
        captchaObj.appendTo("#popup-captcha");
        // 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html
    };
    // 验证开始需要向网站主后台获取id,challenge,success(是否启用failback)
    $.ajax({
        url: "/pc-geetest/register?t=" + (new Date()).getTime(), // 加随机数防止缓存
        type: "get",
        dataType: "json",
        success: function (data) {
            // 使用initGeetest接口
            // 参数1:配置参数
            // 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件
            initGeetest({
                gt: data.gt,
                challenge: data.challenge,
                product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
                offline: !data.success // 表示用户后台检测极验服务器是否宕机,一般不需要关注
                // 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config
            }, handlerPopup);
        }
    });
</script>
 
 
</body>
</html>
View Code

5、app01.models.py和app01.Myforms.py同上

posted @ 2019-01-17 01:53  silencio。  阅读(347)  评论(0编辑  收藏  举报