• 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同上

  • 相关阅读:
    dev gridcontrol设置复选框列,和按数据选择行
    Django——三种方式上传文件/数据 (form ajax json)
    Django——ajax简单使用
    Django——ajax介绍,django内置序列化器
    阿里云oss 上传文件的两种方式(本地路径上传远程链接上传)
    easywechat 网页授权登录
    19。删除链表倒数第N个节点
    142环形链表II
    141环形链表
    701二叉搜索树中的插入操作
  • 原文地址:https://www.cnblogs.com/bubu99/p/10280280.html
Copyright © 2020-2023  润新知