最近面试了几家公司,发现JWT(JSON Web Token)是一个高频考点,几乎每家公司都会问,从JWT的基本概念、到结构、到原理、到优缺点、到和Session的区别、到安全问题、到刷新机制、到实际应用,都会问到。

JWT是现在前后端分离项目中,最常用的认证方案之一,掌握JWT,不仅对面试有帮助,对实际工作也很有帮助。今天就来总结一下,我被问到的那些JWT面试题,以及我的回答和理解,希望能给正在找工作的朋友一些参考。

一、JWT是什么?

这是最基础的问题,几乎每次面试都会问,考察你对JWT的基本概念的理解。

我的回答:

JWT,全称是JSON Web Token,是一种开放标准(RFC 7519),用于在各方之间安全地传输信息,特别是用于身份认证和信息交换。

JWT的核心思想,是把用户的身份信息,编码成一个JSON对象,然后签名,生成一个令牌,客户端拿着这个令牌,每次请求都带上,服务端验证令牌的签名,确认令牌的合法性,然后从令牌中取出用户信息,完成认证。

JWT的特点,是无状态的,服务端不需要存储令牌,只需要验证签名,就能确认令牌的合法性,这样,服务端就可以很方便地扩展,支持分布式部署,不需要共享Session。

JWT主要用于以下场景:

  1. 身份认证: 用户登录后,服务端生成JWT,返回给客户端,客户端后续请求都带上JWT,服务端验证JWT,完成认证。这是JWT最常用的场景,特别是前后端分离项目、单页应用、移动App。
  2. 信息交换: JWT可以在各方之间安全地传输信息,因为JWT有签名,可以验证信息是否被篡改,也可以验证发送方的身份。比如,微服务之间的调用,可以用JWT来传递用户信息和权限信息。

简单来说,JWT就是一种令牌,一种凭证,用来证明用户的身份,服务端不需要存储,只需要验证签名,就能确认用户的身份,是一种无状态的认证方案。

二、JWT的结构是什么?

这个问题,也是高频考点,考察你对JWT的结构的理解,JWT由三部分组成,Header、Payload、Signature,用点(.)分隔。

我的回答:

JWT由三部分组成,分别是Header(头部)、Payload(负载)、Signature(签名),三部分用点(.)分隔,格式是Header.Payload.Signature,比如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

下面分别介绍这三部分:

1. Header(头部)

Header是一个JSON对象,描述JWT的元数据,通常包含两个字段:

  • alg:签名算法,比如HS256(HMAC SHA256)、RS256(RSA SHA256)等。
  • typ:令牌类型,通常是"JWT"。

比如:

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

然后,把这个JSON对象,用Base64Url编码,就得到了JWT的第一部分,Header。

2. Payload(负载)

Payload也是一个JSON对象,存放实际要传输的数据,也就是声明(Claims)。声明分为三种:

  • 注册声明(Registered Claims): 是JWT标准中预定义的声明,不是必须的,但是推荐使用,比如:

- iss(issuer):签发者 - sub(subject):主题,通常是用户ID - aud(audience):受众 - exp(expiration time):过期时间 - nbf(not before):生效时间 - iat(issued at):签发时间 - jti(JWT ID):JWT的唯一标识,用于防止重放攻击

  • 公共声明(Public Claims): 是使用JWT的人,自己定义的声明,比如用户名、角色、权限等,但是为了避免冲突,应该在IANA JSON Web Token Registry中注册,或者使用带命名空间的URI。
  • 私有声明(Private Claims): 是使用JWT的双方,自己约定的声明,比如用户ID、用户名、角色等,不需要注册,但是要注意不要和注册声明冲突。

比如:

{
  "sub": "1234567890",
  "name": "John Doe",
  "role": "admin",
  "iat": 1516239022,
  "exp": 1516242622
}

然后,把这个JSON对象,用Base64Url编码,就得到了JWT的第二部分,Payload。

注意:Payload只是用Base64Url编码,不是加密,所以任何人都可以解码,看到Payload的内容,所以,不要在Payload中存放敏感信息,比如密码、银行卡号等。

3. Signature(签名)

Signature是JWT的第三部分,用于验证JWT是否被篡改,验证发送方的身份。

Signature的生成方法,是把Header和Payload,用点(.)拼接起来,然后用Header中指定的签名算法,加上一个密钥,进行签名,生成签名。

比如,用HS256算法,签名的公式是:

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

如果用RS256算法,就是用RSA的私钥签名,用RSA的公钥验证签名。

然后,把签名的结果,用Base64Url编码,就得到了JWT的第三部分,Signature。

Signature的作用,是验证JWT是否被篡改,因为如果Header或者Payload被修改了,那么用同样的算法和密钥签名,得到的签名就会不一样,服务端验证签名的时候,就会发现签名不匹配,从而拒绝这个JWT。同时,Signature也能验证发送方的身份,因为只有知道密钥的人,才能生成正确的签名。

最后,把Header、Payload、Signature,用点(.)拼接起来,就得到了一个完整的JWT。

三、JWT的工作原理是什么?

这个问题,考察你对JWT的工作流程的理解,JWT是怎么实现身份认证的。

我的回答:

JWT的工作原理,大致如下:

  1. 用户登录: 用户输入用户名和密码,发送登录请求给服务端。
  2. 服务端验证: 服务端验证用户名和密码是否正确,如果正确,就生成一个JWT,把用户的身份信息(比如用户ID、用户名、角色等)放到Payload中,设置过期时间,然后用密钥签名,生成JWT。
  3. 返回JWT: 服务端把生成的JWT,返回给客户端。
  4. 客户端存储JWT: 客户端收到JWT后,把它存储起来,通常存储在localStorage、sessionStorage、或者Cookie中。
  5. 客户端发送请求: 客户端后续的每次请求,都带上JWT,通常放在HTTP请求头的Authorization字段中,格式是Bearer <token>,比如:

`` Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... `` 也可以放在POST请求的body中,或者URL的query参数中,但是推荐放在请求头中。

  1. 服务端验证JWT: 服务端收到请求后,取出JWT,验证JWT的签名是否正确,是否过期,是否有效。如果验证通过,就从Payload中取出用户信息,完成认证,处理请求;如果验证不通过,就返回401 Unauthorized错误,拒绝请求。

这就是JWT的基本工作原理,核心是服务端不需要存储JWT,只需要验证签名,就能确认JWT的合法性,从而完成认证,是一种无状态的认证方案。

四、JWT和Session/Cookie的区别是什么?

这个问题,也是高频考点,考察你对JWT和传统的Session/Cookie认证的区别的理解,以及各自的优缺点和适用场景。

我的回答:

JWT和Session/Cookie,都是常用的身份认证方案,但是它们的原理和特点,有很大的区别,主要有以下几点:

1. 状态性:

  • Session/Cookie: 是有状态的,服务端需要存储Session,每个用户的Session,都存储在服务端(内存、数据库、Redis等),客户端的Cookie中,只存储Session ID。
  • JWT: 是无状态的,服务端不需要存储JWT,JWT的所有信息,都存储在客户端,服务端只需要验证签名,就能确认JWT的合法性。

2. 扩展性:

  • Session/Cookie: 因为是有状态的,服务端需要存储Session,所以在分布式部署的时候,需要共享Session,比如,用Redis存储Session,或者用Session粘滞,扩展性比较差。
  • JWT: 因为是无状态的,服务端不需要存储JWT,所以在分布式部署的时候,不需要共享状态,任何一台服务器,都可以验证JWT,扩展性很好,很适合微服务、分布式系统。

3. 跨域支持:

  • Session/Cookie: Cookie是有域的限制的,跨域的时候,Cookie不会自动携带,需要配置CORS,而且第三方Cookie,在很多浏览器中,被限制了,所以跨域支持比较差。
  • JWT: JWT通常放在HTTP请求头中,没有域的限制,跨域的时候,只要客户端把JWT带上,就可以完成认证,跨域支持很好,很适合前后端分离、多域名、多端的场景。

4. 安全性:

  • Session/Cookie: Cookie有HttpOnly、Secure、SameSite等属性,可以防止XSS攻击窃取Cookie,防止CSRF攻击,安全性相对较好。但是,Session ID如果被窃取,攻击者就可以冒充用户,而且服务端很难主动注销Session ID(除非服务端删除Session)。
  • JWT: JWT通常存储在localStorage中,容易受到XSS攻击,被窃取;如果存储在Cookie中,又会受到CSRF攻击。而且,JWT一旦签发,在过期之前,服务端很难主动注销,除非用黑名单机制,所以JWT的安全性,相对较差,需要特别注意。

5. 数据量:

  • Session/Cookie: Cookie的大小,通常限制在4KB以内,Session存储在服务端,没有大小限制,但是Session太多,会占用服务端的资源。
  • JWT: JWT是放在请求头中的,每次请求都要携带,所以JWT的大小,不能太大,否则会增加请求的体积,影响性能。通常,JWT的大小,应该控制在几KB以内。

6. 注销和过期:

  • Session/Cookie: 服务端可以主动删除Session,让用户注销,很方便。Session的过期时间,也可以灵活控制,比如,用户活跃的时候,自动续期。
  • JWT: JWT一旦签发,在过期之前,服务端很难主动注销,除非用黑名单机制,把注销的JWT存到黑名单中,这样就又变成有状态的了。JWT的过期时间,通常比较短,需要配合Refresh Token,来实现续期。

7. 适用场景:

  • Session/Cookie: 适合传统的Web应用,单体应用,不需要跨域,不需要分布式部署的场景,安全性要求较高的场景。
  • JWT: 适合前后端分离应用,单页应用,移动App,微服务,分布式系统,需要跨域,需要多端认证的场景。

总结一下,Session/Cookie和JWT,各有优缺点,各有适用场景,没有绝对的好坏,要根据实际的业务场景,选择合适的认证方案。现在,前后端分离、微服务、分布式系统,越来越流行,所以JWT的使用,也越来越广泛。

五、JWT的优缺点是什么?

这个问题,考察你对JWT的优缺点的理解,以及在实际项目中,怎么权衡。

我的回答:

JWT的优点,主要有以下几点:

1. 无状态,扩展性好: JWT是无状态的,服务端不需要存储JWT,只需要验证签名,就能确认JWT的合法性,所以在分布式部署、微服务架构中,扩展性很好,不需要共享Session,任何一台服务器,都可以验证JWT。

2. 跨域支持好: JWT通常放在HTTP请求头中,没有Cookie的域限制,跨域支持很好,很适合前后端分离、多域名、多端的场景。

3. 适合多端认证: JWT可以在Web端、移动端、小程序等多个端使用,只要客户端把JWT带上,就可以完成认证,很适合多端统一认证的场景。

4. 信息自包含: JWT的Payload中,可以存放用户的身份信息、权限信息等,服务端验证JWT后,直接从Payload中取出信息,不需要再查数据库,减少了数据库的查询,提高了性能。

5. 标准化,生态好: JWT是一个开放标准(RFC 7519),有很多语言的库支持,比如Java、Python、PHP、Node.js、Go等,都有成熟的JWT库,使用很方便,生态很好。

JWT的缺点,主要有以下几点:

1. 安全性问题: JWT的Payload,只是用Base64Url编码,不是加密,所以不要存放敏感信息。JWT一旦签发,在过期之前,服务端很难主动注销,除非用黑名单机制。JWT如果存储在localStorage中,容易受到XSS攻击,被窃取;如果存储在Cookie中,又会受到CSRF攻击。所以,JWT的安全性,需要特别注意。

2. 无法主动注销: JWT一旦签发,在过期之前,服务端很难主动注销,除非用黑名单机制,把注销的JWT存到黑名单中,这样就又变成有状态的了,失去了JWT无状态的优势。所以,JWT通常设置较短的过期时间,配合Refresh Token,来降低风险。

3. 令牌过期和续期问题: JWT的过期时间,如果设置太长,安全性差;如果设置太短,用户需要频繁登录,体验差。所以,通常需要配合Refresh Token,来实现续期,Access Token过期后,用Refresh Token获取新的Access Token,这样既保证了安全性,又保证了用户体验。但是,Refresh Token的管理,也比较复杂。

4. 数据量问题: JWT每次请求都要携带,如果JWT太大,会增加请求的体积,影响性能,增加带宽消耗。所以,JWT的Payload中,不要存放太多信息,只存放必要的信息,比如用户ID、角色等,其他信息,可以服务端查数据库获取。

5. 签名算法的选择: JWT的签名算法,有对称加密(HS256)和非对称加密(RS256),对称加密,密钥的管理比较麻烦,分布式部署的时候,所有服务器都要共享密钥;非对称加密,用私钥签名,公钥验证,密钥管理比较方便,但是性能比对称加密差。所以,要根据实际情况,选择合适的签名算法。

总结一下,JWT有很多优点,也有一些缺点,在实际项目中,要根据业务场景,权衡优缺点,选择合适的认证方案,并且做好安全防护。

六、JWT的安全问题有哪些?怎么防范?

这个问题,是高频考点,也是实际项目中,必须重视的问题,考察你对JWT的安全问题的理解,以及防范措施。

我的回答:

JWT的安全问题,主要有以下几点,以及对应的防范措施:

1. 篡改和伪造:

  • 问题: 攻击者可能会篡改JWT的Header或者Payload,或者伪造JWT,冒充用户。
  • 防范: JWT有签名,服务端验证签名,如果Header或者Payload被篡改,签名就会不匹配,服务端就会拒绝。所以,一定要验证签名,并且使用安全的签名算法,比如HS256、RS256,不要使用none算法,不要使用弱密钥。另外,要注意算法替换攻击,比如,攻击者把alg改成none,或者把RS256改成HS256,用公钥作为密钥签名,所以,服务端要固定签名算法,不要信任JWT中的alg字段,或者验证alg是否是预期的算法。

2. 令牌泄露:

  • 问题: JWT如果被攻击者窃取,攻击者就可以冒充用户,因为JWT是无状态的,服务端无法区分是合法用户还是攻击者。JWT可能通过XSS攻击(存储在localStorage中)、CSRF攻击(存储在Cookie中)、网络窃听(HTTP传输)、日志泄露等方式被窃取。
  • 防范:

- 一定要使用HTTPS,加密传输,防止网络窃听。 - 如果存储在localStorage中,要防范XSS攻击,对用户输入进行过滤和转义,使用HttpOnly的Cookie存储敏感信息,不要在JWT中存放敏感信息。 - 如果存储在Cookie中,要设置HttpOnly、Secure、SameSite属性,防范XSS和CSRF攻击。 - 不要在日志中打印JWT,避免泄露。 - 设置较短的过期时间,即使JWT被窃取,攻击者也只能在短时间内使用,降低风险。 - 可以绑定JWT和用户的IP、User-Agent等,如果IP或者User-Agent变化,就要求重新登录,但是这样会影响用户体验,而且IP可能会变化,要权衡。

3. 重放攻击:

  • 问题: 攻击者截获了JWT,然后重放这个JWT,冒充用户,因为JWT在过期之前,都是有效的。
  • 防范:

- 设置较短的过期时间,降低重放攻击的风险。 - 在JWT的Payload中,添加jti(JWT ID)声明,每个JWT有唯一的ID,服务端可以记录已经使用过的jti,防止重放,但是这样就又变成有状态的了。 - 可以添加nbf(not before)声明,设置JWT的生效时间,防止JWT被过早使用。 - 对于重要的操作,比如修改密码、支付等,可以要求用户重新认证,或者使用二次验证,比如短信验证码、邮箱验证码等。

4. 无法主动注销:

  • 问题: JWT一旦签发,在过期之前,服务端很难主动注销,即使用户修改了密码,或者退出登录,原来的JWT依然有效,直到过期。
  • 防范:

- 设置较短的过期时间,降低风险。 - 用黑名单机制,把注销的JWT的jti,存到Redis中,设置过期时间和JWT的过期时间一致,每次验证JWT的时候,检查jti是否在黑名单中,如果在,就拒绝。这样就又变成有状态的了,但是黑名单的数据量不大,因为JWT的过期时间短,过期后自动删除,对性能影响不大。 - 用户修改密码后,可以让所有旧的JWT失效,比如,在用户表中,记录一个tokenversion,每次修改密码,tokenversion加1,JWT的Payload中,也带上tokenversion,验证的时候,检查JWT中的tokenversion和数据库中的是否一致,如果不一致,就拒绝。这样,用户修改密码后,所有旧的JWT都失效了。 - 退出登录的时候,客户端删除JWT,同时服务端把JWT加入黑名单。

5. XSS攻击:

  • 问题: 如果JWT存储在localStorage、sessionStorage中,攻击者可以通过XSS攻击,注入恶意脚本,窃取JWT。
  • 防范:

- 防范XSS攻击,对用户输入进行过滤和转义,使用CSP(内容安全策略),禁止执行未授权的脚本。 - 不要在JWT中存放敏感信息,即使JWT被窃取,也不会泄露敏感信息。 - 设置较短的过期时间,降低风险。 - 可以把JWT存储在HttpOnly的Cookie中,这样JavaScript无法读取,防止XSS攻击窃取,但是要注意防范CSRF攻击。

6. CSRF攻击:

  • 问题: 如果JWT存储在Cookie中,攻击者可以通过CSRF攻击,诱导用户访问恶意网站,利用用户的Cookie,发送请求,冒充用户。
  • 防范:

- 防范CSRF攻击,设置Cookie的SameSite属性,比如Strict或者Lax,防止跨站请求携带Cookie。 - 使用CSRF Token,每次请求,都带上CSRF Token,服务端验证CSRF Token。 - 对于重要的操作,要求用户重新认证,或者使用二次验证。 - 可以把JWT放在HTTP请求头中,而不是Cookie中,这样就不会有CSRF攻击的问题,但是要注意防范XSS攻击。

7. 算法替换攻击:

  • 问题: 攻击者可以修改JWT的Header中的alg字段,比如,把RS256改成HS256,然后用公钥作为密钥,进行签名,因为服务端如果信任JWT中的alg字段,就会用HS256算法,用公钥作为密钥验证签名,这样攻击者伪造的JWT,就会通过验证。
  • 防范:

- 服务端固定签名算法,不要信任JWT中的alg字段,验证的时候,用预期的算法验证,比如,预期是RS256,就用RS256验证,不管JWT中的alg是什么。 - 或者,验证JWT中的alg字段,是否是预期的算法,如果不是,就拒绝。 - 不要使用none算法,不要允许alg为none。

8. 弱密钥:

  • 问题: 如果使用对称加密算法(HS256),密钥太弱,容易被暴力破解,攻击者破解了密钥,就可以伪造JWT。
  • 防范:

- 使用强密钥,密钥长度足够,比如,HS256的密钥,至少32位,最好是随机生成的字符串。 - 定期更换密钥。 - 可以使用非对称加密算法(RS256),用私钥签名,公钥验证,私钥只保存在签发服务端,公钥可以公开,这样即使公钥泄露,也不会有问题,因为攻击者没有私钥,无法伪造JWT。

总结一下,JWT的安全问题,主要是令牌泄露、无法主动注销、重放攻击、XSS、CSRF、算法替换、弱密钥等,在实际项目中,要根据业务场景,采取相应的防范措施,确保JWT的安全。

七、JWT的刷新机制是什么?Refresh Token怎么用?

这个问题,也是高频考点,考察你对JWT的过期和续期机制的理解,因为JWT的过期时间,如果设置太长,安全性差;如果设置太短,用户体验差,所以需要Refresh Token来续期。

我的回答:

JWT的刷新机制,是用Refresh Token来获取新的Access Token,解决JWT过期后,用户需要重新登录的问题,同时保证安全性。

具体来说,有两种令牌:

  • Access Token(访问令牌): 就是我们平时说的JWT,用于身份认证,放在请求头中,每次请求都带上,过期时间比较短,比如15分钟、30分钟、1小时,这样即使Access Token被窃取,攻击者也只能在短时间内使用,安全性高。
  • Refresh Token(刷新令牌): 用于获取新的Access Token,过期时间比较长,比如7天、30天、90天,通常存储在HttpOnly的Cookie中,或者安全的存储中,不会放在每次请求的请求头中,只有在Access Token过期,需要刷新的时候,才会使用。

刷新机制的工作流程,大致如下:

  1. 用户登录: 用户登录成功后,服务端生成Access Token和Refresh Token,返回给客户端。
  2. 客户端存储: 客户端把Access Token存储在内存或者localStorage中,把Refresh Token存储在HttpOnly的Cookie中(推荐),或者安全的存储中。
  3. 正常请求: 客户端发送请求,带上Access Token,服务端验证Access Token,处理请求。
  4. Access Token过期: 当Access Token过期后,服务端返回401错误,提示Access Token过期。
  5. 刷新Access Token: 客户端收到401错误后,发送刷新请求,带上Refresh Token,请求服务端的刷新接口。
  6. 服务端验证Refresh Token: 服务端验证Refresh Token的签名、过期时间、是否有效(是否在黑名单中,是否被注销等),如果验证通过,就生成新的Access Token,返回给客户端;如果验证不通过,就返回401错误,要求用户重新登录。
  7. 重试请求: 客户端拿到新的Access Token后,用新的Access Token,重新发送之前的请求,完成请求。
  8. Refresh Token过期: 当Refresh Token也过期后,用户需要重新登录,获取新的Access Token和Refresh Token。

Refresh Token的使用,有几个注意点:

  1. Refresh Token要安全存储: Refresh Token的过期时间长,权限大,所以要安全存储,推荐存储在HttpOnly的Cookie中,设置Secure、SameSite属性,防止XSS和CSRF攻击,不要存储在localStorage中,因为容易被XSS攻击窃取。
  1. Refresh Token要可以主动注销: 用户退出登录,或者修改密码后,Refresh Token应该失效,所以,Refresh Token最好存储在服务端(比如Redis),可以主动删除,或者用黑名单机制,记录注销的Refresh Token。这样,Refresh Token就变成有状态的了,但是因为Refresh Token只有在刷新的时候才使用,使用频率低,对性能影响不大。
  1. Refresh Token轮换(Rotation): 每次刷新Access Token的时候,也生成新的Refresh Token,返回给客户端,旧的Refresh Token失效,这样,即使Refresh Token被窃取,攻击者也只能使用一次,因为下次刷新的时候,旧的Refresh Token已经失效了。这叫Refresh Token轮换,能提高安全性。
  1. 检测Refresh Token重用: 如果发现旧的Refresh Token被重用了,说明Refresh Token可能被窃取了,这时候,应该让这个用户的所有Refresh Token都失效,要求用户重新登录,保护用户的安全。
  1. 刷新接口要限流: 刷新接口,要做限流,防止暴力破解,防止攻击者用大量的Refresh Token尝试刷新。
  1. Access Token的过期时间要短: Access Token的过期时间,要设置得短一些,比如15分钟、30分钟,这样即使Access Token被窃取,风险也小。Refresh Token的过期时间,可以设置得长一些,比如7天、30天,保证用户体验。

总结一下,JWT的刷新机制,是用短过期时间的Access Token,加上长过期时间的Refresh Token,来实现续期,既保证了安全性,又保证了用户体验,是JWT认证中,常用的方案。

八、JWT怎么实现注销?

这个问题,也是高频考点,因为JWT是无状态的,一旦签发,在过期之前,服务端很难主动注销,所以怎么实现注销,是一个常见的问题。

我的回答:

JWT的注销,主要有以下几种方案:

1. 客户端删除JWT:

  • 最简单的方案,用户退出登录的时候,客户端删除存储的JWT,这样,客户端就没有JWT了,就无法再发送认证请求了。
  • 优点:简单,无状态,服务端不需要做任何事情。
  • 缺点:不安全,因为JWT在过期之前,依然有效,如果JWT已经被攻击者窃取了,攻击者依然可以使用这个JWT,直到过期。所以,这个方案,只适合安全性要求不高的场景,或者配合短过期时间使用。

2. 黑名单机制:

  • 用户退出登录的时候,服务端把这个JWT的jti(JWT ID),存到黑名单中(比如Redis),设置过期时间和JWT的过期时间一致,JWT过期后,自动从黑名单中删除。
  • 每次验证JWT的时候,检查jti是否在黑名单中,如果在,就拒绝这个JWT。
  • 优点:可以主动注销JWT,安全性较高。
  • 缺点:变成有状态的了,服务端需要存储黑名单,但是因为JWT的过期时间短,黑名单的数据量不大,对性能影响不大。
  • 这个方案,是最常用的JWT注销方案。

3. Token版本机制:

  • 在用户表中,记录一个tokenversion字段,默认是0,每次用户修改密码,或者需要让所有旧的JWT失效的时候,tokenversion加1。
  • JWT的Payload中,也带上token_version。
  • 每次验证JWT的时候,检查JWT中的tokenversion,和数据库中的tokenversion是否一致,如果不一致,就拒绝这个JWT。
  • 优点:可以让用户的所有旧JWT都失效,适合修改密码、账号被盗等场景,而且不需要存储每个JWT,只需要存储一个版本号,数据量小。
  • 缺点:不能单独注销某一个JWT,只能让用户的所有JWT都失效,粒度比较粗。
  • 这个方案,通常和黑名单机制配合使用,黑名单机制用于单独注销某一个JWT(比如退出登录),Token版本机制用于让用户的所有JWT都失效(比如修改密码)。

4. 短过期时间:

  • 把JWT的过期时间,设置得很短,比如15分钟、30分钟,这样,即使用户退出登录,JWT也很快就过期了,风险很小。
  • 配合Refresh Token,实现续期,用户退出登录的时候,同时让Refresh Token失效,这样,Access Token很快过期,Refresh Token也失效了,用户就无法再获取新的Access Token了。
  • 优点:简单,不需要存储黑名单,无状态。
  • 缺点:Access Token在过期之前,依然有效,有一定的风险,但是因为过期时间短,风险很小。
  • 这个方案,通常和其他方案配合使用。

5. 服务端存储JWT:

  • 把所有签发的JWT,都存储在服务端(比如Redis),用户退出登录的时候,从服务端删除这个JWT。
  • 每次验证JWT的时候,检查JWT是否在服务端存储中,如果不在,就拒绝。
  • 优点:可以主动注销JWT,安全性高。
  • 缺点:完全变成有状态的了,失去了JWT无状态的优势,而且存储所有JWT,数据量大,对性能影响大,不推荐。

总结一下,JWT的注销,最常用的方案是黑名单机制,配合短过期时间和Token版本机制,既能主动注销JWT,又对性能影响不大,安全性也较高。在实际项目中,要根据业务场景,选择合适的注销方案。

九、JWT的实际应用场景有哪些?

这个问题,考察你对JWT的实际应用的理解,以及在什么场景下,应该使用JWT。

我的回答:

JWT的实际应用场景,主要有以下几个:

1. 前后端分离的Web应用:

  • 前后端分离的项目,前端和后端是分开部署的,可能跨域,这时候,用JWT认证,比Session/Cookie更方便,因为JWT放在请求头中,没有跨域问题,而且无状态,服务端容易扩展。
  • 比如,Vue、React、Angular等单页应用,通常用JWT认证。

2. 移动App:

  • 移动App(iOS、Android),没有Cookie的概念,用JWT认证,比Session/Cookie更方便,App把JWT存储在本地,每次请求带上,就可以完成认证。
  • 而且,JWT无状态,服务端容易扩展,适合移动App的高并发场景。

3. 小程序:

  • 微信小程序、支付宝小程序等,也可以用JWT认证,小程序把JWT存储在本地存储中,每次请求带上,完成认证。

4. 微服务和分布式系统:

  • 微服务和分布式系统,服务很多,部署在多台服务器上,用JWT认证,无状态,不需要共享Session,任何一个服务,都可以验证JWT,扩展性很好。
  • 而且,JWT的Payload中,可以存放用户信息、权限信息,服务之间调用的时候,带上JWT,就可以传递用户信息,不需要再查数据库,提高了性能。

5. 第三方登录和单点登录(SSO):

  • JWT可以用于第三方登录,比如,用户用微信、QQ、微博登录,第三方验证通过后,生成JWT,返回给业务系统,业务系统验证JWT,完成登录。
  • JWT也可以用于单点登录(SSO),用户登录一次,就可以访问多个系统,因为JWT是无状态的,任何系统都可以验证JWT,不需要共享Session。

6. API认证和授权:

  • 开放API,给第三方调用,可以用JWT认证,第三方申请API密钥,服务端生成JWT,第三方用JWT调用API,服务端验证JWT,完成认证和授权。
  • JWT的Payload中,可以存放权限信息,服务端验证JWT后,直接从Payload中取出权限信息,判断是否有权限调用API,不需要再查数据库。

7. 邮件验证和密码重置:

  • 用户注册后,发送验证邮件,邮件中的链接,可以带上JWT,JWT的Payload中,存放用户ID和过期时间,用户点击链接,服务端验证JWT,完成邮箱验证。
  • 密码重置,也可以用JWT,用户申请密码重置,服务端生成JWT,发送到用户的邮箱,用户点击链接,服务端验证JWT,然后重置密码。
  • 这种场景,JWT的过期时间通常很短,比如1小时,而且只能使用一次,用后失效。

总结一下,JWT的应用场景很广泛,特别是前后端分离、移动App、微服务、分布式系统等场景,JWT是很合适的认证方案。但是,也要注意JWT的安全问题,做好安全防护。

十、JWT的常见坑和最佳实践

最后,总结一下JWT的常见坑和最佳实践,帮助大家在实际项目中,用好JWT,避免踩坑。

常见坑:

  1. 在Payload中存放敏感信息: JWT的Payload只是Base64Url编码,不是加密,任何人都可以解码,所以不要存放敏感信息,比如密码、银行卡号、身份证号等。
  1. 不验证签名: 服务端一定要验证JWT的签名,不要只解码Payload,否则攻击者可以伪造JWT。
  1. 使用none算法: 不要允许alg为none,否则攻击者可以不签名,伪造JWT。
  1. 信任JWT中的alg字段: 不要信任JWT中的alg字段,要固定签名算法,或者验证alg是否是预期的算法,防止算法替换攻击。
  1. 使用弱密钥: 对称加密的密钥,要足够强,足够长,不要用简单的字符串,容易被暴力破解。
  1. 过期时间设置太长: Access Token的过期时间,不要设置太长,比如几天、几个月,这样安全性差,应该设置短一些,比如15分钟、30分钟,配合Refresh Token续期。
  1. 不处理注销: JWT一旦签发,在过期之前,很难主动注销,所以要用黑名单机制,或者Token版本机制,处理注销,特别是用户修改密码、退出登录的时候。
  1. 存储在localStorage中,不防范XSS: JWT如果存储在localStorage中,容易被XSS攻击窃取,所以要防范XSS攻击,或者存储在HttpOnly的Cookie中。
  1. 存储在Cookie中,不防范CSRF: JWT如果存储在Cookie中,容易受到CSRF攻击,所以要设置SameSite属性,或者用CSRF Token,防范CSRF攻击。
  1. JWT太大: JWT每次请求都要携带,如果太大,会增加请求体积,影响性能,所以Payload中,不要存放太多信息,只存放必要的信息。

最佳实践:

  1. 使用HTTPS: 一定要使用HTTPS,加密传输,防止JWT被网络窃听。
  1. 短过期时间的Access Token + 长过期时间的Refresh Token: Access Token过期时间短,比如15分钟,保证安全性;Refresh Token过期时间长,比如7天,保证用户体验,配合Refresh Token续期。
  1. Refresh Token存储在HttpOnly的Cookie中: Refresh Token权限大,过期时间长,要存储在HttpOnly的Cookie中,设置Secure、SameSite属性,防止XSS和CSRF攻击。
  1. 黑名单机制处理注销: 用Redis存储注销的JWT的jti,设置过期时间和JWT一致,每次验证JWT的时候,检查是否在黑名单中。
  1. Token版本机制处理修改密码: 用户修改密码后,token_version加1,让所有旧的JWT失效。
  1. 固定签名算法: 服务端固定签名算法,不要信任JWT中的alg字段,防止算法替换攻击。
  1. 使用强密钥: 对称加密的密钥,至少32位,随机生成;或者使用非对称加密(RS256),用私钥签名,公钥验证。
  1. Payload中只存放必要信息: 比如用户ID、角色、token_version等,不要存放太多信息,不要存放敏感信息。
  1. 添加jti和iat、exp: JWT的Payload中,添加jti(唯一ID)、iat(签发时间)、exp(过期时间),方便验证和管理。
  1. 验证所有声明: 服务端验证JWT的时候,不仅要验证签名,还要验证exp(是否过期)、nbf(是否生效)、iss(签发者)、aud(受众)等声明,确保JWT有效。
  1. 刷新接口限流: 刷新接口要做限流,防止暴力破解。
  1. Refresh Token轮换: 每次刷新Access Token的时候,也生成新的Refresh Token,旧的Refresh Token失效,提高安全性。
  1. 监控和告警: 监控JWT的验证失败率、刷新频率、异常IP等,发现异常,及时告警和处理。

写在最后

JWT令牌面试题:我被问到的那些问题。

JWT是现在前后端分离项目中,最常用的认证方案之一,也是面试中的高频考点,掌握JWT,不仅对面试有帮助,对实际工作也很有帮助。

本文从JWT是什么、到JWT的结构、到工作原理、到和Session/Cookie的区别、到优缺点、到安全问题和防范、到刷新机制、到注销方案、到实际应用场景、再到常见坑和最佳实践,全面总结了JWT的面试题和知识点,希望能给大家一些参考。

JWT虽然强大,但是也有一些安全问题,在实际项目中,要根据业务场景,权衡优缺点,选择合适的认证方案,并且做好安全防护,确保用户的账号安全。

最后,用一句话结尾:

"JWT是一把双刃剑,用好了,能大大提升开发效率和系统扩展性;用不好,可能会有安全隐患。掌握JWT的原理、安全问题和最佳实践,才能用好JWT,构建安全、高效的系统。"

祝大家面试顺利,都能拿到心仪的offer!