zoukankan      html  css  js  c++  java
  • 掌握基于 JWT 实现的 Token 身份认证

    引语

    最近正好在独立开发一个后台管理系统,涉及到了基于Token的身份认证,自己边学边用边做整理和总结,对基于JWT实现的Token的身份认证做一次相对比较全面的认识。
    一、基于session的跨域身份验证

    Internet服务无法与用户身份验证分开。一般过程如下。

    用户向服务器发送用户名和密码。
    验证服务器后,相关数据(如用户角色,登录时间等)将保存在当前会话中。
    服务器向用户返回session_id,session信息都会写入到用户的Cookie。
    用户的每个后续请求都将通过在Cookie中取出session_id传给服务器。
    服务器收到session_id并对比之前保存的数据,确认用户的身份。
    

    996415-20180508203515598-1955105543.png

    这种模式最大的问题是,没有分布式架构,无法支持横向扩展。如果使用一个服务器,该模式完全没有问题。但是,如果它是服务器群集或面向服务的跨域体系结构的话,则需要一个统一的session数据库库来保存会话数据实现共享,这样负载均衡下的每个服务器才可以正确的验证用户身份。
    例如虫虫举一个实际中常见的单点登陆的需求:站点A和站点B提供同一公司的相关服务。现在要求用户只需要登录其中一个网站,然后它就会自动登录到另一个网站。怎么做?

    1350514-20180504122814029-1201707523.png

    一种解决方案是听过持久化session数据,写入数据库或文件持久层等。收到请求后,验证服务从持久层请求数据。该解决方案的优点在于架构清晰,而缺点是架构修改比较费劲,整个服务的验证逻辑层都需要重写,工作量相对较大。而且由于依赖于持久层的数据库或者问题系统,会有单点风险,如果持久层失败,整个认证体系都会挂掉。

    1350514-20180504123036062-1920411426.png

    总结基于服务器验证方式暴露的一些明显的问题:

    Session:每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加。
    可扩展性:在服务端的内存中使用Seesion存储登录信息,伴随而来的是可扩展性问题。
    CORS(跨域资源共享):当我们需要让数据跨多台移动设备上使用时,跨域资源的共享会是一个让人头疼的问题。在使用Ajax抓取另一个域的资源,就可以会出现禁止请求的情况。
    CSRF(跨站请求伪造):用户在访问银行网站时,他们很容易受到跨站请求伪造的攻击,并且能够被利用其访问其他的网站。
    在这些问题中,可扩展行是最突出的。因此我们有必要去寻求一种更有行之有效的方法。
    

    基于Token认证的身份认证方案

    那么有什么更好的方案吗?当然有,那就是基于Token的身份认证方案。

    那么基于Token的身份验证可以解决哪些问题呢?

    Token 完全由应用管理,所以它可以避开同源策略
    Token 可以避免 CSRF 攻击
    Token 可以是无状态的,可以在多个服务间共享
    

    基于Token认证的原理

    Token 是在服务端产生的,是无状态的,我们不将用户信息存在服务器或Session中。如果客户端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给客户端。

    客户端可以在每次请求的时候带上 Token 证明自己的合法地位。如果这个 Token 在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌。基于Token的身份验证这种概念解决了在服务端存储信息时的许多问题。NoSession意味着你的程序可以根据需要去增减机器,而不用去担心用户是否登录。

    996415-20180508211129069-742527294.png

    基于Token的身份验证的方案过程如下:

    用户通过用户名和密码发送请求。
    服务端验证。
    服务端返回一个签名的token 给客户端。
    客户端储存token,并且每次发送请求都会携带token。
    服务端验证token并返回数据。
    

    Token的优势
    无状态、可扩展

    在客户端存储的Tokens是无状态的,并且能够被扩展。基于这种无状态和不存储Session信息,负载负载均衡器能够将用户信息从一个服务传到其他服务器上。
    安全性

    请求中发送token而不再是发送cookie能够防止CSRF(跨站请求伪造)。即使在客户端使用cookie存储token,cookie也仅仅是一个存储机制而不是用于认证。不将信息存储在Session中,让我们少了对session操作。
    Token是有时效的,一段时间之后用户需要重新验证。我们也不一定需要等到token自动失效,token有撤回的操作,通过token revocataion可以使一个特定的token或是一组有相同认证的token无效。
    可扩展性

    Tokens能够创建与其它程序共享权限的程序。例如,能将一个随便的社交帐号和自己的大号(Fackbook或是Twitter)联系起来。当通过服务登录Twitter(我们将这个过程Buffer)时,我们可以将这些Buffer附到Twitter的数据流上(we are allowing Buffer to post to our Twitter stream)。

    使用tokens时,可以提供可选的权限给第三方应用程序。当用户想让另一个应用程序访问它们的数据,我们可以通过建立自己的API,得出特殊权限的tokens。
    多平台跨域

    我们提前先来谈论一下CORS(跨域资源共享),对应用程序和服务进行扩展的时候,需要介入各种各种的设备和应用程序。
    Having our API just serve data, we can also make the design choice to serve assets from a CDN. This eliminates the issues that CORS brings up after we set a quick header configuration for our application.
    只要用户有一个通过了验证的token,数据和资源就能够在任何域上被请求到。
    基于标准

    创建token的时候,你可以设定一些选项。我们在后续的文章中会进行更加详尽的描述,但是标准的用法会在JSON Web Tokens体现。
    最近的程序和文档是供给JSON Web Tokens的。它支持众多的语言。这意味在未来的使用中你可以真正的转换你的认证机制。
    无状态 Token

    如果我们把所有状态信息都附加在 Token 上,服务器就可以不保存。但是服务端仍然需要认证 Token 有效。不过只要服务端能确认是自己签发的 Token,而且其信息未被改动过,那就可以认为 Token 有效——“签名”可以作此保证。平时常说的签名都存在一方签发,另一方验证的情况,所以要使用非对称加密算法。但是在这里,签发和验证都是同一方,所以对称加密算法就能达到要求,而对称算法比非对称算法要快得多(可达数十倍差距)。更进一步思考,对称加密算法除了加密,还带有还原加密内容的功能,而这一功能在对 Token 签名时并无必要——既然不需要解密,摘要(散列)算法就会更快。可以指定密码的散列算法,自然是 HMAC。

    上面说了这么多,还需要自己去实现吗?不用! JWT 已经定义了详细的规范,而且有各种语言的若干实现。

    在使用无状态 Token 的时候,有两点需要注意:

    Refresh Token 有效时间较长,所以它应该在服务器端有状态,以增强安全性,确保用户注销时可控
    应该考虑使用二次认证来增强敏感操作的安全性
    

    基于JWT实现的Token认证方案
    JSON Web Token是什么?

    JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。

    JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公用/专用密钥对对JWT进行签名。

    JWT架构:

    截屏2019-12-0615.41.15.png
    什么时候应该使用 JSON Web Token?
    身份验证

    这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。
    单一登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
    信息交换

    JSON Web令牌是在各方之间安全地传输信息的一种好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确定发件人是本人。
    另外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改。
    JSON Web Token 结构

    JSON Web令牌以紧凑的形式由三部分组成,这些部分由点 (. )分隔,分别是:

    Header:标头
    Payload: 有效载荷
    Signature: 签名
    

    因此,JWT通常如下所示

    xxxxx.yyyyy.zzzzz

    Header:标头

    让我们分解不同的部分。
    标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。
    例如:

    {
    "alg": "HS256",
    "typ": "JWT"
    }

    然后,此JSON被Base64Url编码以形成JWT的第一部分。
    Payload: 有效载荷

    令牌的第二部分是包含声明的有效负载。声明是关于实体(通常是用户)和其他数据的声明。有三种类型的声明:已注册声明、公共声明和私有声明。
    已注册的声明:这些是一组预定义的声明,它们不是强制的,而是推荐的,以提供一组有用的、可互操作的声明。主要有:

    iss:发行人
    exp:到期时间
    sub:主题
    aud:用户
    nbf:在此之前不可用
    Iat:发布时间
    jti:JWT ID用于标识该JWT
    
    请注意,声明名称仅是三个字符,因为JWT是紧凑的。
    

    公共声明:这些声明可以由使用JWTs的用户随意定义。但是,为了避免冲突,应该在IANA JSON Web令牌注册表中定义它们,或者将它们定义为包含防冲突命名空间的URI。

    私有声明:这些是为在同意使用它们的各方之间共享信息而创建的自定义索赔,既不是注册索赔,也不是公开索赔。

    有效负载示例可以是:

    {
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
    }

    对有效负载进行Base64Url编码,以形成JSON Web令牌的第二部分。

    请注意,对于已签名的令牌,此信息尽管可以防止篡改,但任何人都可以读取。除非将其加密,否则请勿将机密信息放入JWT的有效负载或报头元素中。
    

    签名

    要创建签名部分,您必须获取编码的头、编码的负载、密钥、头中指定的算法,并对其进行签名。
    例如,如果要使用HMAC SHA256算法,则将通过以下方式创建签名:

    HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret)

    签名用于验证消息在整个过程中没有更改,并且对于使用私钥进行签名的令牌,它还可以验证JWT的发送者是它所说的真实身份。
    整合在一起

    输出是三个由点分隔的Base64 URL字符串,这些点可以在HTML和HTTP环境中轻松传递,同时与基于XML的标准(如SAML)相比更加紧凑。
    下面显示了一个JWT,它对前一个报头和有效负载进行了编码,并用一个秘密进行了签名。

    encoded-jwt3.png

    可以从此图中看出JWT生成的令牌的格式与其对应饿原文之间的关联。这里也顺带推荐一下jwt官网的JWT debuger工具。

    legacy-app-auth-5.png
    JSON Web Token工作原理

    在身份验证中,当用户使用其凭据成功登录时,将返回一个JSON Web Token。由于Token是凭据,必须非常小心地防止安全问题。一般来说,您不应该将令牌保留的时间超过所需的时间。

    由于缺乏安全性,也不应将敏感会话数据存储在浏览器存储中。

    当用户想要访问受保护的路由或资源时,用户代理应该发送JWT,通常在授权头中使用承载模式。标题的内容应如下所示:

    Authorization: Bearer

    在某些情况下,这可以是无状态授权机制。服务器的受保护路由将检查授权头中是否存在有效的JWT,如果存在,则允许用户访问受保护的资源。如果JWT包含必要的数据,则可以减少查询数据库以执行某些操作的需要,尽管情况并非总是如此。

    如果令牌在授权头中发送,则跨源资源共享(CORS)不会成为问题,因为它不使用cookies。

    下图显示了如何获取JWT并将其用于访问API或资源:

    17.png

    应用程序或客户端向授权服务器请求授权。这是通过不同的授权流之一执行的。例如,典型的符合OpenID Connect的web应用程序将使用授权代码流通过/oauth/authorize端点。
    当授权被授予时,授权服务器将向应用程序返回一个访问Token。
    应用程序使用访问令牌访问受保护的资源(如API)。
    

    请注意:使用签名的Token,Token中包含的所有信息都将向用户或其他方公开,即使他们无法更改它。这意味着您不应将机密信息放入Token中。
    JWT的使用
    生成令牌

    jwt.sign(payload, secretOrPrivateKey, [options, callback])
    // paylod: 有效载荷
    // secretOrPrivateKey: 加密密钥或私钥
    // option(可选): 生成令牌设置
    // callback(可选): 回调函数

    其中option可配置属性,属性均为可选:

    algorithm: 算法(默认: HS256)

    noTimestamp: 无时间戳

    header: 头部

    keyid: 键值编号

    mutatePayload: 是否对payload进行转化,若为true则会用payload初始值生成令牌

    以下6相即可在payload中配置也可在option中配置,注意只可在一处出现。

    expiresIn: 令牌过期时间(可为数字(单位秒)或带单位的字符串,例如 60, "2 days", "10h", "7d"等)

    notBefore: 在此之前不可用(格式如上述expiresIn)

    audience: 用户

    issuer: 发布者

    jwtid: 令牌id

    subject: 主题

    生成令牌实例

    // 异步回调方式
    Let privateKey = 'Cloudy'
    jwt.sign({ id: '1', exp:'7d' }, privateKey, { algorithm: 'RS256' }, function(err, token) {
    console.log(token);
    });

    // 同步方式(推荐用promise对其进行进一步封装)
    let token = jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256', expiresIn: '1h' });

    令牌验证

    jwt.verify(token, secretOrPublicKey, [options, callback])
    // token: 令牌
    // secretOrPublicKey: 密钥或公钥
    // option: 生成令牌设置(可选)
    // callback: 回调函数(可选)

    其中option可配置属性有:

    algorithms: 算法

    audience: 如果你想验证用户,为其提供一个字符串或正则表达式

    complete: Boolean值,若为true则完整输出令牌

    issuer(可选): 如果你想验证发布者,为其提供一个字符串或正则表达式

    ignoreExpiration: Boolean值,若为true则不会验证过期时间

    subject: 如果你想验证主题,为其提供一个字符串或正则表达式

    clockTolerance: 时钟容忍,在检查nbf和exp声明时,处理不同服务器之间的小时钟差异所允许的秒数

    maxAge: 允许令牌的最大允许年龄仍然有效。它以秒或描述时间跨度zeit/ms的字符串表示。Eg: 1000, "2 days", "10h", "7d".

    clockTimestamp: 时间戳,应用作所有必要比较的当前时间(秒)。

    nonce:如果要检查nonce声明,请在此处提供一个字符串值。

    验证令牌实例

    // 同步验证(对称加密算法)
    var decoded = jwt.verify(token, 'shhhhh');
    console.log(decoded.foo) // bar

    // 异步验证用户(使用不对称加密算法)
    var cert = fs.readFileSync('public.pem'); // 获取公钥
    jwt.verify(token, cert, { audience: 'urn:foo', jwtid: 'jwtid', subject: 'subject' }, function(err, decoded) {
    // if audience mismatch, err == invalid audience
    });

    简单解码(无验证)

    (同步)返回解码的有效负载,而不验证签名是否有效。

    jwt.decode(token [, options])

    解码options可配置属性:

    json 在负载上强制JSON.parse,即使头不包含“typ”:“JWT”。
    complete 返回一个带有解码有效负载和头的对象。

    支持的算法数组。目前支持以下算法。
    alg Parameter Value Digital Signature or MAC Algorithm
    HS256 HMAC using SHA-256 hash algorithm
    HS384 HMAC using SHA-384 hash algorithm
    HS512 HMAC using SHA-512 hash algorithm
    RS256 RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm
    RS384 RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm
    RS512 RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm
    PS256 RSASSA-PSS using SHA-256 hash algorithm (only node ^6.12.0 OR >=8.0.0)
    PS384 RSASSA-PSS using SHA-384 hash algorithm (only node ^6.12.0 OR >=8.0.0)
    PS512 RSASSA-PSS using SHA-512 hash algorithm (only node ^6.12.0 OR >=8.0.0)
    ES256 ECDSA using P-256 curve and SHA-256 hash algorithm
    ES384 ECDSA using P-384 curve and SHA-384 hash algorithm
    ES512 ECDSA using P-521 curve and SHA-512 hash algorithm
    none No digital signature or MAC value included
    JWT问题和趋势

    1、JWT默认不加密,但可以加密。生成原始令牌后,可以使用改令牌再次对其进行加密。
    2、当JWT未加密方法是,一些私密数据无法通过JWT传输。
    3、JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。
    4、JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,一旦JWT签发,在有效期内将会一直有效。
    5、JWT本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT的有效期不宜设置太长。对于某些重要操作,用户在使用时应该每次都进行进行身份验证。
    6、为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。
    结语

    最近正好在独立开发一个后台管理系统,涉及到了Token验证,如果觉得文章对你有用,请你帮我点个赞吧,你的点赞和关注是我一直坚持分享的动力!

    推荐阅读:
    【专题:JavaScript进阶之路】
    深入理解 ES6 Promise
    JavaScript之函数柯理化
    ES6 尾调用和尾递归
    Git常用命令小结
    浅谈 MVC 和 MVVM 模型

    参考:
    node-jsonwebtoken
    Introduction to JSON Web Tokens
    Token 认证的来龙去脉
    彻底理解cookie,session,token
    前后端分离使用 Token 登录解决方案
    基于JWT的token身份认证方案

    https://segmentfault.com/a/1190000021224288

  • 相关阅读:
    ubuntu远程windows桌面
    spring boot 给返回值加状态 BaseData
    spring boot 拦截异常 统一处理
    IntelliJ IDEA spring boot 远程Ddbug调试
    IntelliJ IDEA 常用插件
    spring boot 请求地址带有.json 兼容处理
    spring boot 接口返回值去掉为null的字段
    spring boot 集成disconf
    Spring boot 自定义拦截器
    Linux下安装MySQL
  • 原文地址:https://www.cnblogs.com/jiftle/p/12676946.html
Copyright © 2011-2022 走看看