Python笔记_72_用户的登陆认证_JWT_后端实现认证接口_前端实现登陆功能_接入极验验证

用户的登陆认证

JWT

Django REST framework JWT

在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token认证机制。

在这里插入图片描述

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

在这里插入图片描述

JWT的构成

JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

header

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
payload

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避时序攻击。

公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后将其进行base64加密,得到JWT的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature

JWT的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

# python 使用base64模块的算法,对数据进行编码转换[可以理解是加密]
import base64
data_dict = {
    "name":"xiaoming"
}
data_str = str(data_dict)
data1 = base64.b64encode(data_str)
print(data1)   # 'eyduYW1lJzogJ3hpYW9taW5nJ30='
data2 = base64.b64decode(data1)
print(data2)   # "{'name': 'xiaoming'}"
// javascript中也可以使用base64算法的,甚至还可以手动伪造jwt的token
// 因此这就是jwt的token为什么要有签名的原因!!!

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

在这里插入图片描述

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

关于签发和核验JWT,我们可以使用Django REST framework JWT扩展来完成。

文档网站:http://jpadilla.github.io/django-rest-framework-jwt/

安装配置JWT

安装

pip install djangorestframework-jwt

配置settings/dev.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}

import datetime
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
  • JWT_EXPIRATION_DELTA 指明token的有效期
生成jwt

Django REST framework JWT 扩展的说明文档中提供了手动签发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)
token = jwt_encode_handler(payload)

在用户注册或登录成功后,在序列化器中返回用户信息以后同时返回token即可。

后端实现登陆认证接口

Django REST framework JWT提供了登录获取token的视图,可以直接使用

在子应用路由urls.py中

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    path(r'authorizations/', obtain_jwt_token, name='authorizations'),
]

在主路由中,引入当前子应用的路由文件

urlpatterns = [
		...
    path('users/', include("users.urls")),
    # include 的值必须是 模块名.urls 格式,字符串中间只能出现一个圆点
]

接下来,我们可以通过postman来测试下功能

[外链图片转存失败(img-O1EGebmT-1565570737885)(assets/1553572597916.png)]

前端实现登陆功能

在登陆组件中找到登陆按钮,绑定点击事件

<button class="login_btn" @click="loginhander">登录</button>

methods中请求后端

export default {
  name: 'Login',
  data(){
    return {
        login_type: 0,
        username:"",
        password:"",
    }
  },
  methods:{
    // 发送登录请求
    loginHeader(){
      if(
        (this.username.length >=4  && this.username.length < 16) &&
        (this.password.length  >= 3 && this.password.length <= 18)
      ) {

        // 发送ajax
        this.$axios.post(this.$settings.Host+"/users/login/", {
          "username": this.username,
          "password": this.password,
        }).then(response=>{
            console.log(response.data)
        }).catch(error=>{
            console.log(error.response)
        })
      }
    }
  },

};
前端保存jwt

我们可以将JWT保存在cookie中,也可以保存在浏览器的本地存储里,我们保存在浏览器本地存储中

浏览器的本地存储提供了sessionStoragelocalStorage 两种:

  • sessionStorage 浏览器关闭即失效
  • localStorage 长期有效

使用方法

sessionStorage.变量名 = 变量值   // 保存数据
sessionStorage.变量名  // 读取数据
sessionStorage.removeItem("变量名") // 删除指定数据
sessionStorage.clear()  // 清除所有sessionStorage保存的数据

localStorage.变量名 = 变量值   // 保存数据
localStorage.变量名  // 读取数据
localStorage.removeItem("变量名") // 删除指定数据
localStorage.clear()  // 清除所有localStorage保存的数据

保存了登录状态信息以后,我们可以使用弹出进行提示,并使用路由功能进行页面的跳转

# elementUI本身就提供了页面弹窗和提示功能。
this.$confirm()   # 弹出确认框
this.$alert()     # 弹出警告框
this.$prompt()    # 弹出对话框
this.$message()   # 弹出提示框

登陆组件代码Login.vue

<template>
	<div class="login box">
		<img src="/static/image/Loginbg.3377d0c.jpg" alt="">
		<div class="login">
			<div class="login-title">
				<img src="/static/image/Logotitle.1ba5466.png" alt="">
				<p>帮助有志向的年轻人通过努力学习获得体面的工作和生活!</p>
			</div>
			<div class="login_box">
				<div class="title">
					<span @click="login_type=0" :class="login_type==0?'current':''">密码登录</span>
					<span @click="login_type=1" :class="login_type==1?'current':''">短信登录</span>
				</div>
				<div class="inp" v-if="login_type==0">
					<input v-model="username" type="text" placeholder="用户名 / 手机号码" class="user">
					<input v-model="password" type="password" name="" class="pwd" placeholder="密码">
					<div id="geetest1"></div>
					<div class="rember">
						<p>
							<input type="checkbox" class="no" v-model="remember"/>
							<span>记住密码</span>
						</p>
						<p>忘记密码</p>
					</div>
					<button class="login_btn" @click="loginHander">登录</button>
					<p class="go_login" >没有账号 <span>立即注册</span></p>
				</div>
				<div class="inp" v-show="login_type==1">
					<input v-model = "username" type="text" placeholder="手机号码" class="user">
					<input v-model = "password"  type="text" class="pwd" placeholder="短信验证码">
          <button id="get_code">获取验证码</button>
					<button class="login_btn">登录</button>
					<p class="go_login" >没有账号 <span>立即注册</span></p>
				</div>
			</div>
		</div>
	</div>
</template>

<script>
export default {
  name: 'Login',
  data(){
    return {
        login_type: 0,
        username:"",
        password:"",
        remember:false,
    }
  },

  methods:{
      loginHander(){
          // 登录功能实现
          // 1. 从vue中提取username和password, 验证数据
          if(this.username.length<1 || this.password.length<1){
              this.$message("对不起,请填写账号或密码!");
              return false;// 阻止函数继续执行
          }

          // 3. 发送ajax提交登录信息
          this.$axios.post(`${this.$settings.Host}/user/authorizations/`,{
            username:this.username,
            password:this.password,
          }).then(response=>{
              // 4. 接受jwt的token字符串
              // console.log(response.data);
              // 4.1 根据用户是否勾选了 记住密码 来判断使用不用的存储对象保存数据
              if(this.remember){
                  // 永久存储
                  // localStorage.setItem("user_token",response.data.token);
                  localStorage.user_token = response.data.token; // 上面一句和当前一句是同样意思
                  sessionStorage.removeItem("user_token");
              }else{
                  // 回话存储
                  sessionStorage.user_token = response.data.token;
                  localStorage.removeItem("user_token");
              }

              // 5.跳转到其他页面[首页]
              let self = this;
              this.$alert("欢迎回到路飞学城!~~","登录成功!",{
                  callback(){
                    self.$router.push("/"); // 跳转到指定地址的页面
                  }
              });
              // this.$router.push("/"); // 跳转到指定地址的页面
              // // this.$router.go(-1);    // 跳转返回上一页

          }).catch(error=>{
              // 获取后端相应的错误信息
              this.$alert(error.response.data.non_field_errors[0],"警告!");
          })

      }
  },

};
</script>

默认的返回值仅有token,我们还需在返回值中增加username和id,方便在客户端页面中显示当前登陆用户

通过修改该视图的返回值可以完成我们的需求。

users/utils.py 中,创建

def jwt_response_payload_handler(token, user=None, request=None):
    """
    自定义jwt认证成功返回数据
    """
    return {
        'token': token,
        'id': user.id,
        'username': user.username
    }

修改settings/dev.py配置文件

import datetime
# 设置jwt的格式
JWT_AUTH = {
    # 设置jwt的过期时间
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    # 设置jwt的返回内容
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler'
}

登陆组件代码Login.vue

             if(this.remember){
                  // 永久存储
                  // localStorage.setItem("user_token",response.data.token);
                  localStorage.user_token = response.data.token; // 上面一句和当前一句是同样意思
                  localStorage.user_id = response.data.user_id;
                  localStorage.user_name = response.data.user_name;
                  sessionStorage.removeItem("user_token");
                  sessionStorage.removeItem("user_id");
                  sessionStorage.removeItem("user_name");
              }else{
                  // 回话存储
                  sessionStorage.user_token = response.data.token; // 上面一句和当前一句是同样意思
                  sessionStorage.user_id = response.data.user_id;
                  sessionStorage.user_name = response.data.user_name;
                  localStorage.removeItem("user_token");
                  localStorage.removeItem("user_id");
                  localStorage.removeItem("user_name");
              }
多条件登录

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方法的思路:

  1. 根据username参数查找用户User对象,username参数可能是用户名,也可能是手机号
  2. 若查找到User对象,调用User对象的check_password方法检查密码是否正确

users/utils.py中编写:


def jwt_response_payload_handler(token, user=None, request=None):
    # 自定义登录以后的返回数据
    return {
        "token": token,
        "user_id": user.id,
        "user_name": user.username
    }

from django.contrib.auth.backends import ModelBackend
from .models import User
from django.db.models import Q
class UsernameMobileAuthBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None):
        try:
            user = User.objects.filter( Q(username=username) | Q(mobile=username) | Q(email=username) ).first()
        except User.DoesNotExist:
            return None

        if user and user.check_password(password) and self.user_can_authenticate(user):
            return user

在配置文件settings/dev.py中告知Django使用我们自定义的认证后端

AUTHENTICATION_BACKENDS = [
    'users.utils.UsernameMobileAuthBackend',
]

前端首页实现登陆状态的判断

Header子组件根据登陆状态显示不同登录信息。

Header.vue

<template>
    <div class="header-box">
      <div class="header">
        <div class="content">
          <div class="logo full-left">
            <router-link to="/"><img src="/static/image/logo.svg" alt=""></router-link>
          </div>
          <ul class="nav full-left">
              <li :key="key" v-for="nav,key in nav_list">
                <router-link v-if="nav.link.search('://') == -1" :to="nav.link">{{nav.name}}</router-link>
                <a v-else :href="nav.link">{{nav.name}}</a>
              </li>
          </ul>

          <div v-if="token" class="login-bar full-right">
            <div class="shop-cart full-left">
              <span class="shop-cart-total"></span>
              <img src="/static/image/cart.svg" alt="">
              <span><router-link to="/cart">购物车</router-link></span>
            </div>
            <div class="login-box login-box1 full-left">
              <router-link to="">学习中心</router-link>
              <el-menu width="200" class="member el-menu-demo" mode="horizontal">
                  <el-submenu index="2">
                    <template slot="title"><router-link to=""><img src="/static/image/logo@2x.png" alt=""></router-link></template>
                    <el-menu-item index="2-1">我的账户</el-menu-item>
                    <el-menu-item index="2-2"><router-link to="/user/order">我的订单</router-link></el-menu-item>
                    <el-menu-item index="2-3">我的优惠卷</el-menu-item>
                    <el-menu-item index="2-3"><span>退出登录</span></el-menu-item>
                  </el-submenu>
                </el-menu>
            </div>
          </div>

          <div v-if="!token" class="login-bar full-right">
            <div class="shop-cart full-left">
              <img src="/static/image/cart.svg" alt="">
              <span><router-link to="/cart">购物车</router-link></span>
            </div>
            <div class="login-box login-box2 full-left">
              <router-link to="/user/login">登录</router-link>
              &nbsp;|&nbsp;
              <span>注册</span>
            </div>
          </div>
        </div>
      </div>
    </div>
</template>

<script>
    export default {
      name: "Header",
      data(){
        return{
          token:"",
          nav_list:[]
        }
      },
      created(){
        this.checkUserLogin();
        this.get_header_nav();
      },
      methods:{
        get_header_nav(){
            this.$axios.get(`${this.$settings.Host}/nav/header/`).then(response=>{
                console.log(response.data);
                this.nav_list = response.data;
            })
        },
        checkUserLogin(){
            this.token = localStorage.user_token || sessionStorage.user_token;
        }
      }
    }
</script>

<style scoped>
.header-box{
  height: 80px;
}
.header{
  width: 100%;
  height: 80px;
  box-shadow: 0 0.5px 0.5px 0 #c9c9c9;
  position: fixed;
  top:0;
  left: 0;
  right:0;
  margin: auto;
  z-index: 99;
  background: #fff;
}
.header .content{
  max-width: 1200px;
  width: 100%;
  margin: 0 auto;
}
.header .content .logo{
  height: 80px;
  line-height: 80px;
  margin-right: 50px;
  cursor: pointer; /* 设置光标的形状为爪子 */
}
.header .content .logo img{
  vertical-align: middle;
}
.header .nav li{
  float: left;
  height: 80px;
  line-height: 80px;
  margin-right: 30px;
  font-size: 16px;
  color: #4a4a4a;
  cursor: pointer;
}
.header .nav li span{
  padding-bottom: 16px;
  padding-left: 5px;
  padding-right: 5px;
}
.header .nav li span a{
  display: inline-block;
}
.header .nav li .this{
  color: #4a4a4a;
  border-bottom: 4px solid #ffc210;
}
.header .nav li:hover span{
  color: #000;
}
.header .login-bar{
  height: 80px;
}
.header .login-bar .shop-cart{
  margin-right: 20px;
  border-radius: 17px;
  background: #f7f7f7;
  cursor: pointer;
  font-size: 14px;
  height: 28px;
  width: 88px;
  margin-top: 30px;
  line-height: 32px;
  text-align: center;
  position: relative;
}
.header .login-bar .shop-cart:hover{
  background: #f0f0f0;
}
.header .login-bar .shop-cart img{
  width: 15px;
  margin-right: 4px;
  margin-left: 6px;
}
.header .login-bar .shop-cart span{
  margin-right: 6px;
}
.header .login-bar .shop-cart-total{
    width: 16px;
    height: 16px;
    line-height: 17px;
    font-size: 12px;
    color: #fff;
    text-align: center;
    background: #fa6240;
    border-radius: 50%;
    transform: scale(.8);
    position: absolute;
    left: 16px;
    top: -1px;
}
.header .login-bar .login-box1{
  margin-top: 16px;
}
.header .login-bar .login-box2{
  margin-top: 34px;
}
.header .login-bar .login-box span{
  color: #4a4a4a;
  cursor: pointer;
}
.header .login-bar .login-box span:hover{
  color: #000000;
}
.member{
    display: inline-block;
    height: 34px;
    margin-left: 20px;
}
.member img{
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: inline-block;
}
.member img:hover{
  border: 1px solid yellow;
}
</style>

因为elementUI提供的下拉菜单样式需要调整,所以我们在app.vue中,修改elementUI样式。

<style>
/* 声明全局样式和项目的初始化样式 */
body,h1,h2,h3,h4,p,table,tr,td,ul,li,a,form,input,select,option,textarea{
  margin:0;
  padding: 0;
  font-size: 15px;
}
a{
  text-decoration: none;
  color: #333;
}
ul,li{
  list-style: none;
}
table{
  border-collapse: collapse; /* 合并边框 */
}
img{
  width:100%;
}
/* 工具的全局样式 */
.full-left{
  float: left!important;
}
.full-right{
  float: right!important;
}

/* ElementUI的样式兼容 */
[class*=" el-icon-"], [class^=el-icon-]{
  font-size: 50px;
}
.el-carousel__arrow{
  width: 120px;
  height: 120px;
}
.el-checkbox__input.is-checked .el-checkbox__inner,
.el-checkbox__input.is-indeterminate .el-checkbox__inner{
  background: #ffc210;
  border-color: #ffc210;
  border: none;
}
.el-checkbox__inner:hover{
  border-color: #9b9b9b;
}
.el-checkbox__inner{
  width: 16px;
  height: 16px;
  border: 1px solid #9b9b9b;
  border-radius: 0;
}
.el-checkbox__inner::after{
  height: 9px;
  width: 5px;
}
.el-submenu__icon-arrow::before{
  content:"";
}
.el-submenu__title{
  padding: 0;
}
.el-menu--popup{
  min-width: 140px;
  margin-left: -100px;
}


.geetest_holder.geetest_wind{
  min-width: 100%!important;
  margin-top: 20px;
}

.el-pagination button, .el-pagination span:not([class*=suffix]),
.el-input--mini,
.el-pagination__sizes .el-input .el-input__inner,
.el-pager li{
  font-size: 16px;
}

.course-brief img{
  width: 100%;
}
</style>

在登录认证中接入极验验证

官网: https://www.geetest.com/first_page/

注册登录以后,即进入登录后台,选择行为验证。

在这里插入图片描述

[外链图片转存失败(img-uOtaOgOD-1565570737890)(assets/1553576294430.png)]

[外链图片转存失败(img-EzJMird5-1565570737892)(assets/1553576382897.png)]

[外链图片转存失败(img-1rettcEX-1565570737893)(assets/1553576348110.png)]

[外链图片转存失败(img-77CprBag-1565570737893)(assets/1553576402354.png)]

ID: 6e15c7e*********************57790
KEY: db327f*********************ff057

接下来,就可以根据官方文档,把验证码集成到项目中了

文档地址:https://docs.geetest.com/install/overview/start/

下载和安装验证码模块包,不是要我们安装这个第三方模块,而是使用git复制sdk到电脑中。

git clone https://github.com/GeeTeam/gt3-python-sdk.git

分析完官方的sdk目录以后,我们就可以把验证码功能集成到项目中了,首先我们在项目安装验证码的python依赖模块

pip install requests

把验证码模块放置在libs目录中

[外链图片转存失败(img-Rbr01pXN-1565570737894)(3-登录注册.assets/1565250990113.png)]

并在users子应用下创建验证码视图类,并提供验证码和校验验证码的视图方法。

views.py

from rest_framework.views import APIView
from luffyapi.libs.geetest import GeetestLib
from rest_framework.response import Response
from django.conf import settings

class CaptchaAPIView(APIView):
    def get(self,request):
        """生成验证码的流水号和状态"""
        gt = GeetestLib(settings.PC_GEETEST_ID, settings.PC_GEETEST_KEY)
        status = gt.pre_process(settings.PC_GEETEST_USER_ID)
        response_str = gt.get_response_str()
        return Response(response_str)

    def post(self,request):
        """二次验证"""
        gt = GeetestLib(settings.PC_GEETEST_ID, settings.PC_GEETEST_KEY)
        challenge = request.data.get(gt.FN_CHALLENGE, '')
        validate = request.data.get(gt.FN_VALIDATE, '')
        seccode = request.data.get(gt.FN_SECCODE, '')

        result = gt.success_validate(challenge, validate, seccode, settings.PC_GEETEST_USER_ID)
        if not result:
            result = gt.failback_validate(challenge, validate, seccode)

        return Response({"status": result})

配置settings/dev.py

# 极验验证码配置[ Ctrl+shift+U,把选中内容中的字母转换成大小写 ]
PC_GEETEST_ID = "6e1***************************790"
PC_GEETEST_KEY = "db3***************************057"
PC_GEETEST_USER_ID = 'test'

路由注册:
urls.py

from django.urls import path, re_path
from . import views
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    path(r'authorizations/', obtain_jwt_token, name='authorizations'),
    path(r'captcha/', views.CaptchaAPIView.as_view()),
]
前端获取显示并校验验证码

把下载回来的验证码模块包中的gt.js放置到前端项目中,并在main.js中引入

// 导入极验验证码的js文件
import '../static/js/gt.js'

Login.vue代码

<template>
	<div class="login box">
		<img src="/static/image/Loginbg.3377d0c.jpg" alt="">
		<div class="login">
			<div class="login-title">
				<img src="/static/image/Logotitle.1ba5466.png" alt="">
				<p>帮助有志向的年轻人通过努力学习获得体面的工作和生活!</p>
			</div>
			<div class="login_box">
				<div class="title">
					<span @click="login_type=0" :class="login_type==0?'current':''">密码登录</span>
					<span @click="login_type=1" :class="login_type==1?'current':''">短信登录</span>
				</div>
				<div class="inp" v-if="login_type==0">
					<input v-model="username" type="text" placeholder="用户名 / 手机号码" class="user">
					<input v-model="password" type="password" name="" class="pwd" placeholder="密码">
					<div id="geetest1"></div>
					<div class="rember">
						<p>
							<input type="checkbox" class="no" v-model="remember"/>
							<span>记住密码</span>
						</p>
						<p>忘记密码</p>
					</div>
					<button class="login_btn" @click="loginHander">登录</button>
					<p class="go_login" >没有账号 <span>立即注册</span></p>
				</div>
				<div class="inp" v-show="login_type==1">
					<input v-model = "username" type="text" placeholder="手机号码" class="user">
					<input v-model = "password"  type="text" class="pwd" placeholder="短信验证码">
          <button id="get_code">获取验证码</button>
					<button class="login_btn">登录</button>
					<p class="go_login" >没有账号 <span>立即注册</span></p>
				</div>
			</div>
		</div>
	</div>
</template>

<script>
export default {
  name: 'Login',
  data(){
    return {
        login_type: 0,
        username:"",
        password:"",
        remember:false,
    }
  },
  methods:{
      handlerPopup(captchaObj) {
          // 把vue对象保存到一个变量,方便其他对象使用
          let self = this;
          console.log(captchaObj);
          // 成功的回调
          captchaObj.onSuccess(function () {
              var validate = captchaObj.getValidate();

              // 使用axios发送ajax
              self.$axios.post(`${self.$settings.Host}/user/captcha/`,{
                  geetest_challenge: validate.geetest_challenge,
                  geetest_validate: validate.geetest_validate,
                  geetest_seccode: validate.geetest_seccode
              }).then(response=>{
                  if(response.data.status){
                      // 验证成功! 把登录信息发送给后端!
                      self.ajax_login();

                  }else{
                      // 验证失败!
                      self.$message("对不起,验证码验证失败!");
                  }
              });
          });

          // 将验证码加到id为captcha的元素里
          document.querySelector("#geetest1").innerHTML = "";
          // document.getElementById("geetest1").innerHTML = "";  // 这一句也可以
          captchaObj.appendTo("#geetest1");
      },
      loginHander(){
          // 登录功能实现
          // 1. 从vue中提取username和password, 验证数据
          if(this.username.length<1 || this.password.length<1){
              this.$message("对不起,请填写账号或密码!");
              return false;// 阻止函数继续执行
          }

          // 2. 提供极验验证码
          this.$axios.get(`${this.$settings.Host}/user/captcha`,{
              responseType:"json",
          }).then(response=>{
              // 获取到流水号以后,就要对验证码的配置进行初始化
              initGeetest({
                gt: response.data.gt,
                challenge: response.data.challenge,
                product: "popup", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效
                offline: !response.data.success // 表示用户后台检测极验服务器是否宕机,一般不需要关注
            }, this.handlerPopup);
          });
      },
      ajax_login(){
          // 发送ajax提交登录信息
          this.$axios.post(`${this.$settings.Host}/user/authorizations/`,{
            username:this.username,
            password:this.password,
          }).then(response=>{
              // 4. 接受jwt的token字符串
              // console.log(response.data);
              // 4.1 根据用户是否勾选了 记住密码 来判断使用不用的存储对象保存数据
              if(this.remember){
                  // 永久存储
                  // localStorage.setItem("user_token",response.data.token);
                  localStorage.user_token = response.data.token; // 上面一句和当前一句是同样意思
                  localStorage.user_id = response.data.user_id;
                  localStorage.user_name = response.data.user_name;
                  sessionStorage.removeItem("user_token");
                  sessionStorage.removeItem("user_id");
                  sessionStorage.removeItem("user_name");
              }else{
                  // 回话存储
                  sessionStorage.user_token = response.data.token; // 上面一句和当前一句是同样意思
                  sessionStorage.user_id = response.data.user_id;
                  sessionStorage.user_name = response.data.user_name;
                  localStorage.removeItem("user_token");
                  localStorage.removeItem("user_id");
                  localStorage.removeItem("user_name");
              }

              // 5.跳转到其他页面[首页]
              let self = this;
              this.$alert("欢迎回到路飞学城!~~","登录成功!",{
                  callback(){
                    self.$router.push("/"); // 跳转到指定地址的页面
                  }
              });
              // this.$router.push("/"); // 跳转到指定地址的页面
              // // this.$router.go(-1);    // 跳转返回上一页

          }).catch(error=>{
              // 获取后端相应的错误信息
              this.$alert(error.response.data.non_field_errors[0],"警告!");
          })
      }
  },

};
</script>

<style scoped>
.box{
	width: 100%;
  height: 100%;
	position: relative;
  overflow: hidden;
}
.box img{
	width: 100%;
  min-height: 100%;
}
.box .login {
	position: absolute;
	width: 500px;
	height: 400px;
	left: 0;
  margin: auto;
  right: 0;
  bottom: 0;
  top: -338px;
}
.login .login-title{
     width: 100%;
    text-align: center;
}
.login-title img{
    width: 190px;
    height: auto;
}
.login-title p{
    font-size: 18px;
    color: #fff;
    letter-spacing: .29px;
    padding-top: 10px;
    padding-bottom: 50px;
}
.login_box{
    width: 400px;
    height: auto;
    background: #fff;
    box-shadow: 0 2px 4px 0 rgba(0,0,0,.5);
    border-radius: 4px;
    margin: 0 auto;
    padding-bottom: 40px;
}
.login_box .title{
	font-size: 20px;
	color: #9b9b9b;
	letter-spacing: .32px;
	border-bottom: 1px solid #e6e6e6;
	 display: flex;
    	justify-content: space-around;
    	padding: 50px 60px 0 60px;
    	margin-bottom: 20px;
    	cursor: pointer;
}
.login_box .title .current{
	    color: #4a4a4a;
    	border-bottom: 2px solid #84cc39;
}

.inp{
	width: 350px;
	margin: 0 auto;
}
.inp input{
    outline: 0;
    width: 100%;
    height: 45px;
    border-radius: 4px;
    border: 1px solid #d9d9d9;
    text-indent: 20px;
    font-size: 14px;
    background: #fff !important;
}
.inp input.user{
    margin-bottom: 16px;
}
.inp .rember{
     display: flex;
    justify-content: space-between;
    align-items: center;
    position: relative;
    margin-top: 10px;
}
.inp .rember p:first-of-type{
    font-size: 12px;
    color: #4a4a4a;
    letter-spacing: .19px;
    margin-left: 22px;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-align: center;
    align-items: center;
    /*position: relative;*/
}
.inp .rember p:nth-of-type(2){
    font-size: 14px;
    color: #9b9b9b;
    letter-spacing: .19px;
    cursor: pointer;
}

.inp .rember input{
    outline: 0;
    width: 30px;
    height: 45px;
    border-radius: 4px;
    border: 1px solid #d9d9d9;
    text-indent: 20px;
    font-size: 14px;
    background: #fff !important;
}

.inp .rember p span{
    display: inline-block;
  font-size: 12px;
  width: 100px;
  /*position: absolute;*/
/*left: 20px;*/

}
#geetest{
	margin-top: 20px;
}
.login_btn{
     width: 100%;
    height: 45px;
    background: #84cc39;
    border-radius: 5px;
    font-size: 16px;
    color: #fff;
    letter-spacing: .26px;
    margin-top: 30px;
}
.inp .go_login{
    text-align: center;
    font-size: 14px;
    color: #9b9b9b;
    letter-spacing: .26px;
    padding-top: 20px;
}
.inp .go_login span{
    color: #84cc39;
    cursor: pointer;
}
</style>

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值