• 用户认证session与token中,前后端的交互方式


    之前写过一篇关于用户认证的文章,不过是在理论方面。
    最近去看了两个课程,里面涉及到了用户认证实战方面,自己写的时候遇到了许多问题,所以想写篇文章记录一下。

    本文涉及技术栈有nodejs、express、fetch、xhr、localstorage。前置知识可参考我的笔记,下面的实例保存在目录04用户认证中。

    用户认证方式

    • session方式的用户认证适合服务器端渲染的web开发模式,该模式有利于SEO,常见于主要功能是展示而没有复杂的交互的网站。
    • token方式的用户认证适合前后端分离的web开发模式,该模式不利于SEO,常见于交互强的后台管理系统。是跨域资源共享的解决方案。

    01 session认证

    过程:客户端发送用户信息给服务端——服务端验证通过后,将数据通过session保存起来,然后把对应的cookie返回给客户端——客户端再次发送请求时携带cookie——服务端对比cookie,验证用户。

    要点

    • 该实例将页面及接口设置为同源,通过express.static()实现
    • 第三方中间件express-session
    • 共三个文件:app.js(服务器)、index.html(后台页)、login.html(登录页)

    app.js

    const express=require('express')
    const app=express()
    //解析请求体
    app.use(express.urlencoded({ extended: false }))
    app.use(express.json())
    // 静态资源
    app.use(express.static('public'))
    
    //使用中间件express-session
    const session=require('express-session')
    app.use(
        session({
            secret: 'itheima',
            resave: false,
            saveUninitialized: true,
        })
    )
    
    // 涉及两个status——0成功,1失败
    // 1 向session中存数据
    app.post('/api/login',(req,res)=>{
        const username=req.body.username
        const password=req.body.password
    
        // 这里会交给数据库检查,判断有没有这个用户
        // 如果没有该用户,返回登陆失败;如果有,往下走
        // 这边就省略该过程了,默认都通过
    
        // 将用户信息保存在session中
        req.session.user=req.body
        req.session.islogin = true
        res.send({status:0,msg:'登陆成功!'})
    })
    
    // 2 从session中获取数据
    app.get('/api/getusername',(req,res)=>{
        if(!req.session.islogin){
            return res.send({status:1,msg:'请重新登陆!'})
        }
        res.send({status:0,msg:'获取成功',username:req.session.user.username})
    })
    
    // 3 清空session
    app.post('/api/logout',(req,res)=>{
        req.session.destroy()
        res.send({
            status:0,
            msg:'已退出!'
        })
    })
    
    app.listen(8080,()=>{
        console.log('你的服务器运行在:http://localhost:8080')
    })
    

    讲个小插曲:
    上次跟着官网的推荐无脑加了个cookie: { secure: true },发现无法获取session。
    原因在于,它要求使用https,而我使用的是http,所以为了安全考量它不会给我保存cookie
    于是发送完请求后我去查看了下cookie,确实没有保存!
    所以如果发送了没有带cookie的请求,服务端是无法从session中获取数据的

    login.html

    <h1>首页</h1>
    <p id="ptxt"></p>
    <button id="btn">退出登录</button>
    <script>
      // 获取用户信息
      let p=document.getElementById('ptxt')
      // 一进来就调用下,判断有没有登陆过
      window.onload=function(){
        //因为是同源,所以可以省略前面的域名
        fetch('/api/getusername').then(res=>res.json()).then(res=>{
          if (res.status !== 0) {
              alert(res.msg)
              location.href = './login.html'
            } else {
              p.innerHTML='欢迎您:' + res.username
            }
        })
      }
    	
      // 退出
      let btn=document.getElementById('btn')
      btn.addEventListener('click',()=>{
        fetch('/api/logout',{
          method:'POST'
        }).then(res=>res.json()).then(res=>{
          if (res.status === 0) {
              location.href = './login.html'
            }
        })
      })
    </script>
    

    login.html

    <div>账号:<input id="username" /></div>
    <div>密码:<input id="password" /></div>
    <button id="submit">登陆</button>
    
    <script>
      let submit=document.getElementById('submit')
      submit.addEventListener('click',(e)=>{
        let username=document.getElementById('username').value
        let password=document.getElementById('password').value
    
        fetch('/api/login',{
          method:'POST',
          //指明请求体的数据类型
          headers: {
            'Content-Type': 'application/json'
          },
          body:JSON.stringify({
            username,
            password
          })
        }).then(res=>res.json()).then(res=>{
          if (res.status === 0) {
            location.href = './index.html'
          } else {
            alert(res.msg)
          }
        })
      })
    

    02 token认证

    过程:客户端发送用户信息——服务端验证通过后返回token——客户端将返回的token保存在localstorage中——在此访问需要用户认证的网页时,通过头部的Authorization字段携带token发送请求——服务端验证通过后返回目标页面

    要点

    • 使用vscode的扩展程序live serve快速给页面创建服务器(其他方式也可以,反正就是要让页面和接口不同源)
    • 第三方中间件jsonwebtoken(生成token)、express-jwt(解析token)、cors(跨域)
    • 注册拦截错误的中间件
    • 共三个文件:app.js(服务器)、index.html(后台页)、login.html(登录页)

    app.js

    const express=require('express')
    const app=express()
    const cors=require('cors')
    app.use(cors())
    app.use(express.json())
    
    //1.定义密钥
    const secretKey="mimayo~" 
    //2.生成jwt
    const enjwt=require('jsonwebtoken')
    //3.还原jwt,并自动对接口进行用户认证(除了api接口),会把解析出来的数据挂载到req.auth的属性上供开发者使用
    const {expressjwt: jwt}=require('express-jwt')
    app.use(
        jwt({
            secret:secretKey,
            algorithms: ["HS256"],
        }).unless({path:[/^\/api\//]})  //// 设置以/api/开头的不需要访问权限
    )
    
    // 1.认证加密
    app.post('/api/login',(req,res)=>{
        const user=req.body
        let {username,password}=user
    
        // 01 当用户调用了登陆接口,需要对其用户信息进行验证,这里假设数据库只有下面这个账户
        if(username!=='admin'||password!=='123456'){
            return res.send({status:0,msg:'账号或密码错误'})
        }
        
        // 02 验证成功后对用户信息进行加密  (不建议携带密码)
        const tokenStr=enjwt.sign(
            {username},
            secretKey,
            {expiresIn:'10h'} //有效期,也可以把单位换成s(秒),以便进行token期限测试
        )
        
        // 03 返回加密后的token
        res.send({
            status:200,
            message:'登陆成功',
            token:tokenStr
        })
    })
    
    //2 验证用户
    // 由于这不是/api接口,所以jwt中间件会自动对该接口进行用户验证,而不需要开发者自己写。
    app.get('/admin/getInfo',(req,res)=>{
        // 通过req.auth可以获取到token解析后的信息
        res.send({
            status:200,
            msg:'获取数据成功',
            data:req.auth
        })
    })
    
    // 3 处理错误:解析token错误或过期(错误中间件写后面)
    app.use((err,req,res,next)=>{
        if(err.name==='UnauthorizedError'){
            res.send({
                status:401,
                msg:'token已过期'
            })
        }
        res.send({
            status:500,
            msg:'请求错误'
        })
    })
    
    app.listen(8080,()=>{
        console.log('你的服务器运行在:http://localhost:8080')
    })
    

    login.html

    <div>账号:<input id="username" /></div>
    <div>密码:<input id="password" /></div>
    <button id="submit">登陆</button>
    
    <script>
      document.getElementById('submit').addEventListener('click',(e)=>{
        let username=document.getElementById('username').value
        let password=document.getElementById('password').value
    
        fetch('http://localhost:8080/api/login',{
          method:'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body:JSON.stringify({
            username,
            password
          })
        }).then(res=>res.json()).then(res=>{
          if (res.status === 200) {
            // 验证通过了,将token保存在localstorage中
            localStorage.setItem('token',res.token)
            location.href = `./index.html`
          } else {
            alert(res.msg)
          }
        })
      })
    </script>
    

    index.html

    <h1>首页</h1>
    <p id="ptxt"></p>
    <button id="btn">退出登录</button>
    <script>
      window.onload=function(){
        //1 拿到传过来的token
        let token=localStorage.getItem('token')
        if(!token){
          //2 当没有token时
          alert('您尚未登录,请登录后再执行此操作!')
          location.href = './login.html'
        }else{
          //3 当有token时,发送请求。由于fetch跨域起来很麻烦,所以我采用的是xhr
          let xhr=new XMLHttpRequest()
          xhr.open('GET','http://localhost:8080/admin/getInfo')
          xhr.setRequestHeader("Authorization", "bearer "+ token)
          xhr.send()
          xhr.onload=function(){
            let res=JSON.parse(this.response)
            if(res.status=='200'){
              document.getElementById('ptxt').innerHTML="欢迎您,"+res.data.username
            }else{
              alert(res.msg)
            }
          }
        }
      }
    
      let btn=document.getElementById('btn')
      btn.addEventListener('click',()=>{
        localStorage.clear()	//清空tokon
        location.href = './login.html'
      })
    </script>
    
  • 相关阅读:
    空心杯 电机
    scikit learn 安装
    python fromkeys() 创建字典
    python 清空列表
    mac最常用快捷键
    php while循环
    php 获取某个日期n天之后的日期
    php 添加时间戳
    php 格式化时间
    php 数值数组遍历
  • 原文地址:https://www.cnblogs.com/sanhuamao/p/16227089.html
Copyright © 2020-2023  润新知