前言
今天讲SpringBoot的最后一个漏洞点,就是JWT(JSON Web Token)身份权鉴。这是Java特有的一种身份鉴别技术,类似于PHP中的Cookie和Session。
什么是JWT
从这张图可以看出来浏览器通过POST请求发送账号和密码到服务端,服务端创建一个JWT并且返回给浏览器。浏览器访问某些页面时通过发送JWT到服务端,服务端检验JWT的合法性,返回对应的功能或者页面。
JWT(JSON Web Token)是由服务端用加密算法对信息签名来保证其完整性和不可伪造;
Token里可以包含所有必要信息,这样服务端就无需保存任何关于用户或会话的信息;
JWT用于身份认证、会话维持等。由三部分组成,header、payload与signature。
Header(头部): JWT 的头部通常包含两部分信息:声明类型(typ)和使用的签名算法(alg)。这些信息以 JSON 格式存在,然后进行 Base64 编码,形成 JWT 的第一个部分。头部用于描述关于该 JWT 的元数据信息。
{"alg": "HS256","typ": "JWT"
}
Payload(负载): JWT 的负载包含有关 JWT 主题(subject)及其它声明的信息。与头部一样,负载也是以 JSON 格式存在,然后进行 Base64 编码,形成 JWT 的第二个部分。
{"sub": "1234567890","name": "John Doe","iat": 1516239022
}
Signature(签名): JWT 的签名是由头部、负载以及一个密钥生成的,用于验证 JWT 的真实性和完整性。签名是由指定的签名算法对经过 Base64 编码的头部和负载组合而成的字符串进行签名生成的。
例如,使用 HMAC SHA-256 算法生成签名:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret
)
项目搭建
新建一个项目名为Java-demo。
依赖选择。
这里要引入一下依赖,引入3.4.0版本的JWT。
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
安全问题
创建一个Java类叫JWTcontroller,并且写入以下代码。
package com.sf.maven.jwtdemo.demos.web;import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;public class JWTcontroller {public static void main(String[] args) {String JWTstring = JWT.create()//创建header部分//.withHeader()//创建payload部分.withClaim("userid",1).withClaim("username","admin").withClaim("password","123456")//创建signature,并且设置签名算法为HMAC256,密钥为wlwnb.sign(Algorithm.HMAC256("wlwnb"));System.out.println(JWTstring);}
}
直接运行一下代码,看看输出的JWT是啥样子的,可以看到生成了一个JWT数据。
这个JWT数据被两个点隔成三部分,分别是
Header:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
payload:eyJwYXNzd29yZCI6IjEyMzQ1NiIsInVzZXJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiJ9
Signature:C2bW9w4bU3UGFTCpf9x247-DqUeso9TbIB6KeGzmJdM
我们拿到JWT解密网站去看看,可以看到我们解密的payload和刚刚我们设置的对得上。
JSON Web Tokens - jwt.io
有人可能说那么我把admin改为其它的用户不就可以实现任意用户登录了?其实我们往下可以看到签名这里显示密钥为空,你修改后加密出来的JWT是不可被服务端识别的,除非你知道加密的密钥。
直接做个实验就懂了,我们先写一个表单提交的页面,在resource/static/index.html里面写入以下代码,创建一个登录提交表单。
<form action="../jwtcreate" method="post">id:<input type="text" name="id"><br>user:<input type="text" name="username"><br>password:<input type="text" name="password"><br><input type="submit" value="create">
</form>
接着我们在创建JWT的代码里面添加一下解密JWT的代码,并且提取数据。
package com.sf.maven.jwtdemo.demos.web;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class JWTcontroller {//创建JWT@PostMapping("/jwtcreate")@ResponseBodypublic static String main(Integer id,String username,String password) {String JWTstring = JWT.create().withClaim("userid",id).withClaim("username",username).withClaim("password",password).sign(Algorithm.HMAC256("wlwnb"));System.out.println(JWTstring);return JWTstring;}//模拟用户身份检测,JWT数据解密@PostMapping("/JWTcheck")@ResponseBodypublic static void JWTcheck(String JWTdata) {//构建解密注册JWTVerifier verifier = JWT.require(Algorithm.HMAC256("wlwnb")).build();//解密注册数据DecodedJWT jwt = verifier.verify(JWTdata);//提取注册解密数据,Integer userid = jwt.getClaim("userid").asInt();String username = jwt.getClaim("username").asString();String password = jwt.getClaim("password").asString();System.out.println(userid+" "+username+" "+password);//获取头部数据//jwt.getHeader();//获取签名数据//jwt.getSignature();}}
运行JwtDemoApplication,部署页面,可以看到这是我们刚刚写的表单页面。
填入数据点击create,可以看到返回了生成的JWT。
现在我们来模拟攻击者,先在原有的代码上添加一段判断的代码。
接着在index页面写个JWT数据的提交表单,使其可以解密JWT数据。
运行代码访问页面,把生成的JWT数据提交解密。
可以看到如果是admin用户的话就会返回you are admin。
如果不是admin,返回如下。
OK,那么我们现在来做个实验,可以看到我们现在JWT解密的payload为wlwnb。
那么我们现在把wlwnb改为admin,再在这个网站加密成JWT数据去提交,看看是否返回admin页面,可以看到是返回了一个报错页面。
这是为啥呢,其实上面我们讲过了,就是没有密钥的问题,你没有密钥那么加密出来的数据代码这边就无法解密,自然就是返回报错页面。
也就是说JWT不需要考虑你的账号密码正确性,只需看你传过来的JWT是否能解密即可,因为你不知道密钥,伪造的数据无法解密直接报错。我们添加密钥再去伪造admin的JWT的数据试试。
结果是代码判定我们是admin,这里更加验证了我们上面的说法。所以JWT的安全问题主要是算法泄露或者在开发的时候alg设置为“none”,这就意味着不用算法对数据进行加密,那么此时没有密钥也可以解密。
打包部署
这个其实没啥可说的,就是顺便讲一下Java的项目该如何在服务器上面部署。其实是非常简单,就是把项目打包成jar包,再放到服务器上面运行即可。
来到我们Maven这里,点击生命周期,点击clean,清除一下缓存。
接着再点击package。
此时在目录这里便生成了一个jar包。
总结
基本关于SpringBoot的知识点都讲完了,后续会讲一下该如何利用,Java安全估计还得要十几篇文章。
最后,以上仅为个人的拙见,如何有不对的地方,欢迎各位师傅指正与补充,有兴趣的师傅可以一起交流学习。