基于JWT的Token身份验证

​ 身份验证,是指通过一定的手段,完成对用户身份的确认。为了及时的识别发送请求的用户身份,我们调研了常见的几种认证方式,cookie、session和token。

1.Cookie

​ cookie是指浏览器里能够永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。

​ 在用户第一次登陆成功后,服务端就将用户的个人信息等写入cookie对象中,然后将此cookie对象发送给对应的客户端,客户端就存储下这个cookie。此后每次客户端向服务端发送请求时,就带上这个cookie对象,便于服务端进行用户认证和分辨,以决定是否提供服务。

​ 由于cookie是存储在客户端的,服务端的仅仅存储一份信息,不会占据太多的磁盘空间,服务端的压力很小。

​ 但是由于cookie对象中会存储大量的用户相关信息,而且每个请求都会携带该对象,因此存在传输效率的问题,同时一旦cookie被有心人截获,那么用户信息就被完全暴露,极易导致用户信息的泄露和伪装身份的恶意访问,给软件安全带来一定的隐患。

2.Session

​ session是对cookie的进一步优化。cookie会存储用户的大部分相关信息,从而导致效率低下和安全不能保障等问题,因此session的基本逻辑是将用户信息存储在服务器端,生成一个唯一字符串来对应每一个用户,然后仅将此唯一的字符串返回给客户端。

​ 不用传输大量的用户相关数据,保证了传输效率和传输的安全性。一般情况,session存储于cookie中,利用cookie对象来传输session数据。

​ 但是此种操作下,每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加。同时如果服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。

3.Token

​ Token的身份验证是无状态的,客户端登陆成功后,服务端会生成一个token并把它返还给客户端,服务端不再保存该Token。客户端每次发送请求时也会携带Token。由于这里的Token是服务端用自己的密钥签名的,当它接受到客户的Token时,只需要用自己的密钥去验证,就可以判断这个Token是不是自己签发的。

​ Token验证的具体流程如下:

  • 客户端使用用户名和密码等向服务端请求登录
  • 服务端通过验证,返回Token给客户端
  • 客户端将Token写入本地缓存,并且后续请求均携带该Token
  • 服务端接受到服务请求,验证Token,验证通过则提供服务,否则拒绝响应。

​ 基于Token的身份验证方式,使我们不用将用户信息存在服务器或Session中,此种方式既解决了传输效率和安全问题,同时也解决了服务器内存压力过大的问题。同时由于Token无状态和不存储Session信息,一次,即使对于负载均衡问题,也能够将用户请求传递到任何一台服务器上,并进行解析和验证,而不需要担心请求必须发送到某一特定机器上的问题。

4.基于JWT的Token身份验证

​ 经过以上的调研和分析,我们发现基于Token的身份验证相较于其他的身份验证方式有着更好的适用性和安全性。因此,我们最终选择Token进行身份验证。

​ 实施Token的验证方法很多,其中比较常用的便是JWT验证。JWT全称JSON Web Token。JWT标准的Token包括header、payload、signature三部分,中间用点分开,并且使用Base64编码。

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c3VyaWQiOjQ0LCJpYXQiOjE7MTk5NjcyMDZ9.UPcMrinrYLr5ZtcWLDqeuxRlUpEQEwk-im8EGyjXTiJk_f0j1bIJWhe34akHfvo0fjbUDK4lo9ADXbUy3a2wmYL_A

​ 第一部分header,放入token的类型(“JWT”)和算法名称(RS256等);第二部分payload,放入用户的不敏感信息(用户id等);第三部分signature,根据不公开的秘钥加上header中声明的算法,生成特定的签名。最终三部分组合起来即形成了token,发送给客户端。

​ Token认证部分,对Token使用不公开的秘钥和声明的算法进行解密分析,若成功解密即通过认证,若解密错误即认证失败。

5.以Node.js为例的JWT验证

​ 此处以RS256加密算法为例进行举例分析。

​ 首先生成秘钥文件,以便后续加密使用:

ssh-keygen -t rsa -b 2048 -f private.key

​ 随后根据秘钥,创建对应的公钥:

openssl rsa -in private.key -pubout -outform PEM -out public.key

​ 秘钥创建完成之后,即可进行Token的签发与认证。

JWT的签发
const jwt = require('jsonwebtoken')
const fs = require('fs')
const path = require('path')

async function generateToken(data) {
    let creatTime = Math.floor(Date.now() / 1000);
    const privateKey = await fs.readFileSync(path.join(__dirname, 'xxxx.key'));
    let obj = {
        data,
        expire: creatTime + 60 * 30
    }
    const token = jwt.sign(obj, privateKey, {algorithm: 'RS256'});
    return token;
}

以RS256算法和不公开的私钥,以及待存储数据和过期时间进行加密和签证,以生成最终可用的token返回给客户端。

JWT的验证
const jwt = require('jsonwebtoken')
const fs = require('fs')
const path = require('path')

async function verifyToken(token) {
    const publicKey = fs.readFileSync(path.join(__dirname, 'xxx.key'));
    let result;
    try {
        result = jwt.verify(token, publicKey)
        let {exp = 0} = result, current = Math.floor(Date.now() / 1000);
        if (current <= exp) {
            res = result;
        }
    } catch (e) {
        result = 'err';
    }
    return result;
}

对Token进行解密验证,并验证Token是否过期。

Token拦截器
app.use(function (req, res, next) {
    if (req._parsedUrl.pathname != '/login') {
        let tk = req.headers.authorization;
        if (tk.substr(0, 5) == 'test-') {
            tk = tk.substring(5);
        }
        token.verifyToken(tk).then( function (result) {
            if (result == 'err') {
                res.send({
                    "data": {},
                    "code": 0,
                    "msg": "Token is wrong!"
                })
            } 
            else {
                next();
            }
        });
    }
    else {
      next();
    }
}) 

在所有请求到来时,首先进行Token认证,认证通过则提供服务,否则直接拒绝服务。

原文地址:https://www.cnblogs.com/cyy---336699/p/14745471.html