本篇文章,我们一起来探讨一下JWT认证的优缺点以及常见问题的解决方法。JWT的优势相对于Session认证的方式,使用JWT进行身份认证主要有两个方面,四个优势。statelessJWT本身包含了认证所需的所有信息,所以我们的服务器不需要存储Session信息。这显然增加了系统的可用性和可扩展性,大大减轻了服务器的压力。但是,也正是因为JWT的无状态性,也导致了它最大的劣势:不可控!比如我们要丢弃一个1个月的JWT或者在JWT的有效期内改变它的权限,是不会立即生效的,通常需要等到有效期结束。再比如,当用户Logout时,JWT仍然有效。除非,我们在后台增加额外的处理逻辑,比如存储无效的JWT,后台先验证JWT是否有效再处理。具体的解决方案会在后面的内容中详细介绍,这里只是简单的提一下。有效避免CSRF攻击CSRF(CrossSiteRequestForgery)一般翻译为跨站请求伪造,属于网络攻击领域。与SQL脚本注入、XSS等安全攻击相比,CSRF并没有它们那么出名。但是,这确实是我们在开发系统时必须考虑的安全风险。就连业界技术标杆谷歌的产品Gmail也在2007年出现了CSRF漏洞,给Gmail用户造成了极大的损失。那么究竟什么是跨站请求伪造?简单的说就是利用你的身份做一些坏事(发送一些对你不友好的请求,比如恶意转账)。举个简单的例子:小庄登录了一家网上银行。他来到网银的帖子区,看到一个帖子下面有个链接,上面写着“科学理财,年利润过万”。小庄好奇的点开了。点开这个链接后,我发现我的账户少了一万元。怎么了?原来黑客在链接中隐藏了一个请求。这个请求直接用小庄的身份向银行发送转账请求,也就是通过你的cookie向银行发送请求。科学理财,年利润率过万CSRF攻击需要依赖Cookie,其中的SessionIDSession认证中的cookie是由浏览器发送给服务器的,只要有请求就会携带cookie。有了这个特性,即使黑客无法获取到你的信息SessionID,只要误点击攻击链接,也能达到攻击效果。另外,并不是一定要点击链接才能达到攻击效果。很多时候,只要打开某个页面,就会发生CSRF攻击。那为什么JWT会出现这样的问题呢?一般情况下,如果我们使用JWT,在我们成功登录并获取JWT后,通常会选择存放在localStorage中。前端后续的每一次请求都会伴随着这个JWT,整个过程完全不会涉及cookies。所以,即使你点击了一个非法链接,向服务器发送了一个请求,这个非法请求也不会携带JWT,所以这个请求是非法的。总结一下就一句话:使用JWT做认证不需要依赖Cookie,可以避免CSRF攻击。但是,也存在XSS攻击的风险。为避免XSS攻击,您可以选择删除存储在标记为httpOnly的cookie中的JWT。但是,这导致你必须自己提供CSRF保护,所以我们在实际项目中通常不会这样做。避免XSS攻击的一种常见方法是过滤掉请求中存在XSS攻击风险的可疑字符串。在Spring项目中,我们一般通过创建XSS过滤器来实现。@Component@Order(Ordered.HIGHEST_PRECEDENCE)publicclassXSSFilter实现过滤器chain.doFilter(wrappedRequest,response);}//其他方法}适用于使用Session进行认证的移动应用,需要在服务端保存一条信息,而该方法会依赖Cookie(需要Cookie保存SessionId),所以不适合移动端.但是使用JWT做认证就没有这个问题,因为只要客户端能存储JWT就可以使用,而且JWT还可以跨语言使用。单点登录友好如果使用Session进行身份认证,要实现单点登录,需要在一台电脑上保存用户的Session信息,同时也会遇到跨域cookie的通病。但是如果使用JWT进行认证,JWT是存储在客户端的,就不会存在这些问题。JWT认证常见问题及解决方案JWT在注销、登录等场景下仍然有效。类似的具体相关场景包括:注销;更改密码;服务器修改用户的权限或角色;用户帐户被阻止/删除;用户被服务器强制退出;用户被踢下线;...这个问题在Session认证方式中是不存在的,因为在Session认证方式中,如果出现这种情况,服务器删除对应的Session记录即可。但是使用JWT认证的方式不好解决。我们也说过,JWT一旦发出去,如果后台不加其他逻辑的话,一直有效到失效为止。那么我们如何解决这个问题呢?在查阅了很多资料后,我简单总结了以下四种解决方案:1.将JWT存储在内存数据库中将JWT存储在DB中,这里Redis内存数据库是一个不错的选择。如果需要使JWT失效,直接从Redis中删除JWT即可。但是这样会导致每次使用JWT发送请求都要从DB中查询JWT是否存在的步骤,违反了JWT的无状态原则。2、黑名单机制与上述方法类似。使用Redis等内存数据库来维护黑名单。如果要让某个JWT失效,只需要将这个JWT加入黑名单即可。然后每次使用JWT发起请求,都会先判断这个JWT是否存在于黑名单中。前两种方案的核心是存储有效的JWT或者黑名单指定的JWT。虽然这两种方案都违背了JWT的无状态原则,但是我们在一般的实际项目中通常都会使用这两种方案。3.修改秘密(Secret):我们为每个用户创建一个专属的秘密。如果我们想让一个JWT失效,我们可以直接修改对应用户的secret。但是,这比引入前两种内存数据库的危害更大:如果服务是分布式的,那么每次下发新的JWT时,key都要在多台机器上进行同步。为此,您需要将密钥存储在数据库或其他外部服务中,这与会话身份验证没有太大区别。如果用户同时在两个浏览器上打开系统,或者在手机上打开系统,如果从一个地方注销账号,不宜从其他地方重新登录。4.保持令牌有效期短,频繁轮换是一种简单的方法。但是不会持久记录用户的登录状态,用户需要经常登录。另外,修改密码后JWT仍然有效的问题也比较容易解决。说一个我觉得比较好的方式:使用用户密码的哈希值对JWT进行签名。因此,如果密码发生变化,之前的任何令牌都会自动验证失败。JWT续费问题一般建议设置JWT的有效期不要太长,那么JWT过期后如何进行鉴权,如何实现JWT的动态刷新,避免用户频繁需要重新登录呢?我们先来看一下Session认证中的一般做法:如果session有效期为30分钟,如果用户在30分钟内访问成功,则session有效期延长30分钟。在JWT认证的情况下,我们应该如何解决续费问题呢?在查阅了很多资料后,我简单总结了以下四种解决方案:1.与Session认证中的做法类似,该方案适用于大部分场景。假设服务器给的JWT有效期设置为30分钟。服务端每次检查时,如果发现JWT的有效期即将到期,服务端会重新生成JWT给客户端。客户端每次请求都会检查新旧JWT,不一致则更新本地JWT。这种方式的问题是请求只会在快过期的时候更新JWT,对客户端不是很友好。2.每次请求返回一个新的JWT的思路很简单,但是开销会比较大,尤其是服务器需要存储和维护JWT的时候。3.JWT有效期设置到午夜。该方案是一种折中方案,保证大部分用户在白天可以正常登录,适用于对安全性要求不高的系统。4.用户登录返回两个JWT。第一个是accessJWT,它的过期时间是JWT本身的过期时间,比如半小时,还有一个是refreshJWT,过期时间比较长,比如1天。客户端登录后,将accessJWT和refreshJWT保存在本地,每次访问时将accessJWT传递给服务端。服务器检查accessJWT的有效性,如果过期,则将refreshJWT发送给服务器。如果有效,服务器会为客户端生成一个新的accessJWT。否则,客户端可以重新登录。该方案的缺点是:需要客户端的配合;用户登出时,两个JWT必须同时失效;在重新请求获取JWT的过程中,JWT会出现短时间不可用(可以在客户端设置一个定时器,当accessJWT即将到期时,提前通过refreshJWT获取新的accessJWT)。综上所述,JWT的一个重要优势就是它是无状态的,但实际上,我们要想在实际项目中合理的使用JWT,还是需要保存JWT信息的。JWT不是灵丹妙药,有很多缺陷。是选择JWT还是Session方案取决于项目的具体需求。不要为难JWT,看不起其他认证方案。另外,也可以不使用JWT,直接使用普通Token(随机生成,无具体信息)结合Redis进行身份认证。我在第8期《优质开源项目推荐》推荐的Sa-Token项目是一个比较完善的基于JWT的身份认证方案,支持自动续费、踢人下线、账号封禁、同时登录互斥end等功能,有兴趣的朋友可以看看。最全学习笔记大厂真题+微服务+MySQL+分布式+SSM框架+Java+Redis+数据结构与算法+网络+Linux+Spring全家桶+JVM+高并发+专业学习思维脑图+面试宝典