JWT全称JSONWebToken,是一种非常流行的跨域认证方案,常用于单点登录-场景。有些人认为它非常易于使用。使用后,就不需要再使用redis来实现服务端的认证过程了。但也有人认为它存在先天缺陷,根本无法使用。为什么是这样?传统的身份验证方法从登录场景开始。你用过那么多的网站和APP,很多都是需要登录的,那我们就选一个场景说吧。以电子商务系统为例。如果您想下订单,您首先需要注册一个帐户。拥有账户后,您需要输入用户名(如手机号码或邮箱地址)和密码,完成登录过程。之后一段时间内再次进入系统时不需要再输入用户名和密码。只有当您长时间未登录(例如一个月未登录)访问系统时,才需要重新输入用户名和密码。对于那些经常使用的网站或应用,通常是不用长时间输入密码的,这样当你换了电脑或手机后,你可能会记不住一些经常使用的网站或APP的密码了.早期的Cookie-Session认证方式早期的互联网以web为主,客户端是浏览器,所以Cookie-Session方式是早期最常用的认证方式。直到现在,一些网站仍然使用这种方式进行身份验证。认证过程大致如下:用户输入用户名、密码或使用短信验证码登录系统;服务器验证后,创建一个Session信息,将SessionID保存在cookie中,返回给浏览器;客户端下次发起请求时,自动带上cookie信息,服务端通过cookie获取Session信息进行校验;image-20200706173031724但是为什么说是传统的认证方式呢,因为现在人人都有智能手机,很多人都不用电脑,平时都是用手机各种APP,比如淘宝,拼多多等等。在这个下趋势上,传统的Cookie-Session遇到了一些问题:1、首先,Cookie-Session只能用于web场景。如果是APP,APP是没有地方存放cookie的。现在的产品基本都是同时提供web和APP,有的产品甚至只有APP。2、退一步说,你做的产品只支持web,跨域的问题也要考虑,但是cookie不能跨域。以天猫商城为例。进入天猫商城,最上方会看到天猫超市、天猫国际、天猫会员等菜单。点击这些菜单会进入不同的域名,不同域名下的cookie是不同的。A域名下无法获取B域名的cookies,即使是子域名。图片-202007061739392913。如果是分布式服务,需要考虑Session同步。现在的互联网网站和APP基本都是分布式部署,也就是服务器端有不止一台服务器。当用户在页面上执行登录操作时,必须向其中一台服务器请求登录操作。您的身份信息必须保存。传统的方式是保存Session。接下来,问题来了。您访问了多个页面。这时,一个请求被负载均衡并路由到另一台服务器(不是你登录的那台)。当后台收到请求后,需要校验用户身份信息和权限,于是接口开始从Session中获取用户信息。但是这个服务器不是你当时登录的,你的Session没有保存,所以后台服务认为你是一个未登录的用户,无法返回数据给你。所以,为了避免这种情况的发生,需要做Session同步。一个服务器收到登录请求后,当前服务器保存Session后,还需要同步其他几台服务器。4、cookies存在CSRF(跨站请求伪造)风险。跨站请求伪造是一种强制用户对当前登录的Web应用程序执行非预期操作的攻击方法。CSRF利用网站对用户网络浏览器的信任。简单的说,攻击者通过一些技术手段,诱使用户的浏览器访问经过他认证的网站,并进行一些操作(比如购买商品)。由于浏览器已通过身份验证,因此访问的网站将被视为由真实用户发起的操作。假设我是一名黑客,我在您经常访问的技术网站之一中发现了一个CSRF漏洞。发布文章支持html格式,然后我在html中添加了一些危险的内容,比如假设指向的地址src是一个你平时使用的购物网站的支付地址(当然只是举个例子,真正的攻击没有这么简单),如果你之前登录过并且保存了识别你身份信息的cookie。当你滚动到我发布的这篇文章时,一旦加载了img标签,这种CSRF攻击就会起作用,在你不知情的情况下向网站付款。修改版的Cookie-Session在传统的Cookie-Session认证中存在很多问题,所以可以修改上面的方案。1.Cookies的改造既然cookies不能在非浏览器如APPs中使用,那么就用其他方式代替cookies作为客户端存储。应该改变什么?在web中可以使用本地存储,在APP中可以使用客户端数据库,这样就可以实现跨域,避免CSRF。2、服务端不再保存Session,将Session信息取出存储在Redis等内存数据库中,既提高了速度,又避免了Session同步的问题;改造后变成如下认证流程:用户输入用户名、密码或短信验证码登录系统;服务端进行验证,将验证信息构建的数据结构存储在Redis中,并返回key值给客户端;客户端获取返回的密钥并将其存储在本地存储或本地数据库中;下次客户端再次请求时,将key值追加到header或requestbody中;服务器根据获取的key从Redis获取认证信息;下面两张图分别演示了首次登录和非首次登录的过程。对首次登录非首次登录进行了猛虎蜕变,解决了传统Cookie-Session方式存在的问题。这个改造需要开发者在项目中完成。改造肯定费时费力,而且可能存在漏洞。当JWT在舞台上,JWT可以在舞台上。JWT是Cookie-Session修改版的具体实现,省去了自己造轮子的时间。JWT的另一个优点是您不需要在服务器端存储身份验证。信息(如token)完全由客户端提供,服务端仅根据JWT自身提供的解密算法即可验证用户的合法性,这个过程是安全的。如果你刚接触JWT,最疑惑的一点可能是:为什么JWT可以完全依赖客户端(比如浏览器)来实现认证功能,而所有的认证信息都存在于客户端,如何保证安全?JWT数据结构JWT最终的形式是一个字符串,由三部分组成:header、payload和signature,以“.”分隔。像下面这样:997EDE1C-5689-4C3F-98E8-25C25BBEC3FCheader以JSON格式表示,用于表示token类型和加密算法。形式如下,表示使用JWT格式,加密算法采用HS256,这是最常用的算法,其他的还有很多。{"alg":"HS256","typ":"JWT"}对应上图中红色header部分,需要Base64编码。payload用来存放服务器需要的数据,比如用户信息,比如姓名、性别、年龄等,需要注意的是最好不要把重要的机密信息放在这里,比如密码。{"name":"ancientkite","introduce":"handsome"}此外,JWT还规定了7个字段供开发者选择。iss(issuer):发行者exp(expirationtime):过期时间sub(subject):主题aud(audience):受众nbf(NotBefore):生效时间iat(IssuedAt):发行时间jti(JWTID):序号这部分信息也是用Base64编码的。SignatureSignature有一个计算公式。HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),Secret)是使用HMACSHA256算法计算出来的。这个方法有两个参数。第一个参数是(base64-encodedheader+base64-encodedpayload)与点数相连,后一个参数是一个自定义的字符串key,这个key不应该暴露给客户端,应该只有服务端知道。如何使用了解了JWT的结构和算法之后,如何使用呢?假设我在这里有一个网站。1、用户登录网站时,需要输入用户名、密码或短信验证方式进行登录,当登录请求到达服务器后,服务器对账号和密码进行验证,然后计算JWT字符串并将其返回给客户端。2.客户端获取到JWT字符串后,保存在cookie中或者浏览器的LocalStorage中。3、再次发送请求,比如请求用户设置页面时,将JWT字符串添加到HTTP请求头中,或者直接放入请求体中。4、服务端拿到JWT字符串后,使用base64header和base64payload,通过HMACSHA256算法计算签名部分,比较计算结果与传输的签名部分是否一致。如果一致,说明这个请求没有问题。如果不一致,则说明该请求已经过期或者是非法请求。如何保证安全保证安全的关键是HMACSHA256或者同类型的加密算法,因为加密过程是不可逆的,所以根据传给前端的JWT,密钥信息是无法逆向的。另外,不同的header和payload加密后得到的签名是不同的,所以如果有人更改了payload部分的信息,最终加密后的结果肯定和修改前不一样,所以最终验证的结果是invalid要求。其他人获得完整的JWT是否安全?假设payload部分存储了权限级别相关的字段,盗贼在拿到JWT字符串后想修改JWT字符串为更高的权限级别。如上所述,在这种情况下,肯定不会成功,因为加密后的签名会不一样,服务器很容易识别。那么如果盗贼拿到之后不做改动,直接使用,那就没办法了。为了更大限度的防止被盗用,应该使用HTTPS协议而不是HTTP协议,这样可以有效的防止一些中间劫持攻击。有的同学会说,这样一点都不安全。您可以使用JWT字符串轻松模拟请求。确实是这样,但是前提是你怎么才能得到。除了上面提到的劫持,还有别的办法吗?除非盗贼直接拿走了你的电脑,那么,对不起,不仅JWT不安全,其他任何网站,任何认证方式都不安全。虽然这种情况很少见,但是在使用JWT的时候还是要注意合理设置过期时间,不要太长。一个问题JWT有一个问题导致很多开发团队放弃使用它,那就是JWT令牌一旦发出,服务器就没有办法丢弃它,除非它自己过期。有很多应用默认只允许最新的登录客户端正常使用,不允许多终端登录。JWT做不到,因为发行了新的token,但是旧的token在过期之前仍然可用。在这种情况下,服务端需要添加相应的逻辑。常用的JWT库JWT官网列出了各种语言对应的库,其中Java如下。image-20200817112359199以java-jwt为例。1.导入对应的Maven包。com.auth0java-jwt3.10.32.登录时调用create方法获取一个token,返回前端。publicstaticStringcreate(){try{Algorithmalgorithm=Algorithm.HMAC256("secret");Stringtoken=JWT.create().withIssuer("auth0").withSubject("subject").withClaim("name","古风筝");,登录成功后,再次请求时将token放在header或者请求体中,服务器会校验token。publicstaticBooleanverify(Stringtoken){try{Algorithmalgorithm=Algorithm.HMAC256("secret");JWTVerifierverifier=JWT.require(algorithm).withIssuer("auth0").build();//ReusableverifierinstanceDecodedJWTjwt=verifier.verify(token);Stringpayload=jwt.getPayload();Stringname=jwt.getClaim("name").asString();Stringintroduce=jwt.getClaim("introduce").asString();System.out.println(payload);System.out。println(name);System.out.println(introduce);returntrue;}catch(JWTVerificationExceptionexception){//Invalidsignature/claimsreturnfalse;}}4、使用创建方法生成令牌,并使用验证方法验证一次。publicstaticvoidmain(String[]args){Stringtoken=create();Booleanresult=verify(token);System.out.println(result);}得到下面的结果eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiaW50cm9kdWNlIjoi6Iux5L-K5r2H5rSSIiwiaXNzIjoiYXV0aDAiLCJuYW1lIjoi5Y-k5pe255qE6aOO562dIn0.ooQ1K_XyljjHf34Nv5iJvg1MQgVe6jlphxv4eeFt8pAeyJzdWIiOiJzdWJqZWN0IiwiaW50cm9kdWNlIjoi6Iux5L-K5r2H5rSSIiwiaXNzIjoiYXV0aDAiLCJuYW1lIjoi5Y-k5pe255qE6aOO562dIn0古时使用kitehandsometrue的create方法创建的JWT字符串可以通过验证。而如果我修改了JWT字符串中的load部分,两个点之间的部分,然后调用verify方法去验证,就会出现JWTVerificationException,无法通过验证。本文转载自微信公众号“古风筝”,可通过以下二维码关注。转载本文请联系古风筝公众号。