用户请求鉴权的实现方案

为什么需要鉴权

  1. 鉴权:是指是指验证用户是否拥有访问系统的权利。

  2. 为什么要鉴权:对用户进行鉴权,防止非法用户占用网络资源,非法用户接入网络,被骗取关键信息。

    1. 明文传输风险:如果您直接在网页或应用程序中输入账号和密码,这些信息可能以明文形式通过网络传输。这会使您的账号和密码容易受到窃听者的攻击,尤其是在未加密的连接上。

    2. 密码存储风险:如果网站或应用程序以明文形式存储您的密码,那么如果其数据库遭到入侵,攻击者可以轻松获取您的密码。这将对您的账户安全构成威胁。

    3. 缺乏多因素认证:直接输入账号和密码通常不提供多因素认证,这是一种额外的安全层,可以在您登录时要求您提供额外的身份验证信息,如短信验证码或令牌,以增强账户安全。

    4. 减少用户动手频率:有了 token ,可以避免用户多次获取信息时重复输入密码,浏览器不必保存密码,只需通过 token 与服务器进行通信验证

  3. Token可以有效减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。

初步调研

市面上通常有两种流行的鉴权方案:

Session :此方案适用于非跨域请求,实现较为简单

Session 原理图解,服务器端如何保证一次会话范围内多次获取的Session对象是同一个

由图可见Session是依赖于Cookie而存在的

session 认证原理

于是就要聊到 Cookie 了

对于 Cookie 大家应该都不陌生,很多网站都会询问你是否允许使用 Cookie 。它十分流行,但它并不具备安全性,

这也是为什么网站需要询问你是否允许使用。

https://cloud.tencent.com/developer/article/1705905 参考这篇文章,发现如果采用这种解决方案,则需要将Session和Cookie相结合来使用,使用起来较为困难且不灵活

JWT :此方案适用于跨域请求,是市面上最为流行的解决方案

JWT 认证流程

在身份验证中,当用户成功登录系统时,授权服务器将会把 JSON Web Token 返回给客户端,用户需要将此凭证信息存储在本地(cookie或浏览器缓存)。当用户发起新的请求时,需要在请求头中附带此凭证信息,当服务器接收到用户请求时,会先检查请求头中有无凭证,是否过期,是否有效。如果凭证有效,将放行请求;若凭证非法或者过期,服务器将回跳到认证中心,重新对用户身份进行验证,直至用户身份验证成功。以访问 API 资源为例,下图显示了获取并使用 JWT 的基本流程:

JWT 认证流程

这两者实际上都可以用于我们的项目中,但 JWT 的认证流程更为简单,安全性更高,且能解决跨域需求,于是选择 JWT 作为鉴权的选择

初次上手实现

在查询学习相关内容之后,手写了一套简单的代码,使用的是 jsonwebtoken 这个库:

并在之前的注册登录中加入如下代码:

又进行一番研究之后,改为使用 express-jwt 库,兴致冲冲地去运行服务时却告诉我

询问GPT却得到了这样的回复

难绷

  • 看来似乎不是我的问题(毕竟gpt也妹搞清楚),于是经过查询文档和查找论坛,发现是库的升级导致了老的语法无法使用,需要在函数中加上这样一句:

  • 再次运行,发现又出错了,网上也难以找到解决方法,无奈只能自行研究。

    几十分钟之后发现了奇怪的问题,总之问题解决了

验证成果

  • 当正确输入token时:

当输入错误token时:

结果是正常的,但返回值不太好看,于是添加一个错误中间件:

router.use((err, req, res, next) => {
  // token验证失败
  if (err.name === 'UnauthorizedError') {
    res.status(401).json({ message: '无效的token' });
  }

  // 其他错误
  res.status(500).json({ message: err.message });
});

再次发送请求,完成:

简洁多了的错误返回

完善代码

  • JWT 的生成需要一个初始密钥,而前往多个文件中修改一串字符串是很浪费时间和精力的事情,还有可能遗漏而造成代码错误。

    • 于是经过查询,我找到了config这个库,它可以从一个统一的配置管理文件中读取对应的数据

      顺带把其他的一些配置比如端口、数据库之类的也统一管理了

  • 考虑到这是一个较大的项目,应该尽量降低后期维护成本,于是采用模块设计,将各个中间件分成一个个组件。

    此过程中遇到了一些困难,查找无果后去 Mattermost 询问,后来不知道为什么好了,于是这一步也解决了。

结束

  • 本次使用 JWT 鉴权已经实现基本功能,后续还需根据需求与其他 API 进行对接

Life is a Rainmeter