JAVA-SpringBoot&JWT&JAR|WAR

Fc04dB Lv4

# JWT

之前写过身份验证鉴权的总结:Authentication - Fc04dB’s BLOG

Untitled

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()
//设置header
//.withHeader()

//设置payload部分
.withClaim("userid",1)
.withClaim("username","admin")
.withClaim("password","admin")
//.withExpiresAt() 设置过期时间

//创建signature
.sign(Algorithm.HMAC256("fcqb")); //算法和密钥

System.out.println(jwt);
}
}

image-20240904161846128

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6ImFkbWluIiwidXNlcmlkIjoxLCJ1c2VybmFtZSI6ImFkbWluIn0.dwnU1P_KAmhuTY4qqBAB2wZBOLlkvSQyU9FM8KhNAgE

解密:JSON Web Tokens - jwt.io

image-20240904161942760

解密 jwt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//解密JWT
public static void jwtcheck(String jwtdata){
//String jwtdata = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6ImFkbWluIiwidXNlcmlkIjoxLCJ1c2VybmFtZSI6ImFkbWluIn0.dwnU1P_KAmhuTY4qqBAB2wZBOLlkvSQyU9FM8KhNAgE";

//构建解密注册
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);
}

image-20240904230014311

构建在 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 for creating JWT -->
<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 for checking JWT -->
<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() //创建JWT
//设置header
//.withHeader()

//设置payload部分
.withClaim("userid", id)
.withClaim("username", user)
.withClaim("password", pass)
//.withExpiresAt() 设置过期时间

//创建signature
.sign(Algorithm.HMAC256("fcqb")); //算法和密钥

System.out.println(jwt);

return jwt;
}

//解密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;
}
}

image-20240904233323050

image-20240904233340076

image-20240904233348695

如果需要根据解密的数据执行不同的逻辑,可以使用以下代码进行判断

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 tool

此工具可用于测试 jwt 的安全性,地址是 https://github.com/ticarpi/jwt_tool

# 打包部署

  • ①jar 类型项目会打成 jar 包:

jar 类型项目使用 SpringBoot 打包插件打包时,会在打成的 jar 中内置一个 tomcat 的 jar。所以我们可以使用 jdk 直接运行该 jar 项目可,jar 项目中有一个功能,将功能代码放到其内置的 tomcat 中运行。我们直接使用浏览器访问即可。

IDEA 内直接用 maven 打包,先 clean 清理缓存,再 oackage 打包

image-20240905115541864

直接运行会报错

image-20240905115928566

java -jar XXX.jar 没有主清单属性以及找不到或无法加载主类的问题 - CSDN 博客

  • ②war 类型项目

在打包时需要将内置的 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
// @SpringBootApplication 注解用于标识这是一个Spring Boot应用程序的主类,它会自动扫描并加载与主类同包或子包下的组件。
public class TestJwtApplication extends SpringBootServletInitializer {

public static void main(String[] args) {
// SpringApplication.run() 用于启动Spring Boot应用程序。
SpringApplication.run(TestJwtApplication.class, args);
}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
// configure() 方法用于配置Spring Boot应用程序的构建,主要用于支持WAR文件的部署。
return builder.sources(TestJwtApplication.class);
}
}

img

  • Title: JAVA-SpringBoot&JWT&JAR|WAR
  • Author: Fc04dB
  • Created at : 2024-09-04 15:33:58
  • Updated at : 2024-09-05 13:28:33
  • Link: https://redefine.ohevan.com/2024/09/04/JAVA-SpringBoot-JWT-JAR-WAR/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
JAVA-SpringBoot&JWT&JAR|WAR