JAVA-SpringBoot&JWT&JAR|WAR
# JWT
之前写过身份验证鉴权的总结:Authentication - Fc04dB’s BLOG
JWT (JSONWeb Token) 是由服务端用加密算法对信息签名来保证其完整性和不可伪造;
Token 里可以包含所有必要信息,这样服务端就无需保存任何关于用户或会话的信息;
JWT 用于身份认证、会话维持等。由三部分组成,header、payload 与 signature。
JWT 是由这三个部分组成的字符串,形如 header.payload.signature
。JWT 通常用于在网络上安全地传输信息,例如在身份验证过程中传递令牌。
1
| eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQss
|
简单开发一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class JwtController { public static void main(String[] args) { String jwt = JWT.create()
.withClaim("userid",1) .withClaim("username","admin") .withClaim("password","admin")
.sign(Algorithm.HMAC256("fcqb"));
System.out.println(jwt); } }
|
1
| eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6ImFkbWluIiwidXNlcmlkIjoxLCJ1c2VybmFtZSI6ImFkbWluIn0.dwnU1P_KAmhuTY4qqBAB2wZBOLlkvSQyU9FM8KhNAgE
|
解密:JSON Web Tokens - jwt.io
解密 jwt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static void jwtcheck(String jwtdata){
JWTVerifier jwtde = JWT.require(Algorithm.HMAC256("fcqb")).build(); DecodedJWT verify = jwtde.verify(jwtdata); Integer userid = verify.getClaim("userid").asInt(); String username = verify.getClaim("username").asString(); String password = verify.getClaim("password").asString();
System.out.println(userid +" "+ username +" "+ password); }
|
构建在 web 应用上
index.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <html> <body> <h1>hello word!!!</h1> <p>this is an HTML page</p>
<form action="../jwtcreate" method="post"> id:<input type="text" name="id"><br> user:<input type="text" name="user"><br> pass:<input type="text" name="pass"><br> <input type="submit" value="create"> </form>
<form action="../jwtcheck" method="post"> jwtdata:<input type="text" name="jwtdata"><br> <input type="submit" value="check"> </form> </body> </html>
|
JwtController.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package com.fc0.testjwt.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.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody;
@Controller public class JwtController { @PostMapping("/jwtcreate") @ResponseBody public static String create(Integer id, String user, String pass) { String jwt = JWT.create()
.withClaim("userid", id) .withClaim("username", user) .withClaim("password", pass)
.sign(Algorithm.HMAC256("fcqb"));
System.out.println(jwt);
return jwt; }
@PostMapping("/jwtcheck") @ResponseBody public static String jwtcheck(String jwtdata){
JWTVerifier jwtde = JWT.require(Algorithm.HMAC256("fcqb")).build(); DecodedJWT verify = jwtde.verify(jwtdata); Integer userid = verify.getClaim("userid").asInt(); String username = verify.getClaim("username").asString(); String password = verify.getClaim("password").asString();
System.out.println(userid +" "+ username +" "+ password); return userid +" "+ username +" "+ password; } }
|
如果需要根据解密的数据执行不同的逻辑,可以使用以下代码进行判断
1 2 3 4 5
| if (username.equals("admin")) { return "admin"; } else { return "gay?"; }
|
# 安全问题
JWT 的安全问题 - tomyyyyy
1. 敏感信息泄露
我们能够轻松解码 payload 和 header,因为这两个都只经过 Base64Url 编码,而有的时候开发者会误将敏感信息存在 payload 中。
一般我们遇到 jwt 字符串可以到 https://jwt.io/ 这个网站解密。
2. 未校验签名
某些服务端并未校验 JWT 签名,所以,可以尝试修改 signature 后 (或者直接删除 signature) 看其是否还有效。
3. 签名算法可被修改为 none (CVE-2015-2951)
头部用来声明 token 的类型和签名用的算法等,比如:
1 2 3 4
| { "alg": "HS256", "typ": "JWT" }
|
以上 header 指定了签名算法为 HS256,意味着服务端利用此算法将 header 和 payload 进行加密,形成 signature,同时接收到 token 时,也会利用此算法对 signature 进行签名验证。
但是如果我们修改了签名算法会怎么样?比如将 header 修改为:
1 2 3 4
| { "alg": "none", "typ": "JWT" }
|
那么服务端接收到 token 后会将其认定为无加密算法, 于是对 signature 的检验也就失效了,那么我们就可以随意修改 payload 部分伪造 token。
https://jwt.io 将 alg 为 none 视为恶意行为 ,所以,无法通过在线工具生成 JWT,可以用 python 的 jwt 库来实现:
1 2
| import jwt jwt.encode({"user":"admin","action":"upload"},algorithm="none",key="")
|
用 none 算法生成的 JWT 只有两部分了,根本连签名都不存在。
4. 签名密钥可被爆破
jwt 使用算法对 header 和 payload 进行加密,如果我们可以爆破出加密密钥,那么也就可以随意修改 token 了。
JWT 爆破脚本:https://github.com/Ch1ngg/JWTPyCrack
也可以使用下面的脚本爆破
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| jwt_str = "xxx.ttt.zzz" path = "D:/keys.txt" alg = "HS256"
with open(path,encoding='utf-8') as f: for line in f: key_ = line.strip() try: jwt.decode(jwt_str,verify=True,key=key_,algorithm=alg) print('found key! --> ' + key_) break except(jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.ImmatureSignatureError): print('found key! --> ' + key_) break except(jwt.exceptions.InvalidSignatureError): continue else: print("key not found!")
|
5. 修改非对称密码算法为对称密码算法 (CVE-2016-10555)
JWT 的签名加密算法有两种,对称加密算法和非对称加密算法。
对称加密算法比如 HS256,加解密使用同一个密钥,保存在后端。
非对称加密算法比如 RS256,后端加密使用私钥,前端解密使用公钥,公钥是我们可以获取到的。
如果我们修改 header,将算法从 RS256 更改为 HS256,后端代码会使用 RS256 的公钥作为 HS256 算法的密钥。于是我们就可以用 RS256 的公钥伪造数据
6. 伪造密钥 (CVE-2018-0114)
jwk 是 header 里的一个参数,用于指出密钥,存在被伪造的风险。比如 CVE-2018-0114: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-0114
攻击者可以通过以下方法来伪造 JWT:删除原始签名,向标头添加新的公钥,然后使用与该公钥关联的私钥进行签名。
比如:
1 2 3 4 5 6 7 8 9 10 11
| { "typ": "JWT", "alg": "RS256", "jwk": { "kty": "RSA", "kid": "TEST", "use": "sig", "e": "AQAB", "n": "oUGnPChFQAN1xdA1_f_FWZdFAis64o5hdVyFm4vVFBzTIEdYmZZ3hJHsWi5b_m_tjsgjhCZZnPOLn-ZVYs7pce__rDsRw9gfKGCVzvGYvPY1hkIENNeBfSaQlBhOhaRxA85rBkg8BX7zfMRQJ0fMG3EAZhYbr3LDtygwSXi66CCk4zfFNQfOQEF-Tgv1kgdTFJW-r3AKSQayER8kF3xfMuI7-VkKz-yyLDZgITyW2VWmjsvdQTvQflapS1_k9IeTjzxuKCMvAl8v_TFj2bnU5bDJBEhqisdb2BRHMgzzEBX43jc-IHZGSHY2KA39Tr42DVv7gS--2tyh8JluonjpdQ" } }
|
此工具可用于测试 jwt 的安全性,地址是 https://github.com/ticarpi/jwt_tool
# 打包部署
jar 类型项目使用 SpringBoot 打包插件打包时,会在打成的 jar 中内置一个 tomcat 的 jar。所以我们可以使用 jdk 直接运行该 jar 项目可,jar 项目中有一个功能,将功能代码放到其内置的 tomcat 中运行。我们直接使用浏览器访问即可。
IDEA 内直接用 maven 打包,先 clean 清理缓存,再 oackage 打包
直接运行会报错
java -jar XXX.jar 没有主清单属性以及找不到或无法加载主类的问题 - CSDN 博客
在打包时需要将内置的 tomcat 插件排除,配置 servlet 的依赖和修改 pom.xml,
然后将 war 文件放到 tomcat 安装目录 webapps 下,启动运行 tomcat 自动解析即可。
pom.xml 加入或修改:
<packaging>war</packaging>
启动类里面加入配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @SpringBootApplication
public class TestJwtApplication extends SpringBootServletInitializer {
public static void main(String[] args) { SpringApplication.run(TestJwtApplication.class, args); }
@Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(TestJwtApplication.class); } }
|