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")
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/"
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
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
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
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
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>
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>
五、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}), ]
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()
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')
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>