简介

借助注册登录demo能够很好了解JWT开发思路,适合用于多个场合的开发。

token的认证过程

  • 客户端使用用户名跟密码请求登录
  • 服务端收到请求,去验证用户名与密码
  • 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
  • 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
  • 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  • 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据。

session和cookie

session和cookies是有联系的,session就是服务端在客户端cookies种下的session_id, 服务端保存session_id所对应的当前用户所有的状态信息。每次客户端请求服务端都带上cookies中的session_id, 服务端判断是否有具体的用户信息,如果没有就去调整登录。

  • cookies安全性不好,攻击者可以通过获取本地cookies进行欺骗或者利用cookies进行CSRF攻击。

  • cookies在多个域名下,会存在跨域问题

  • session的信息是保存在服务端上面的,当我们node.js在stke部署多台机器的时候,需要解决共享session,所以引出来session持久化问题,所以session不支持分布式架构,无法支持横向扩展,只能通过数据库来保存会话数据实现共享。如果持久层失败会出现认证失败

JWT介绍

jwt是json web token的全称,他解决了session以上的问题,优点是服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展,什么情况下使用jwt比较合适,我觉得就是授权这个场景,因为jwt使用起来轻便,开销小,后端无状态,所以使用比较广泛。

JWT的组成

  1. Header
    它的组成部分包括两点

    参数类型-jwt
    签名的算法-hs256

最后会经过Base64加密方式进行编码,看起来是这个样子:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg
  1. Payload

    它的组成就是登陆用户的一些信息,和token发行和失效时间等;这些内容里面有一些是标准字段,你也可以添加其它需要的内容。

iss:Issuer,发行者
sub:Subject,主题
aud:Audience,观众
exp:Expiration time,过期时间
nbf:Not before
iat:Issued at,发行时间
jti:JWT ID

最后它也会通过Base64加密方式进行编码,仍然长的像上面的样子

  1. Signature
    它是由3个部分组成,先是用 Base64 编码的 header 和 payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端。

​ secret就是在最后第二次加密时加的盐,算是一个秘钥(只保留在服务器),不向外部透露。
来看一下最后的jwt完整版

eyJhbGciOiJIUzI1NiIsInR9cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I
kpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
8HILod0ncfVDnbKIPJJqLH998duF9DSDGkx3gRPNVI

登录注册demo实现

流程图
在这里插入图片描述

数据库

数据库需要除了id不能够相同,还需另一个值唯一来注册,大部分场合是使用邮箱,手机,微信号注册。此demo用户名不能相同,正如很多游戏注册的时候,起一些较为“网红”的游戏名称的时候会提醒:“游戏名已被占用,请重新起”等提示。
数据库的id是唯一标识,使用ai的自动增值。

user数据库表
字段
id//主键
name//用户名
password//密码
vip_status//判断用户是否为vip,默认0位普通用户,1位超级用户

前端

使用vant+webpack

  1. 注册
    使用用户名和密码注册,前端做一个表单验证

封装axios
重点介绍axios的interceptor请求拦截器简单处理token

const tokenBool = localStorage.getItem('user_token')//从浏览器的本地存储获取

server.interceptors.request.use(config => {
/**
*业务逻辑处理:略
**/
  if(tokenBool){//判断tokenBool是否存在
   
    config.headers.authorization =tokenBool//存在则在携带在请求的头部发送到服务器端验证
  }
  
  return config//返回请求
  
},err => {
  console.log('err',err);
  return Promise.reject(err);//返回错误信息
  
})

注册成功跳转到登录页面进行登录

当前端发送用户对象信息{name,password}到后台检验成功返回时,同时会生成一个加密的token到客户端,客户端获取到服务端返回的信息,含有token,把token存储到本地存储。
登录成功返回处理:

 const router = useRouter()//创建路由
    
    const onLoginSubmit = () => {
      console.log('onLoginSubmit', userInfo);
      userLoginReq(userInfo).then((res) => {
        console.log('userLoginReq',res);
        if(res.data.status==0){
            Toast.success('登录成功');
            setLocalStorge('user_token',res.data.token)//获取token,保存在本地浏览器
            router.push('/goToToken')//跳转到该页面,该页面向后端获取用户信息(可以理解为个人中心页面),需要携带token验证
          }
      
     })
      
    };

/goToToken路由页面

<template>
  <div>
    此页面会请求一个身份认证鉴权的请求
    <div>{{str}}</div>

  </div>
</template>

<script>
import { onMounted } from '@vue/runtime-core'
import {ref} from 'vue'
import {userMessageReq} from '@/server/user'

export default {
  setup(){
    let str = ref('')
    onMounted(()=>{
      userMessageReq().then((res)=>{
        console.log('res',res);
        str.value = res.data.message//超级会员身份认证处成功||普通会员身份认证成功
        
      })
    })

    
    return{
      str
    }
  }
}
</script>

后端

nodeJs的express快速搭建服务端

注册处理:

const db = require("../db/user_login.js")
//导入明文密码处理
const bcrypt = require('bcryptjs')
exports.regUser = (req, res) => {
  const userInfo = req.body
  console.log('userInfo',userInfo);
  
  if(!userInfo.username || !userInfo.password){
    return res.send({
      status : 1,
      message : '用户名或密码不合法!'
    })
  }
  const sqlStr = 'select * from userinfo where name = ?'

  db.query(sqlStr, [userInfo.username], (err, results) => {
    if(err){
      console.log('err',err);
      
      return res.errMsg(err)
    }

    if(results.length > 0){
      return res.errMsg('用户名被占用,请更换其他用户名!')
    }

    userInfo.password = bcrypt.hashSync(userInfo.password,10)
    //插入新用户
    const sql = 'insert into userinfo set ?'
    db.query(sql,{name:userInfo.username,password:userInfo.password},(err,results)=>{
      if(err){
        
        return res.errMsg(err) 
      }
      if(results.affectedRows !==1){

        return res.errMsg('注册用户失败,请重新注册')
      }

      res.errMsg('注册成功!',0)
    })
  })

}

登录处理:


//生成Token
const jwt = require('jsonwebtoken')

//生成Token
const jwt = require('jsonwebtoken')

const db = require("../db/user_login.js")
exports.regUser = (req, res) => {
  const userInfo = req.body
  console.log('userInfo',userInfo);
  
  if(!userInfo.username || !userInfo.password){
    return res.send({
      status : 1,
      message : '用户名或密码不合法!'
    })
  }
  const sqlStr = 'select * from userinfo where name = ?'

  db.query(sqlStr, [userInfo.username], (err, results) => {
    if(err){
      console.log('err',err);
      
      return res.errMsg(err)
    }

    if(results.length > 0){
      return res.errMsg('用户名被占用,请更换其他用户名!')
    }

    userInfo.password = bcrypt.hashSync(userInfo.password,10)
    //插入新用户
    const sql = 'insert into userinfo set ?'
    db.query(sql,{name:userInfo.username,password:userInfo.password},(err,results)=>{
      if(err){
        
        return res.errMsg(err) 
      }
      if(results.affectedRows !==1){

        return res.errMsg('注册用户失败,请重新注册')
      }

      res.errMsg('注册成功!',0)
    })
  })

}


//登录处理函数
exports.login  = (req,res) =>{

  const userinfo = req.body

  const sql = 'select * from userinfo where name = ?'

  db.query(sql, userinfo.username, function (err,results){
    //执行 SQL 语句失败
    if(err){
      return res.errMsg(err)
    }
    //执行 SQL语句成功, 但是查询到数据条数不等1
    if(results.length!==1){
      return res.errMsg('登录失败!')
    }
    //TODO:判断用户输入的登录密码是否和数据库中的密码一致
    //拿着用户输入的密码和数据库中存储的密码进行对比

    const compareResult = bcrypt.compareSync(userinfo.password, results[0].password)

    //如果对比的结果等于 false, 则证明用户输入的密码错误
    if(!compareResult){
      return res.errMsg('登录错误')
    }


    

    //TODO:登录成功,生成Token字符串

    //剔除完毕之后,user中只保留了用户的id, username,nickname,email 这四个属性的值
    const user = {...results[0],password:'',user_pic:''}
    //导入配置文件
    const config = require('../jwt_str')
    //生成Token字符串
    const tokenStr = jwt.sign(user,config.jwtSecretKey,{
      expiresIn:config.expiresIn
    })

    res.send({
      status:0,
      message:'登录成功',
      token:'Bearer '+tokenStr,
    })

  })
  
}

解析token操作

//一定要在路由之前解析token的中间件
//解析后的token可以在路由的处理函数的回调中(req,res)的req参数获取
const expressJWT = require('express-jwt')
app.use(expressJWT({
  secret:jwtStr.jwtSecretKey
}).unless({
  path:[/^\/userInfo\//,/^\/echart\//]
}))


//导入并注册用户路由模块
const userRouter = require('./router/user.js')
app.use('/userInfo',userRouter)

//访问该/userMessage的路由路径需要携带token
//前端/goToToken路由页面访问了改路由,所以需要在请求头携带token
const userMessageRouter = require('./router/userMessage.js')
app.use('/userMessage',userMessageRouter)

userMessage.js

const express = require('express')

const router = express.Router()

const {getToJWT} =require('../router_handle/userMessage_handle.js')

router.get('/getUserMessage',getToJWT)

module.exports = router

路由的回调处理函数

exports.getToJWT = (req, res) => {
  const user = {...req.user}
  let msg = ''
  if(user.vip_status){
    msg = '超级会员身份认证处成功'
  }else{
    msg = '普通会员身份认证成功'
  }
  res.send({
    status:1,
    message:msg,
    data:user
  })
}

注意事项

当登录超级vip用户账号时,返回一个token的值,如果直接不退出,跳到登录页面登录一个普通vip用户账号,会是什么样的情况

登录超级vip
在这里插入图片描述
在这里插入图片描述
登录普通vip
在这里插入图片描述
在这里插入图片描述
发现解析返回的token数据是一样!!!!因此在用户要换账号登录的时候必须要退出当前的账号才能再进行登录!!!

参考

该文章部分内容参考了各博客,在此处表明链接

补充:本人是前端新手,文章如有简述不对,请多多指教!!

Logo

前往低代码交流专区

更多推荐