常见用户验证方式总结


整理一下前端常见的用户验证方式

本文示例:
前端:js原生
后端:express

文件结构:
structure

Http基本验证

http基本验证的流程是发送验证请求时将用户名和密码拼接成字符串,使用base64转义,然后作为header authorization 的值发送,服务端检测request head内是否存在authorization,如果存在获取authorization的值验证用户名和密码,如果不存在就响应一个code 401,同时要相应header WWW-Authenticate:Basic realm=”Secure Area” 这个header和401 code会触发浏览器的默认行为,弹出登录窗口

下面看代码

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    #root{
        width: 300px;
        display: flex;
        flex-direction: column;
    }
</style>
<body>
    <div id="root">
    </div>
</body>
<script>
    // 登录
    let login = () =>{ 
        let xhr = new XMLHttpRequest()
        xhr.open('post', 'http://127.0.0.1:3000/auth', true)
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4 && xhr.status === 200){
                console.log(xhr.response)
                if(JSON.parse(xhr.response).code === 200){

                    // 如果验证通过显示welcome
                    root.innerHTML = '<h1>welcom</h1>'
                }
            }
        }
        xhr.send()
    }

window.onload = function(){
    let root = document.getElementById('root')
    login()
}
</script>
</html>

后端

const express = require('express')
const bodyParser = require('body-parser')


const app = express()

// html放在public下
app.use(express.static('public'))

//解析post
app.use(express.urlencoded({
    extended:true
}));
app.use(express.json())

app.post('/auth', function(req, res){
    if(req.headers['authorization']){

        //获取authorization header并base64转义
        let credsArr = Buffer((req.headers['authorization'].split(' '))[1], 'base64').toString().split(':')

        //获取用户名和密码
        let user = credsArr[0]
        let pwd = credsArr[1]

        //验证用户名和密码
        if(user === 'hello' && pwd === 'world'){
            res.statusCode === 200
            res.end(JSON.stringify({code: 200, msg: 'success'}))
        }else{
            res.statusCode = 401
            res.setHeader('WWW-Authenticate', 'Basic realm="Secure Area"');
            res.end()
        }
    }else{
        res.statusCode = 401
        res.setHeader('WWW-Authenticate', 'Basic realm="Secure Area"');
        res.end()
    }

})

app.listen(3000, function(){
    console.log("listen port 3000")
})

工作流程总结:
summary1

常用的验证方式,原理是浏览器每一次会话都会产生一个session对象,该对象可保存在内存或文件内,第一次登录时更具特定的规则产生一个session保存在服务端,然后将 session的id 保存在cookie内返回,下一次请求时携带cookie就可以根据cookie内的id获取到session对象,查看是否需要验证

这里要用到包express-session, 管理session更方便

npm i express-session --save

对于所有的请求,我们指定一个生成session的规则

app.use(session({
    secret: 'mySecret',         // 用于加密的字符串,可以自定义
    saveUninitialized: false,   //用户未登录是否生成空session
    resave: true, 
    cookie : {
        maxAge : 10000,         // cookie的有效期 ms
    },
}))

然后可以通过req.session访问到该会话的session( 借助于cookie,用户即使关掉浏览器重新登录也能拿到session )

贴一下demo完整代码

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    #root{
        width: 300px;
        display: flex;
        flex-direction: column;
    }
</style>
<body>
    <div id="root">
        <input class="user" />
        <input class="pwd" />
        <button class="submit">submit</button>
    </div>
</body>
<script>
    let login = (user, pwd) =>{ 
        let xhr = new XMLHttpRequest()
        xhr.open('post', 'http://127.0.0.1:3000/auth', true)
        xhr.setRequestHeader("Content-Type", "application/json")
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4 && xhr.status === 200){
                if(JSON.parse(xhr.response).msg === 'success'){
                    root.innerHTML = '<h1>Login Success</h1>'
                }else{
                    alert('登录失败')
                }
            }
        }
        xhr.send(JSON.stringify({user: user, pwd: pwd}))
    }

    let autoLogin = () =>{
        let xhr = new XMLHttpRequest()
        xhr.open('post', 'http://127.0.0.1:3000/autoLogin', true)
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4 && xhr.status === 200){
                if(JSON.parse(xhr.response).msg === 'success'){
                    root.innerHTML = '<h1>Auto Login Success!</h1>'
                }
            }
        }
        xhr.send()
    }

window.onload = function(){
    let user = ''
    let pwd = ''

    //获取DOM
    let root = document.getElementById('root')
    let userInput = document.getElementsByClassName('user')[0]
    let pwdInput = document.getElementsByClassName('pwd')[0]
    let submitBtn = document.getElementsByClassName('submit')[0]

    //监听数据变化
    userInput.addEventListener('input', function(e){user = e.target.value})
    pwdInput.addEventListener('input', function(e){pwd = e.target.value})
    submitBtn.addEventListener('click', function(e){login(user, pwd)})

    //自动登录
    autoLogin()
}
</script>
</html>

后端

const express = require('express')
const session = require('express-session')


const app = express()

app.use(express.static('public'))

//设置session
app.use(session({
    secret: 'mySecret',
    saveUninitialized: false,
    resave: true, 
    cookie : {
        maxAge : 10000, 
    },
}))

//解析post
app.use(express.urlencoded({
    extended:true
}));
app.use(express.json())

//自动登录验证
app.post('/autoLogin', function(req, res){
    if(req.session && req.session.isLogin){
        res.end(JSON.stringify({code: 200, msg: "success"}))
    }else{
        res.end(JSON.stringify({code: -1, msg: "error"}))
    }
})

app.post('/auth', function(req, res){
    if(req.body.user === 'hello' && req.body.pwd === 'world'){
        req.session.isLogin = true
        res.end(JSON.stringify({code: 200, msg: "success"}))
    }else{
        res.end(JSON.stringify({code: -1, msg: "error"}))
    }

})

app.listen(3000, function(){
    console.log("listen port 3000")
})

工作流程总结

summary2

token(jwt)验证

这里要用到jsonwebtoken

npm i jsonwebtoken --save

思路和session差不多,只不过将服务端加密签发的token保存在客户端,保存方式由客户端决定,相对于session可以缓解服务器压力

具体的工作流程:

  1. 客户登录后服务端检查是否携带了token(token是否有效),如有效跳过登录验证过程,无效则需要重新登录
  2. token无效,用户重新输入用户名和密码
  3. 服务端验证用户名密码有效性,若有效签发新的token
  4. 客户端拿到新的token后保存在本地,下次登录时携带

看一下几个核心代码

function setToken(user, pwd){
    const token = jwt.sign({user: user, pwd: pwd}, key, {expiresIn:10})
    return token
}

jwt.sing用于签发token , 和session的secret一样,签发token需要一个key(不建议token保存密码,这里只是测试一下)

function verifyToken(token){
    return jwt.verify(token, key)
}

使用之前的key解析token,返回的对象内包含之前传输的user和pwd

贴一下完整代码

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    #root{
        width: 300px;
        display: flex;
        flex-direction: column;
    }
</style>
<body>
    <div id="root">
        <input class="user" />
        <input class="pwd" />
        <button class="submit">submit</button>
    </div>
</body>
<script>
    let login = (user, pwd) =>{ 
        let xhr = new XMLHttpRequest()
        xhr.open('post', 'http://127.0.0.1:3000/auth', true)
        xhr.setRequestHeader("Content-Type", "application/json")
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4 && xhr.status === 200){
                if(JSON.parse(xhr.response).msg === 'success'){
                    localStorage.setItem('token', JSON.parse(xhr.response).token)
                    root.innerHTML = '<h1>Login Success</h1>'
                }else{
                    alert('登录失败')
                }
            }
        }
        xhr.send(JSON.stringify({user: user, pwd: pwd}))
    }

    let autoLogin = () =>{
        let xhr = new XMLHttpRequest()
        xhr.open('post', 'http://127.0.0.1:3000/autoLogin', true)
        xhr.setRequestHeader("Content-Type", "application/json")
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4 && xhr.status === 200){
                if(JSON.parse(xhr.response).msg === 'success'){
                    root.innerHTML = '<h1>Auto Login Success!</h1>'
                }
            }
        }
        xhr.send(JSON.stringify({token: localStorage.getItem('token')}))
    }

window.onload = function(){
    let user = ''
    let pwd = ''

    //获取DOM
    let root = document.getElementById('root')
    let userInput = document.getElementsByClassName('user')[0]
    let pwdInput = document.getElementsByClassName('pwd')[0]
    let submitBtn = document.getElementsByClassName('submit')[0]

    //监听数据变化
    userInput.addEventListener('input', function(e){user = e.target.value})
    pwdInput.addEventListener('input', function(e){pwd = e.target.value})
    submitBtn.addEventListener('click', function(e){login(user, pwd)})

    //自动登录
    autoLogin()
}
</script>
</html>

后端

const express = require('express')
const jwt = require('jsonwebtoken')

const app = express()

app.use(express.static('public'))

//解析post
app.use(express.urlencoded({
    extended:true
}));
app.use(express.json())

const key = 'myKey'

function setToken(user, pwd){
    const token = jwt.sign({user: user, pwd: pwd}, key, {expiresIn:10})
    return token
}

function verifyToken(token){
    return jwt.verify(token, key, (err) =>{
        return null
    })
}


//自动登录验证
app.post('/autoLogin', function(req, res){
    if(req.body && req.body.token){
        let verifyRes = verifyToken(req.body.token)
        if(verifyRes && verifyRes.user === 'hello' && verifyRes.pwd === 'world'){
            res.end(JSON.stringify({code: 200, msg: 'success'}))
        }
    }else{
        res.end(JSON.stringify({code: -1, msg: 'failed'}))
    }
})

app.post('/auth', function(req, res){
    console.log(req.body)
    if(req.body.user === 'hello' && req.body.pwd === 'world'){
        const token = setToken(req.body.user, req.body.pwd)
        res.end(JSON.stringify({code: 200, token: token, msg: 'success'}))
    }else{
        res.end(JSON.stringify({code: -1, msg: "error"}))
    }

})

app.listen(3000, function(){
    console.log("listen port 3000")
})

工作流程总结
summary3


Author: Maple
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Maple !
  TOC