luffy之多条件登录与极验滑动验证码
多条件登录
JWT扩展的登录视图,在收到用户名与密码时,也是调用Django的认证系统中提供的authenticate()来检查用户名与密码是否正确。
我们可以通过修改Django认证系统的认证后端(主要是authenticate方法)来支持登录账号既可以是用户名也可以是手机号。
修改Django认证系统的认证后端需要继承django.contrib.auth.backends.ModelBackend,并重写authenticate方法。
authenticate(self, request, username=None, password=None, **kwargs)方法的参数说明:
request 本次认证的请求对象
username 本次认证提供的用户账号
password 本次认证提供的密码
我们想要让用户既可以以用户名登录,也可以以手机号登录,那么对于authenticate方法而言,username参数即表示用户名或者手机号。
重写authenticate方法的思路:
根据username参数查找用户User对象,username参数可能是用户名,也可能是手机号
若查找到User对象,调用User对象的check_password方法检查密码是否正确
在users/utils.py中编写:
def get_user_by_account(account):
"""
根据帐号获取user对象
:param account: 账号,可以是用户名,也可以是手机号
:return: User对象 或者 None
"""
try:
if re.match('^1[3-9]\d{9}$', account):
# 帐号为手机号
user = User.objects.get(mobile=account)
else:
# 帐号为用户名
user = User.objects.get(username=account)
except User.DoesNotExist:
return None
else:
return user class UsernameMobileAuthBackend(ModelBackend):
"""
自定义用户名或手机号认证
""" def authenticate(self, request, username=None, password=None, **kwargs):
user = get_user_by_account(username)
if user is not None and user.check_password(password):
return user
在配置文件settings.py中告知Django使用我们自定义的认证后端
AUTHENTICATION_BACKENDS = [
'users.utils.UsernameMobileAuthBackend',
]
在登陆功能中集成极验验证码
需要先去极验创建账号,并且创建一个新的验证,创建好了以后,就可以得到APPID和APPKEY。

有了id和key以后,就可以根据官方的文档进行集成了.
官方: https://docs.geetest.com/install/overview/start/

python集成文档: https://docs.geetest.com/install/deploy/server/python/
SDK: 开发集成工具包
安装依赖模块:
pip install requests
安装完成模块以后,可以参考官方文档中的案例,把提供验证码和校验验证码的功能集成到视图类中.
文档: https://github.com/GeeTeam/gt3-python-sdk
在使用前要将相应的文件下载到项目中:geetest.py

以下是后端验证的代码:users/user/view
from django.shortcuts import render
import json,random
# Create your views here.
from rest_framework.response import Response
from rest_framework.views import APIView from luffy.libs.geetest import GeetestLib pc_geetest_id = 'af88c60fe04b********8b626b478c497'
pc_geetest_key = '2d8570960*********dd219ca1c6ffe7' class VerifyCodeView(APIView):
def get(self,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 Response(response_str) def post(self,request):#TODO这里的极验验证在post请求获取session值的时候获取不到
gt = GeetestLib(pc_geetest_id, pc_geetest_key)
status = request.session[gt.GT_STATUS_SESSION_KEY]
challenge = request.data.get(gt.FN_CHALLENGE)
validate = request.data.get(gt.FN_VALIDATE)
seccode = request.data.get(gt.FN_SECCODE)
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"}
return Response(result)
<script>
export default {
name: "Login",
data() {
return {
login_type: 1,
username: "",
password: "",
remember: false,
}
},
methods: {
login_submit() {
this.$axios.post('http://127.0.0.1:8000/users/login', {
'username': this.username,
'password': this.password
}, {
responseType: "json"
}).then(response => {
// 请求成功,保存登陆状态
if (this.remember) {
// 记住密码
sessionStorage.removeItem("token");
let data = response.data;
localStorage.token = data.token;
} else {
// 不记住密码
localStorage.removeItem("token");
let data = response.data;
sessionStorage.token = data.token;
}
// 登录成功以后,跳转页面
// this.$router.go(-1);
this.$router.push("/home");
}).catch(err => {
console.log(err);
})
},
handlerEmbed(captchaObj) {
// 成功的回调
let _this = this;
captchaObj.onSuccess(function () {
var validate = captchaObj.getValidate();
_this.$axios.post("http://127.0.0.1:8000/users/verify", {
geetest_challenge: validate.geetest_challenge,
geetest_validate: validate.geetest_validate,
geetest_seccode: validate.geetest_seccode
}, {
responseType: "json",
},).then(response => {
// 请求成功
console.log('response',response);
console.log('response.data',response.data); // 获取验证结果
}).catch(error => {
// 请求失败
console.log(error)
})
});
captchaObj.appendTo("#embed-captcha");//将滑动验证图片加载到该地中的元素内 }
}, created() {
this.$axios.get('http://127.0.0.1:8000/users/verify',).then(resopnse => {
var data = JSON.parse(resopnse.data);
initGeetest({
gt: data.gt,
challenge: data.challenge,
product: "embed", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
offline: !data.success // 表示用户后台检测极验服务器是否宕机,一般不需要关注
// 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config
}, this.handlerEmbed)
}).catch(err => {
console.log(err)
})
}
} </script>
注意:因为使用极验,需要用到session,但在vue中axios发送ajaxi默认是不携带cookie值,假如需要携带值
在main.js中修改
import axios from 'axios'
axios.defaults.withCredentials=true;//让ajax携带cookie
Vue.prototype.$axios = axios;
补充:双向验证
从上面的例子看.当有人用程序不经过前端注册时,只需要在请求体中添加status=success即可跳过短信验证,向后端发送数据,这样不太安全.
所以在后端的值短信验证成功,在后端添加一个随机数,返回给前端,我们将此数值存入到redis中,当后端发送注册请求时后端从redis拿出对应数值即可.
客户端验证: 使用js进行识别判断和校验,但是这里的验证不能保存百分百的安全,
后端验证: 入库前最后判断, 最后一道防火墙.
settings.py中设置一个新的redis数据库保存后端生成的随机码:
CACHES = {
...
"slicode_randint":{
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/3",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
views.py
class VerifyCodeView(APIView):
def get(self,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 Response(response_str) def post(self,request):#TODO这里的极验验证在post请求获取session值的时候获取不到
gt = GeetestLib(pc_geetest_id, pc_geetest_key)
status = request.session[gt.GT_STATUS_SESSION_KEY]
challenge = request.data.get(gt.FN_CHALLENGE)
validate = request.data.get(gt.FN_VALIDATE)
seccode = request.data.get(gt.FN_SECCODE)
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) # if result == "success":
# # 验证通过了,生成一个随机值保存到redis中,在用户再次提交注册信息的时候,验证是否附带了唯一值
# slicode_randint = random.randint(1000, 9999)
slicode_randint = "%08d" %random.randint(0, 99999999)
print('slicode_randint',slicode_randint)
# # 保存到redis中
redis = get_redis_connection("slicode_randint")
redis.setex(slicode_randint, 60 * 10, 1) result = {"status": "success", "randint_code": slicode_randint} if result else {"status": "fail", "randint_code": -1}
return Response(result)
前端代码,需要在接受验证返回值,多接受一个参数slicode_randint,并且在data先定义这个值
class VerifyCodeView(APIView):
def get(self,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 Response(response_str) def post(self,request):#TODO这里的极验验证在post请求获取session值的时候获取不到
gt = GeetestLib(pc_geetest_id, pc_geetest_key)
status = request.session[gt.GT_STATUS_SESSION_KEY]
challenge = request.data.get(gt.FN_CHALLENGE)
validate = request.data.get(gt.FN_VALIDATE)
seccode = request.data.get(gt.FN_SECCODE)
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) # if result == "success":
# # 验证通过了,生成一个随机值保存到redis中,在用户再次提交注册信息的时候,验证是否附带了唯一值
# slicode_randint = random.randint(1000, 9999)
slicode_randint = "%08d" %random.randint(0, 99999999)
print('slicode_randint',slicode_randint)
# # 保存到redis中
redis = get_redis_connection("slicode_randint")
redis.setex(slicode_randint, 60 * 10, 1) result = {"status": "success", "randint_code": slicode_randint} if result else {"status": "fail", "randint_code": -1}
return Response(result)
后端注册用户视图调用序列化器,需要验证当前验证码.(添加验证字段,验证slicode_randint与redis中存储的是否一致,验证一致,在存入数据库前需要删除该字段,不能存到数据库中)
# from rest_framework import serializers
# from .models import User
# import re
# from django_redis import get_redis_connection
#
# class UserModelSerializer(serializers.ModelSerializer):
# """用户信息序列化器"""
# sms_code = serializers.CharField(label='手机验证码', required=True, allow_null=False, allow_blank=False, write_only=True)
# password2 = serializers.CharField(label='确认密码', required=True, allow_null=False, allow_blank=False, write_only=True)
# token = serializers.CharField(label='jwt', read_only=True)
randint_code= serializers.CharField(label="极验验证验的验证字符串",write_only=True)
# class Meta:
# model=User
fields = ('id','sms_code', 'mobile', 'password','password2','token',"verify_code")
# extra_kwargs={
# "password":{
# "write_only":True
# },
# "id":{
# "read_only":True,
# }
# }
#
def validate_randint_code(self,value):
"""验证当前客户端是否已经通过了极验验证码的验证"""
redis = get_redis_connection("verify_code")
verify_code = redis.get(value)
print(verify_code)
if(not verify_code):
raise serializers.ValidationError('验证码失效~') return value # def validate_mobile(self, value):
# """验证手机号"""
# if not re.match(r'^1[345789]\d{9}$', value):
# raise serializers.ValidationError('手机号格式错误')
#
# # 验证手机号是否已经被注册了
# # try:
# # user = User.objects.get(mobile=value)
# # except:
# # user = None
# #
# # if user:
# # raise serializers.ValidationError('当前手机号已经被注册')
#
# # 上面验证手机号是否存在的代码[优化版]
# try:
# User.objects.get(mobile=value)
# # 如果有获取到用户信息,则下面的代码不会被执行,如果没有获取到用户信息,则表示手机号没有注册过,可以直接pass
# raise serializers.ValidationError('当前手机号已经被注册')
# except:
# pass
#
# return value
#
# def validate(self,data):
# """验证密码"""
# password = data.get("password")
# password2 = data.get("password2")
# if len(password)<6:
# raise serializers.ValidationError('密码太短不安全~')
#
# if password !=password2:
# raise serializers.ValidationError('密码和确认必须一致~')
#
# """验证短信验证码"""
#
# mobile = data.get("mobile")
# sms_code = data.get("sms_code")
#
# # 从redis中提取短信验证码
# redis = get_redis_connection("sms_code")
# # 注意,在redis中保存数据的格式,最终都是bytes类型的字符串,所以提取数据时,要转下编码
# redis_sms_code = redis.get("sms_%s" % mobile).decode()
# # 把redis中的短信验证码和客户端提交的验证码进行匹配
# if not (redis_sms_code and redis_sms_code == sms_code):
# raise serializers.ValidationError('手机验证码无效~')
#
# return data
#
# def create(self, validated_data):
# # 删除一些不需要保存到数据库里面的字段
# del validated_data['password2']
# del validated_data['sms_code']
del validated_data['randint_code']
#
# # 可以补充删除redis的验证码逻辑
#
# # 因为数据库中默认用户名是唯一的,所以我们把用户手机号码作为用户名
# validated_data["username"] = validated_data["mobile"]
#
# # 继续调用ModelSerializer内置的添加数据功能
# user = super().create(validated_data)
#
# # 针对密码要加密
# user.set_password(user.password)
# # 修改密码等用于更新了密码,所以需要保存
# user.save()
#
# # 一旦注册成功以后,则默认表示当前用户已经登录了
# # 所以后端要生成一个jwt提供给客户端
# from rest_framework_jwt.settings import api_settings
# jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
# jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
#
# payload = jwt_payload_handler(user)
# # 把jwt生成的token作为user模型的字段
# user.token = jwt_encode_handler(payload)
#
# return users
View Cod
luffy之多条件登录与极验滑动验证码的更多相关文章
- thinkphp整合系列之极验滑动验证码
对于建站的筒子们来说:垃圾广告真是让人深恶痛绝:为了清净:搞个难以识别的验证码吧:又被用户各种吐槽:直到后来出现了极验这个滑动的验证码:这真是一个体验好安全高的方案:官网:http://www.gee ...
- vue_drf之实现极验滑动验证码
一.需求 1,场景 我们在很多登录和注册场景里,为了避免某些恶意攻击程序,我们会添加一些验证码,也就是行为验证,让我们相信现在是一个人在交互,而不是一段爬虫程序.现在市面上用的比较多的,比较流行的是极 ...
- Python——破解极验滑动验证码
极验滑动验证码 以上图片是最典型的要属于极验滑动认证了,极验官网:http://www.geetest.com/. 现在极验验证码已经更新到了 3.0 版本,截至 2017 年 7 月全球已有十六万家 ...
- python验证码识别(2)极验滑动验证码识别
目录 一:极验滑动验证码简介 二:极验滑动验证码识别思路 三:极验验证码识别 一:极验滑动验证码简介 近些年来出现了一些新型验证码,不想旧的验证码对人类不友好,但是这种验证码对于代码来说识别难度上 ...
- Python 破解极验滑动验证码
Python 破解极验滑动验证码 测试开发社区 1周前 阅读目录 极验滑动验证码 实现 位移移动需要的基础知识 对比两张图片,找出缺口 获得图片 按照位移移动 详细代码 回到顶部 极验滑动验证码 以 ...
- selenium+java破解极验滑动验证码的示例代码
转自: https://www.jianshu.com/p/1466f1ba3275 selenium+java破解极验滑动验证码 卧颜沉默 关注 2017.08.15 20:07* 字数 3085 ...
- selenium处理极验滑动验证码
要爬取一个网站遇到了极验的验证码,这周都在想着怎么破解这个,网上搜了好多知乎上看到有人问了这问题https://www.zhihu.com/question/28833985,我按照这思路去大概实现了 ...
- selenium+java破解极验滑动验证码
摘要 分析验证码素材图片混淆原理,并采用selenium模拟人拖动滑块过程,进而破解验证码. 人工验证的过程 打开威锋网注册页面(https://passport.feng.com/?r=user/r ...
- thinkphp整合系列之极验滑动验证码geetest
给一个央企做官网,登录模块用的thinkphp验证码类.但是2019-6-10到12号,国家要求央企检验官网漏洞,防止黑客攻击,正直贸易战激烈升级时期,所以各事业单位很重视官网安全性,于是乎集团总部就 ...
随机推荐
- UML类图中箭头的含义
Explanation of the UML arrows Here's some explanations from the Visual Studio 2015 docs: UML Class D ...
- R class of subset of matrix and data.frame
a = matrix( c(2, 4, 3, 1, 5, 7), # the data elements nrow=2, # number of rows ...
- 题解——P1108低价购买(DP)
第一问是最长下降子序列,n很小,n^2可过,注意最长下降子序列的枚举顺序即可 ;i<=n;i++)//不要写错 ;j<i;j++)//不要打成<= ) b[i]=b[j]+; 第二问 ...
- Unity3D学习笔记(二十七):MVC框架下的背包系统(2)
Tools FileTools using System.Collections; using System.Collections.Generic; using UnityEngine; using ...
- 【Hadoop 分布式部署 二:分布式环境预备工作(主机名 IP地址等设置)】
1.首先使用工具连接上 这三台虚拟主机 2.配置主机名 切换到 root 用户 第一种方式 可以使用命令 hostname [要更改的主机名] 但是这种更改主机名的方式 ...
- 《C语言程序设计》指针篇<一>
指针 指针是C语言的精华,同时也是其中的难点和重点,我在近日对这一部分内容进行了重新的研读,把其中的一些例子自己重新编写和理解了一遍.此篇博客的内容即是我自己对此书例子的一些理解和总结. 一.大问题: ...
- Java 虚拟机 最易理解的 全面解析
先上一个最容易理解的类实例化的内存模型案例截图: 转载自:https://www.zybuluo.com/Yano/note/321063 周志明著的<深入理解 Java 虚拟机>的干货~ ...
- phpstorm软件配置端口问题
phpstorm默认的端口号是:63342 但是我装的apache服务器的默认端口是80 网上查找资料,都说可以加listen的端口,比如这里 #Listen 12.34.56.78:80 Lis ...
- R语言可视化学习笔记之ggpubr包—SCI文章图
转载:https://www.jianshu.com/p/678213d605a5?from=jiantop.com Hadley Wickham创建的可视化包ggplot2可以流畅地进行优美的可视化 ...
- 【Python】【有趣的模块】【Requests】session & cookie
保存http请求的状态(请求的上下文) [区别&联系] 1. cookie保存在客户端的浏览器,比如标识是哪个请求者.购物车应用等 session保存在服务端,http连接时无则创建,有则用现 ...