前后端的身份认证

Web开发模式

客户端渲染

优点:

  • 前端耗时少。因为服务器端负责动态的生成 HTML 内容,浏览器只需要直接渲染页面即可。尤其是移动端,更加省电

  • 有利于SEO。因为服务端响应是完整的 HTML 页面内容,所以爬虫更容易爬取获得信息,更利于SEO(搜索引擎优化)。

缺点:

  • 占用服务器资源。服务器来完成页面的内容拼接,如果请求太多,会对服务器造成一定的访问压力。
  • 不利于前后端分离开发效率低。使用服务端渲染,则无法进行分工合作,尤其对于前端复杂度较高的项目,不利于项目高效开发。

前后端分离

前后端分离的概念

前后端分离的开发模式,依赖Ajax技术的广泛应用。简而言之,前后端分离的Web开发模式,就是后端只负责提供API接口,前端使用Ajax调用接口的开发模式。

前后端分离的优缺点

优点:

  • 开发体验好。前端专注于UI页面的开发,后端专注于api的开发,且前端有更多的选择性。
  • 用户体验好Ajax技术广泛应用,极大的提高了用户的体验,可以轻松的实现页面的局部刷新。
  • 减轻了服务器端的渲染压力

缺点:

  • 不利于SEO。因为完整的页面需要在客户端动态拼接完成,所有爬虫对无法爬取的页面的有效信息。(解决方案:利用VueReact 等前端框架的 SSR 技术能够很好的解决。)

如何选择 Web 开发模式

  • 比如企业级的网站,主要功能是展示而没有复杂的交互,并且需要良好的SEO,则这是我们就需要使用服务器端渲染。
  • 而类似后台管理项目,交互性比较强,不需要考虑SEO,那么就可以使用前后端分离的开发模式。

身份认证

身份认证指通过一定的手段,完成对用户身份的确认

不同开发模式下的身份认证

  • 服务端渲染推荐使用Session认证机制
  • 前后端分离推荐使用JWT认证机制

Session 认证机制

HTTP 协议

无状态性

  • HTTP协议的无状态性,指的是客户端的每次HTTP请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保存每次请求的状态

如何突破

  • 使用 Cookie

Cookie

Cookie简介

Cookie概念

Cookie是储存在用户浏览器中一段不超过4KB的字符串。它由一个名称(Name),一个值(Value)和其他几个用于控制Cookie有效期安全性使用范围的可选属性组成。

不同域名下的Cookie各自独立,每当客户端发起请求的时候,会自动把当前域名下的所有未过期的Cookie一同发送到服务器。

总结cookie的几大特性:

  • 自动发送
  • 域名独立
  • 过期时限
  • 4KB限制
Cookie在身份认证中的作用
  • 客户端在第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie ,客户端会自动将Cookie保存在浏览器中。
  • 随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie ,通过请求头的形式发送给服务器,服务器即可检验客户端身份。
Cookie不具有安全性

由于Cookie是储存在浏览器中的,而且浏览器也提供了读写Cookie的API,因此Cookie很容易被伪造,不具有安全性,因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器。

在 express 中使用 cookie-parser 中间件

1.下载
npm install cookie-parser
2.配置
var cookieParser = require('cookie-parser')
3.使用
// 根据是否记住来设置cookie
// 这里只记录邮箱 隐私的信息不宜记录在cookie里
if(req.body.remember === 'on'){
    res.cookie('user' , {
        email: req.body.email,
    },{
        // 设置记住时间为七天
        maxAge: 7*24*60*60*1000
    })
}else{
    // 如果这次没有设置记住 清理 user 的 okie
    res.clearCookie('user')
}

Session

session工作原理

session工作原理

express中使用 express-session 中间件

1.安装

Express 项目中,只需要安装 express-session 中间件,即可在项目中使用 Session 认证:

npm install express-session
2.配置

安装完成后,需要通过require来导入,再通过app.use()来注册session中间件:

// 导入
var session = require('express-session')

// 配置 (路由之前)
app.use(session({
    secret: 'myKeyBoard',        //配置加密字符串
    resave: false,			    //固定写法
    saveUninitialized: true		//固定写法
}))
3.存数据

通过req.session来访问和使用session对象:

// 登录成功 通过Session记录登录状态
req.session.user = data
4.读数据

通过req.session.xxx来读数据:

//通过是否登录来渲染首页
router.get('/' , function(req , res, next){
    if(req.session.user){
        User.findOne({
            email: req.session.user.email
        }).then(function(data){
            res.render('index.html' , {
                user: data,
            })
        })
    }else{
        res.render('index.html' , {
            user: null
        })
    }
})
5.清空数据

通过将指定数据设置为null或者使用 req.session.destroy() 函数:

注意req.session.destroy() 只会清空当前用户的session,每个用户之间的session独立

// 处理退出请求
router.get('/logout' , function(req , res){
    // 清除登录状态
    req.session.user = null  //将指定数据设置为`null`
    // req.session.destroy() // 或者使用 req.session.destroy() 函数
    // 刷新当前页面
    // a 标签是同步请求 服务端可以重定向
    res.redirect('/')
})

Session局限性

Session认证机制需要配合Cookie才能实现。由于Cookie默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域Session认证。此时推荐JWT身份认证。

JWT认证机制

JWT相关知识

1.JWT工作原理

JWT工作原理
用户的信息通过token字符串的形式,保存在客户端浏览器中。服务器通过还原token字符串的形式来认证用户的身份。

2.JWT组成部分

  • 通常由三部分组成,分别是Header(头部)Payload(有效荷载)Signature(签名)

  • Payload真正的用户信息,是用户信息加密过后生成的字符串。

  • HeaderSignature是安全性相关的部分,只是为了保证Token的安全性。

  • 三者使用英文分隔符.隔开,格式如下:

    Header.Payload.Signature
    

3.JWT使用方式

  • 客户端收到服务器返回的JWT之后,通常将它储存在localStoragesessionStorage中。

  • 此后,客户端每次与服务器通信,都要带上这个JWT字符串,从而进行身份验证。

  • 推荐的做法是把JWT放在HTTP请求头的Authorization字段中,格式如下:

    Authorization: Bearer <token>
    

在 express 中使用 JWT

1.安装相关包

npm install jsonwebtoken express-jwt
  • jsonwebtoken用于生成jwt字符串
  • express-jwt用于将jwt字符串解析还原成json对象

2.导入相关包

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

3.定义 secret 密钥

为了保证JWT字符串的安全性,防止JWT字符串再网络传输过程中被别人破解,我们需要专门定义一个用于加密解密secret密钥。

  • 当生成JWT字符串的时候,需要使用secret密钥对用户的信息进行加密,最终得到加密好的JWT字符串。
  • 当把JWT字符串解密还原成JSON对象的时候,需要使用secret密钥进行解密
const secretKey = 'mySecretKey'

4.登录后生成 token

jsonwebtoken包提供sign()方法,将用户的信息加密成JWT字符串,响应给客户端:

  • 参数1:用户的信息对象
  • 参数2:加密密钥
  • 参数3:配置对象,可配置token有效期
app.post('/api/post' , function(req , res){
    console.log(req.body)
    //省略了登录失败的 code ...
    res.send({
        status:200,
        message: '登录成功',
        token:jwt.sign({
            foo: 'bar',
        } , secretKey ,{ 
            //设置token的有效期 
            //'xxs' xx秒
            //'xxh' xx小时
            expiresIn: '30s' 
        })
    })
})

5.将 token 字符串解析为 JSON 对象

  • expressJWT({secret: secretKey})解析 Token 的中间件
  • .unless({path : [/^\/api\//]}) 用来指定哪些接口不需要访问权限(可以自己写正则 ,多个放在数组中用逗号隔开)
  • 配置好后会将解析出来的数据直接挂载到req.user 属性上
// expressJWT({secret: secretKey})  解析 Token 的中间件
// .unless({path : [/^\/api\//]})   用来指定哪些接口不需要访问权限(自己写正则)
// 将解析出来的数据直接挂载到 req.user 上
app.use(expressJWT({secret: secretKey , algorithms: ['HS256']}).unless({path : [/^\/api\//]}))

6. demo 代码

app.js

const express = require('express')
const bodyParser = require('body-parser')
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
const cors = require('cors')


const secretKey = 'mySecretKey'

var app = express()

// 解决跨域
app.use(cors())

//配置解析 post
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

// expressJWT({secret: secretKey})  解析 Token 的中间件
// .unless({path : [/^\/api\//]})   用来指定哪些接口不需要访问权限(自己写正则)
// 将解析出来的数据直接挂载到 req.user 上
app.use(expressJWT({secret: secretKey , algorithms: ['HS256']}).unless({path : [/^\/api\//]}))

app.post('/api/login' , function(req , res){
    return res.send({
        status:200,
        message: '登录成功',
        token:jwt.sign({
            foo: 'bar',
        } , secretKey , { expiresIn: '30s' })
    })
})

app.post('/admin' , function(req , res , next){
    console.log(req.user)
    return res.send({
        status:200,
        message: 'token有效',
    })
})

// 配置一个全局错误处理中间件
app.use(function(err, req, res, next){
    // token解析失败导致的错误
    if(err.name === 'UnauthorizedError') {
        return res.status(401).json({
            err_code: 500,
            message: '无效的token'
        })
    }
    // 其他原因导致的错误 
    return res.status(500).json({
        err_code: 500,
        message: err.message
    })
})

app.listen(5000 , function() {
    console.log('Running...')
})

前端 index.js 页面

<!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>
    <script src="./node_modules/jquery/dist/jquery.min.js"></script>
</head>
<body>
    <button id="btnPOST">POST</button>
    <button id="btnAdmin">Admin</button>
    <script>
        $(function(){
            $('#btnPOST').on('click' , function(){
                $.ajax({
                    type: 'POST',
                    url: 'http://127.0.0.1:5000/api/login',
                    data: {
                        username: '李四'
                    },
                    success: function(res){
                        console.log(res)
                        // 将 token 储存到 localStorage 中去
                        localStorage.setItem('token',JSON.stringify(res.token))
                    }
                })
            })
            $('#btnAdmin').on('click' , function(){
                let head = null
                console.log('Bearer'+" "+ localStorage.getItem('token'))
                $.ajax({
                    type: 'POST',
                    url: 'http://127.0.0.1:5000/admin',
                    headers:{
                        Authorization:'Bearer'+ " " + JSON.parse(localStorage.getItem('token')) 
                    },
                    data: {
                        username: '李四'
                    },
                    success: function(res){
                        console.log(res)
                    }
                })
            })
        })
    </script>
</body>
</html>

报错总结:

Cannot set headers after they are sent to the client

原因:客户端发送一次请求的时候,服务器端给出了多次响应

解决:每次响应后添加return

Error: algorithms should be set

解决:解密加入解密算法algorithms: ['HS256']

app.use(expressJWT({secret: secretKey , algorithms: ['HS256']}).unless({path : [/^\/api\//]}))
一直报UnauthorizedError
  • 有可能是token字符串设置有效期过短导致解析时发现token已经过期

  • 还有可能是前端页面发送的token格式有误

    正确的格式:

    //  请求头          空格 token字符串
    Authorization: Bearer <token>
    

    express-jwt源码
    该部分源码来自链接.

    //  从options中获取token
    if (options.getToken && typeof options.getToken === 'function') {
          try {
            token = options.getToken(req);
          } catch (e) {
            return next(e);
          }
    //从authorization中获取token
    } else if (req.headers && req.headers.authorization) {
          // 根据空格来划分
          var parts = req.headers.authorization.split(' '); 
          if (parts.length == 2) {
            var scheme = parts[0];
            var credentials = parts[1];
            if (/^Bearer$/i.test(scheme)) {
              // 如果前部分是 'Bearer' 则后半部分是token
              token = credentials;           
            } else {
              if (credentialsRequired) {
                return next(new UnauthorizedError('credentials_bad_scheme', { message: 'Format is Authorization: Bearer [token]' }));
              } else {
                return next();
              }
            }
            //如果以上俩种方法都得不到有效的 token 则报错
          } else {
            return next(new UnauthorizedError('credentials_bad_format', { message: 'Format is Authorization: Bearer [token]' }));
          }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值