3655 字
18 分钟
第40天:安全开发-JavaEE应用

ea59c3b9053b5314c31919adb8c71a91

040-安全开发-JavaEE应用&SpringBoot框架&JWT身份鉴权&打包部署JAR&WAR#

目录#

知识点与演示案例:#

1、JavaEE-SpringBoot-身份鉴权-JWT技术
2、JavaEE-SpringBoot-打包部署-JAR&WAR

演示案例:#

➢SpringBoot-身份鉴权-JWT技术
➢SpringBoot-打包部署-JAR&WAR

792ad269e39da677869f9e410f7cd757

浏览器与服务器基于 JWT 的认证流程:#

  1. 用户登录请求

    • 浏览器发送 POST /users/login 请求到服务器,包含 usernamepassword(这是身份认证的起点,用户需提供凭证)。
  2. 服务器生成 JWT

    • 服务器验证用户名和密码(核心步骤,验证失败则拒绝登录)。
    • 若验证成功,使用密钥(Secret) 生成 JWT(JSON Web Token)(密钥是安全性的核心,必须妥善保管)。
    • JWT 包含用户信息(如用户ID、角色等)和有效期(有效期用于控制令牌的使用时长,降低被盗用风险)。
  3. 返回 JWT 给浏览器

    • 服务器将生成的 JWT 通过 HTTP 响应返回给浏览器(通常放在响应体或 Set-Cookie 头中,不同存储方式有不同的安全考量,如Cookie可配置HttpOnly、Secure属性防XSS)。
  4. 浏览器发送 JWT 后续请求

    • 浏览器在后续请求的 Authorization 请求头中添加 JWT,格式为:
      Authorization: Bearer <JWT>
      (这种方式是行业通用规范,便于服务器统一解析)
  5. 服务器验证 JWT

    • 服务器用相同的密钥验证 JWT 的签名,确保未被篡改(签名验证是防止令牌被伪造的关键)。
    • 从 JWT 的 Payload 中直接提取用户信息(无需查询数据库,提升效率,但依赖令牌本身的安全性)。
  6. 返回响应

    • 服务器根据用户信息处理请求,将结果返回给浏览器。

关键点总结:#

  • 无状态认证:服务器无需保存会话状态,JWT 自包含用户信息(减少服务器存储压力,但令牌一旦发出无法主动撤销,需依赖有效期)。
  • 安全性
    • 密钥(Secret)用于签名,防止 JWT 被篡改(密钥泄露会导致令牌可伪造,是最高风险点)。
    • 全程需使用 HTTPS 防止 JWT 被窃取(HTTP 传输易被中间人拦截令牌)。
  • 效率
    • 验证 JWT 签名后可直接读取用户信息,减少数据库查询(提升接口响应速度,但需平衡令牌大小与传输效率)。

⚠️ 注意:JWT 的 Payload 仅经过 Base64 编码(非加密),切勿存放敏感信息(如密码)(Base64 可直接解码,敏感信息会暴露)。

SpringBoot-身份鉴权-JWT技术#

JWT(JSON Web Token)是由服务端用加密算法对信息签名来保证其完整性和不可伪造
Token里可以包含所有必要信息,这样服务端就无需保存任何关于用户或会话的信息;
JWT用于身份认证、会话维持等。由三部分组成:header、payload与signature

JWT的组成#

HeaderPayloadSignature 是 JSON Web Token(JWT)的三个主要组成部分。

  1. Header(头部)
    JWT 的头部通常包含两部分信息:声明类型(typ)和使用的签名算法(alg)。这些信息以 JSON 格式存在,然后进行 Base64 编码,形成 JWT 的第一个部分。头部用于描述关于该 JWT 的元数据信息。

    {
    "alg": "HS256", // 签名算法(如HMAC SHA-256)
    "typ": "JWT" // 令牌类型
    }

    (算法选择影响安全性,HS256是对称加密,需双方共享密钥;RS256是非对称加密,用私钥签名、公钥验证,更适合分布式系统)

  2. Payload(负载)
    JWT 的负载包含有关 JWT 主题(subject)及其它声明的信息。与头部一样,负载也是以 JSON 格式存在,然后进行 Base64 编码,形成 JWT 的第二个部分。

    {
    "sub": "1234567890", // 主题(通常是用户ID)
    "name": "John Doe", // 自定义声明(用户名)
    "iat": 1516239022 // 签发时间(Unix时间戳)
    }

    (标准声明包括iss(签发者)、exp(过期时间)、sub(主题)等,自定义声明需避免敏感信息)

  3. Signature(签名)
    JWT 的签名是由头部、负载以及一个密钥生成的,用于验证 JWT 的真实性和完整性。签名是由指定的签名算法对经过 Base64 编码的头部和负载组合而成的字符串进行签名生成的。

    例如,使用 HMAC SHA-256 算法生成签名:

    HMACSHA256(
    base64UrlEncode(header) + "." + // 编码后的头部
    base64UrlEncode(payload), // 编码后的负载
    secret // 服务器密钥
    )

    (签名是JWT的安全核心,若签名验证失败,令牌会被视为无效)

最终,JWT 是由这三个部分组成的字符串,形如 header.payload.signature。JWT 通常用于在网络上安全地传输信息,例如在身份验证过程中传递令牌。

实现步骤#

1、创建项目引入依赖#

image-20250816143327414

image-20250816143403763

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version> <!-- JWT操作的核心依赖,提供生成、解析令牌的工具类 -->
</dependency>

image-20250816143651244

2、创建JWT并配置JWT#

对应目录下创建JwtController.java

package cn.xiaodi.testjwt.demos.web;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class JwtController {
// 模拟用户的JWT身份创建,数据的JWT加密
@PostMapping("/jwtcreate") // 处理POST请求,路径为/jwtcreate
@ResponseBody // 直接返回字符串,而非视图
public static String create(Integer id, String user, String pass) {
// 创建JWT令牌
String jwttoken = JWT.create()
// 设置创建的Header部分(可省略,默认使用算法配置)
//.withHeader()
// 设置创建的Payload部分(自定义声明,存储用户信息)
.withClaim("userid", id) // 用户ID
.withClaim("username", user) // 用户名
.withClaim("password", pass) // 注意:实际开发中禁止存储密码!此处仅为演示
// 设置时效(JWT过期时间,如:.withExpiresAt(new Date(System.currentTimeMillis() + 3600000)) 表示1小时后过期)
//.withExpiresAt()
// 创建设置的Signature部分,指定算法(HMAC256)和密钥(xiaodisec)
.sign(Algorithm.HMAC256("xiaodisec")); // 密钥硬编码存在安全风险,实际需用配置文件存储
// 打印创建好的jwt
System.out.println(jwttoken);
return jwttoken;
}
}

3、解析JWT#

// 模拟JWT身份的检测,JWT数据解密
@PostMapping("/jwtcheck") // 处理POST请求,路径为/jwtcheck
@ResponseBody
public static String jwtcheck(String jwtdata) {
// 构建JWT解密验证器:指定算法(与生成时一致)和密钥
JWTVerifier jwt = JWT.require(Algorithm.HMAC256("xiaodisec")).build();
// 解密并验证JWT:若签名无效或过期,会抛出异常
DecodedJWT verify = jwt.verify(jwtdata);
// 提取Payload部分的用户信息
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 "admin page"; // 验证通过后返回的资源
// 如果需要根据解密的数据执行不同的逻辑,可以使用以下代码进行判断
// if (username.equals("admin")) {
// return "admin";
// } else {
// return "gay?";
// }
// 提取Header部分
// verify.getHeader();
// 提取Sign签名部分
// verify.getSignature();
}

4、配置前端提交数据访问客户端页面#

  • 在resources→static→index.html创建如下的前端页面
<html>
<body>
<h1>hello word!!!</h1>
<p>this is an HTML page</p>
<!-- Form for creating JWT:用户输入信息生成JWT -->
<form action="../jwtcreate" method="post">
id:<input type="text" name="id"><br> <!-- 对应后端create方法的id参数 -->
user:<input type="text" name="user"><br> <!-- 对应username参数 -->
pass:<input type="text" name="pass"><br> <!-- 对应password参数 -->
<input type="submit" value="create"> <!-- 提交按钮,触发/jwtcreate请求 -->
</form>
<!-- Form for checking JWT:输入JWT进行验证 -->
<form action="../jwtcheck" method="post">
jwtdata:<input type="text" name="jwtdata"><br> <!-- 输入生成的JWT字符串 -->
<input type="submit" value="check"> <!-- 提交按钮,触发/jwtcheck请求 -->
</form>
</body>
</html>

image-20250816154641407

  • 对应输入id,用户名,密码,点击创建得到eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6IjEyMzQ1NiIsInVzZXJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiJ9.q__DmCYaffqXmQweBgITek-NmhsSAhgwExNA3lQspQk

  • 官网进行解密(官网提供JWT编码/解码工具,可用于调试,但敏感令牌需避免在此类工具中暴露)

  • 在页面对应输入对应jwtdata,进行解密

  • 跳转至解密成功页面,回显admin page

image-20250816155619667

image-20250816155632559

image-20250816155801716

image-20250816155855750

image-20250816155907924

image-20250816160015121

安全问题与网安风险#

c34059f5e6f2bde1413ae4cd30d6c204

  1. 未使用密钥加密(或签名验证失效)
    只有JWT编码,在JWT官网修改用户参数信息后重新编码,由于缺少签名验证,服务器可能误判为有效令牌。

    • 正常情况下,服务器会报错“令牌无效”(因签名不匹配)。

    c19c77326492e7d94028f7ee7115f43f

    91e90751080d942d50e8f5a528675d6a

  2. 密钥泄露风险
    若攻击者获取密钥,可修改Payload后用密钥重新签名,生成“合法”令牌。

    • 示例:输入密钥,修改用户信息后重新生成令牌,可成功登录。

    image-20250816161815971

    image-20250816161909537

    image-20250816161940554

更多网安风险补充:

  1. 密钥爆破
    若密钥强度低(如短字符串、常见密码),攻击者可通过暴力破解工具尝试猜测密钥,进而伪造令牌。
    1c0a0fc98afc9ee3e75804a7d518f358
    (防御:使用长随机字符串作为密钥,如32位以上随机字符,避免硬编码在代码中,通过环境变量或加密配置文件存储)

  2. 未验证签名
    部分开发可能因疏忽跳过签名验证步骤(如直接解析Payload而不验证签名),导致攻击者可随意篡改令牌内容。
    81b7c7975e16a68f812d650822ccd40b
    (防御:严格执行签名验证流程,任何情况下不跳过验证)

  3. 算法协商漏洞
    若服务器支持动态选择签名算法(如允许客户端指定alg: none),攻击者可伪造无签名的令牌,服务器可能接受为有效。
    (防御:固定签名算法,不允许客户端指定,如强制使用HS256或RS256)

  4. 过期时间设置不当
    若JWT过期时间过长(如永久有效),令牌被盗后攻击者可长期滥用;若过短,会频繁要求用户重新登录,影响体验。
    (防御:根据业务场景设置合理过期时间,如1小时,同时实现令牌刷新机制)

可参考:https://zhuanlan.zhihu.com/p/93129166

SpringBoot-打包部署-JAR&WAR#

参考:https://mp.weixin.qq.com/s/HyqVt7EMFcuKXfiejtfleg
SpringBoot项目打包在linux服务器中运行:

JAR类型项目#

jar类型项目使用SpringBoot打包插件打包时,会在打成的jar中内置tomcat的jar
所以使用jdk直接运行jar即可,jar项目中功能将代码放到其内置的tomcat中运行。

  • Jar打包步骤

    报错解决:https://blog.csdn.net/wobenqingfeng/article/details/129914639
    https://blog.csdn.net/Mrzhuangr/article/details/124731024

    image-20250816165245728

    <skip>true</skip>修改为<skip>false</skip> 即可(skip设为false表示启用打包插件)

    • <configuration>: Maven 插件配置的根元素,包含具体配置内容。

    • <mainClass>: 指定 Java 应用程序的主类(入口点),此处为com.example.testjwt.TestJwtApplication。

    • <skip>: 控制插件是否跳过执行,true为跳过,false为执行(打包时需设为false)。

      image-20250816165424080

    1. 执行maven-clean-package命令(清理并打包,生成jar文件)。
    2. 运行命令java -jar xxxxxx.jar(通过JDK直接启动内置Tomcat,无需额外部署容器)。

image-20250816165013486

会报错:

image-20250816165118228

不会报错(可以发现多了一个文件):

image-20250816165631351

image-20250816165812447

image-20250816165839909

WAR类型项目#

在打包时需要将内置的tomcat插件排除,配置servlet的依赖和修改pom.xml,
然后将war文件放到tomcat安装目录webapps下,启动运行tomcat自动解析即可。

  1. pom.xml配置
    修改打包类型为war:<packaging>war</packaging>

    image-20250816170155383

  2. 启动类配置

@SpringBootApplication
// @SpringBootApplication 注解标识Spring Boot应用主类,自动扫描同包/子包组件
public class TestJwtApplication extends SpringBootServletInitializer { // 继承SpringBootServletInitializer以支持WAR部署
public static void main(String[] args) {
// 启动Spring Boot应用(JAR部署时使用)
SpringApplication.run(TestJwtApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
// 配置应用构建器(WAR部署时Tomcat会调用此方法初始化应用)
return builder.sources(TestJwtApplication.class);
}
}

image-20250816170328339

  1. 执行maven-clean-package命令(生成war文件)。

    image-20250816170543820

  2. 将war文件放置到tomcat的D:\....\apache-tomcat-9.0.27\webapps 文件夹后启动

    image-20250816170735983

    运行该文件夹下的tomcat的startup.bat启动程序(Windows)或startup.sh(Linux)

    image-20250816170933661

    遇到环境变量问题参考https://zhidao.baidu.com/question/1682233609979465627.html

    image-20250816172637281

    image-20250816172556086

    image-20250816172722761

打包部署的网安补充:

  • JAR/WAR文件安全:打包时需排除敏感文件(如配置文件中的密钥、数据库密码),避免随包泄露;可通过Maven插件过滤敏感文件。
  • 内置Tomcat漏洞:JAR包内置的Tomcat若版本过低,可能存在已知漏洞(如远程代码执行),需定期升级SpringBoot版本以更新内置组件。
  • 部署权限控制:服务器上的JAR/WAR文件及运行目录应限制权限(如仅管理员可读写),避免被恶意篡改;运行用户应使用低权限账号(非root),降低被攻击后的影响范围。

JAVAEE源码架构安全#

无源码下载泄漏风险#

  • 无下载路径,运行的程序是一个压缩包,不能通过访问下载路径去下载源码(相比PHP等脚本语言,Java编译后的class文件不易直接获取源码,但仍需防范压缩包泄露)

image-20250816173219460

image-20250816173406030

源码泄漏也需反编译#

  • 如果得到打包好的源码,将打包的源码进行解压(攻击者若获取JAR/WAR包,可通过反编译工具还原源码)

image-20250816173535950

直接使用idea打开文件夹,即可实现自动反编译,看到源码(反编译后的代码可能存在变量名混淆,但核心逻辑可被还原,需通过代码混淆工具增强保护)

image-20250816173630493

image-20250816173740620

难点总结#

  1. JWT密钥管理

    • 难点:密钥需保证高强度且不泄露,同时在分布式系统中安全同步(如多服务器共享密钥)。
    • 解决:使用环境变量或加密配置中心存储密钥,避免硬编码;非对称加密(如RS256)可避免密钥共享问题。
  2. JWT过期与刷新机制

    • 难点:平衡安全性(短有效期)与用户体验(减少登录频率)。
    • 解决:实现“访问令牌+刷新令牌”机制,访问令牌短期有效(如1小时),刷新令牌长期有效(如7天),过期后用刷新令牌获取新访问令牌。
  3. JWT签名验证的严格性

    • 难点:开发中易因疏忽跳过签名验证或使用不安全的算法(如none)。
    • 解决:封装JWT工具类,强制验证签名和过期时间,固定签名算法,禁止动态协商。
  4. JAR与WAR部署的选择与配置

    • 难点:内置Tomcat与外部Tomcat的配置差异(如端口、上下文路径),以及依赖冲突问题。
    • 解决:JAR适合快速部署和微服务场景,需注意内置组件版本;WAR适合传统容器部署,需正确排除内置Tomcat并匹配外部容器版本。
  5. 源码保护

    • 难点:JAR/WAR包易被反编译,导致核心逻辑或敏感信息泄露。
    • 解决:使用代码混淆工具(如ProGuard),移除调试信息,敏感配置不随包存储,通过环境变量注入。
第40天:安全开发-JavaEE应用
https://konwait12.github.io/my-kon-blog/posts/040安全开发/
作者
k-on!--wait
发布于
2025-08-16
许可协议
CC BY-NC-SA 4.0