ysi6701/SpringBoot-Security-Audit-Playbook
GitHub: ysi6701/SpringBoot-Security-Audit-Playbook
Spring Boot安全审计实战指南
Stars: 2 | Forks: 0
# 拿到一个 SpringBoot 项目,我会怎么做安全审计
## 1. 背景与说明
在学习 Java / SpringBoot 代码审计的过程中,我尝试从简单到复杂,对多个开源项目进行了逐步审计练习,并整理成了一系列审计报告。如果你感兴趣,也可以在我的主页中查看相关内容。
这些项目覆盖了不同的业务复杂度与安全场景,例如:
- **简单 Java Swing 桌面系统**:例如图书管理系统
- **基于 SpringBoot 的 Web 项目**:例如常见的博客系统
- **更复杂的 O2O 业务系统**:例如外卖 / 电商类项目
在这些项目中,OWASP Top 10 中的一些常见漏洞(如`A01:broken access control`;`A03:Security Misconfiguration`;`A04:Cryptographic Failures`等)往往都会出现。
但相比“知道这些漏洞”,更关键的问题是:
- **如何在拿到源码后,有条理地去发现它们?**
- **这些漏洞在 Java / SpringBoot 项目之中是怎样出现的?**
因此,这篇文章将围绕一个核心主题展开:
📌 本文适合:
- 刚接触代码审计,不知道从哪里下手的初学者
- 具备一定开发经验,希望从安全角度重新理解项目的开发者
⭐ 你需要的前置知识:
在阅读本文之前,不需要具备非常深入的安全或开发经验,但具备以下基础会更容易理解:
- **基本的 Web 漏洞知识**
例如:XSS、CSRF、文件上传漏洞、SQL 注入(SQLi)、SSRF 等
- **基本的 Java 语法**
能够看懂类、方法、参数传递,以及简单的业务逻辑
- **基本的 SpringBoot 结构**
了解 controller / service / mapper(dao) 的基本分层
- **基础的后端安全常识**
例如:认证与授权的区别、敏感数据不应明文存储、不要信任前端输入等
- **基本的数据库访问方式(MyBatis / JDBC)**
- 了解 SQL 是如何被构造和执行的
- 知道预编译(PreparedStatement)与字符串拼接的区别
📌 如果以上内容有部分不熟悉,也不影响阅读,可以在遇到具体问题时再补充学习。
👋 本文更偏“带你走一遍审计流程”,而不是对每一个漏洞进行深入讲解。
## 2. 整体审计流程概览
🔎 现在,一个项目源码摆在我们面前,我们应该从哪里开始?
❓️ 是去找一个 XSS?一个 CSRF?还是 SQL 注入?然后想办法“打穿”它?
但问题在于:如果只是随机去找漏洞,这个过程往往依赖经验甚至运气,也很难复现和总结。
❓️ 那么,有没有一种方式,可以让这个过程更加**稳定、可重复**?
*换句话说,我们是否可以通过一套合理的流程,尽可能系统性地发现代码中不合理的设计,而不是因为路径不同而产生遗漏?*
基于我个人的一些审计实践,我通常会按照如下步骤展开分析:
1. 从项目结构入手,快速了解系统的分层结构与业务模块划分
2. 查看配置类,分析全局配置与安全基线,判断系统是否具备基本的安全防护能力
3. 梳理认证与权限机制,明确用户身份是如何建立与校验的
4. 查看数据库与数据存储方式,关注敏感数据与潜在注入风险
5. 检查工具类实现,提前识别系统中可能存在的“漏洞能力”(如文件上传、XSS、SSRF 等)
6. 重点审计 Controller 层业务逻辑,结合前面发现的问题,分析接口是否存在可利用的安全风险
📌 值得注意的是:
- 在实际分析过程中,并发问题、日志记录、敏感信息泄露等,并不会单独作为一个阶段,而是会在审计具体业务接口时一并关注
- 关于第 5 步和第 6 步,不同的人可能会有不同的顺序偏好
例如:
- 可以先从 Controller 入手,明确接口调用了哪些工具类,再回头分析这些工具类是否存在安全问题(更偏“审计视角”)
- 也可以先分析工具类,提前掌握系统具备的“攻击能力”,再在 Controller 中寻找触发点,从而构造攻击链(更偏“攻击视角”)
我认为这两种方式都是合理的,可以根据个人习惯选择。
**⭐ 在接下来的内容中,这些步骤将被逐一展开,并结合具体示例进行说明。**
需要说明的是,本文重点在于**审计思路的梳理**,而非具体工具的使用。因此,你可以:
- 通过手工方式进行代码审计
- 使用静态分析工具(如 Semgrep)
- 或结合动态分析工具、甚至 AI 辅助工具
来完成整个审计过程。
## 3. 核心审计流程
### 3.1 从项目结构入手建立整体认知(项目入口)
——通俗来说,就是先看一眼项目的整体目录结构,弄清楚每一部分大致是做什么的,以及后续我们应该重点关注哪些位置。
那么,一个典型的 Java / SpringBoot 项目大概长什么样子?这里我通过两个初学者项目来简单说明。
#### 3.1.1 BigEvent:一个较为常见的 Java / SpringBoot 教学项目
下面是它的整体结构。
BigEvent-main # 项目根目录(前后端分离项目)
│
├── big-event-backend # 后端项目(Spring Boot)
│ │
│ ├── com.itheima.bigevent # Java 主包(所有代码入口)
│ │ │
│ │ ├── controller # 控制层:接收前端请求,返回响应(接口入口)
│ │ ├── service # 业务层:处理业务逻辑(核心处理)
│ │ ├── mapper # 数据访问层:操作数据库(MyBatis接口)
│ │ ├── pojo # 实体类:封装数据(如User、Article等)
│ │ │
│ │ ├── config # 配置类:Spring配置(如拦截器注册、跨域等)
│ │ ├── interceptor # 拦截器:登录校验、权限控制等
│ │ ├── exception # 异常处理:统一异常返回(全局异常处理)
│ │ ├── validation # 参数校验:表单/接口参数校验规则
│ │ ├── anno # 自定义注解:配合校验或权限使用
│ │ ├── utils # 工具类:通用工具(JWT、日期、加密等)
│ │ │
│ │ └── BigEventApplication.java # 启动类(Spring Boot 入口)
│ │
│ └── resources # ⭐ 资源目录
│ │
│ ├── application.yml # 核心配置文件(端口、数据库、JWT等)
│ ├── application-dev.yml # 开发环境配置(可选)
│ ├── application-prod.yml # 生产环境配置(可选)
│
├── big-event-frontend # 前端项目(Vue + Vite)
│ │
│ ├── src # ⭐ 前端核心源码目录
│ │ │
│ │ ├── views # 页面组件(用户看到的页面)
│ │ ├── api # 接口封装(axios请求后端)
│ │ ├── router # 路由管理(页面跳转控制)
│ │ ├── stores # 状态管理(Pinia/Vuex,全局数据)
│ │ ├── utils # 工具函数(格式化、token处理等)
│ │ ├── assets # 静态资源(图片、样式等)
│ │ │
│ │ ├── App.vue # 根组件(所有页面的入口组件)
│ │ └── main.js # 项目入口(创建Vue实例)
│ │
│ ├── vite.config.js # ⭐ Vite配置文件(开发服务器+代理)
│ │ # 👉 解决前后端跨域(最关键配置)
│ │
│ ├── package.json # 项目依赖配置(npm包管理)
│ ├── package-lock.json # 依赖锁定文件
│ ├── index.html # 页面入口HTML(挂载Vue)
│ └── node_modules/ # npm依赖(自动生成)
│
**① 分析项目结构**
从结构上可以很明显看出,这是一个基于 SpringBoot + Vue 的**前后端分离项目**。
➡️ 看到这一点,其实就已经可以提高警惕了:
前后端分离架构中,常见的安全问题包括:
- 未授权访问(接口权限控制不严)
- Token 泄露或伪造
- XSS(前端渲染不当导致)
- CSRF(在配置不当的情况下)
这也意味着,在后续的审计过程中,我们需要重点关注这些问题是否被妥善处理。
**② config**
`/big-event-backend/com.itheima.bigevent/config`
这个包中主要存放后端的各类配置类,例如:
- Web 相关配置(路径映射、拦截器等)
- 安全相关配置(认证、权限、过滤器等)
- 跨域(CORS)配置
- 请求处理规则等
从代码审计的角度来看,这一部分非常重要,因为它直接决定了:
👉 **哪些请求可以进入系统,以及这些请求在进入业务逻辑之前会经过哪些安全校验。**
换句话说,这一层是在“接口被调用之前”的第一道控制线:
- 是否允许跨域访问
- 是否需要认证
- 是否存在拦截器进行校验
- 是否关闭了某些安全机制(如 CSRF)
因此,在审计时,可以优先通过配置类快速判断:
这里需要简单关注一下项目的安全控制方式:
是通过实现 `WebMvcConfigurer` 等方式进行自定义拦截,还是基于 Spring Security 来实现统一的安全控制。
一般来说,如果是前者,更容易因为拦截不全或逻辑分散而出现问题;如果是后者,则需要关注配置是否合理,例如是否存在不必要的放行(`permitAll`)等。
这一部分在后续会结合具体代码进行详细分析。
**③ mapper**
`/big-event-backend/com.itheima.bigevent/mapper`
这一层通常用于与数据库进行直接交互。在一些项目中,也可能被进一步细分,放在 `dao` 包之下。
从审计的角度来看,这一层主要关注数据是如何被查询和拼接的。
例如:
- 对于基于 MyBatis 实现的项目,需要重点关注是否存在不安全的 `${}` 使用,这往往可能导致 SQL 注入问题
- 对于使用 MyBatis-Plus 的项目,虽然底层仍通过 Mapper 层访问数据库,但部分查询条件可能在 service 层构建(如 Wrapper)。因此,在审计时需要结合业务代码一起分析
相比之下,MyBatis-Plus 出现注入风险的概率相对较低,但并非不存在。例如:
- Wrapper 条件拼接不当
- 与 MyBatis 混用时(在一些大型项目之中是常见的),手写 SQL 仍然存在 `${}` 风险
- 动态条件可能带来的逻辑绕过问题
- 更需要警惕的是一些老项目中直接使用 JDBC 的情况。如果开发者安全意识不足,容易出现“伪预编译”(字符串拼接 SQL)的情况,此时需要逐条检查 SQL 语句的构造方式
**④ controller / service(接口与业务逻辑)**
controller 层是系统的接口入口,而 service 层则承载具体的业务逻辑实现。两者通常需要结合起来一起分析。
从流程上看:
- controller 负责接收请求、参数处理与简单校验
- service 负责具体的业务执行与数据操作
👉 因此,用户“能调用什么接口”,以及“调用后实际能做什么”,是由这两层共同决定的。
从审计的角度来看,这一部分是整个系统中最核心的分析区域,大部分漏洞都会在这里体现。
常见需要关注的问题包括:
- **水平越权**(如通过修改 ID 访问他人数据,对象级访问控制失效)
- **垂直越权**(普通用户调用管理员接口)
- **权限校验缺失或不一致**(controller 有校验,但 service 未校验,或反之)
- **过度信任前端传参**
同时,在分析时还需要结合具体业务逻辑,例如:
- 操作是否基于当前登录用户,而不是前端传入的用户信息
- 是否存在批量操作、数据遍历等风险点
- 关键业务(如下单、支付、修改权限)是否存在逻辑缺陷
这一部分是整个审计过程的核心。一般会先从 controller 层的接口入手,顺着调用链进入 service 层,分析具体业务逻辑的实现,从而判断是否存在可被利用的逻辑漏洞。
**⑤ utils**
这一层在一些项目中也可能被称为 `common`,或者被放`common`包下,主要用于存放通用工具类。例如:
- 文件上传工具类(如常见的小项目中封装的 OSS 上传逻辑)
- HTML 过滤工具类
- JWT 相关工具类
- ThreadLocalUtil 等上下文工具
从审计角度来看,这一层往往决定了系统是否具备某些“潜在漏洞能力”。例如:
- 文件上传是否缺乏校验,可能导致任意文件上传
- HTML 过滤是否不完整,可能导致 XSS
- Token 处理是否存在问题,可能影响认证安全
👉 因此,这一层不一定直接暴露漏洞,但会影响漏洞“是否能够成立”。
在实际分析中,需要结合 controller 提供的接口一起看:
- 这些工具类是否被用户可控的接口调用
- 是否可以通过这些实现不当的工具,构造攻击链(如提权、信息泄露等)
**⑥ 后端模块其他值得关注的地方**
除了上述核心模块外,后端中还有一些容易被忽略但同样重要的部分,也需要简单关注:
- `application.yml` 等配置文件
- 可能涉及数据库连接池、线程池、限流等配置
- 这些配置会直接影响系统在高并发或 DoS 场景下的可用性
- `interceptor`(拦截器)
- 常用于权限控制、登录校验等
- 需要关注拦截路径是否完整、是否存在绕过或遗漏
- `pojo`(或 `entity`)
- 定义了系统中存储和传输的数据结构
- 需要关注是否包含敏感信息(如密码、手机号等)
- 以及在存储或返回时是否进行了加密或脱敏处理
**⑦ 前端模块**
虽然常说“后端不应信任前端”,但前端的实现同样会影响漏洞是否能够成立,因此也需要简单关注。
主要可以看以下几点:
- 前端配置(如 `vite.config.js`)
- 是否存在富文本渲染(如 `v-html`、`innerHTML` 等)
- 是否有过滤机制(如 DOMPurify)
- Token 的存储方式(如放在 Cookie 中可能带来 CSRF 风险)
#### 3.1.2 Sky Take Out:一个较为常见的 Java / SpringBoot 外卖小程序教学项目
在 3.1.1 中,我们已经介绍了常见项目结构以及需要重点关注的模块。在这个项目中,整体结构并没有本质变化,但**分层更加清晰、模块拆分更加细致**。
sky-take-out-main
├── .vscode
├── demo
├── sky-common
│ └── src/main/java/com/sky
│ ├── constant // 常量类
│ ├── context // 上下文(如ThreadLocal等)
│ ├── enumeration // 枚举类
│ ├── exception // 自定义异常
│ ├── json // JSON处理
│ ├── properties // 配置属性类
│ ├── result // 返回结果封装
│ └── utils // 工具类
│
├── sky-pojo
│ └── src/main/java/com/sky
│ ├── dto // 数据传输对象(接收前端参数)
│ ├── entity // 实体类(数据库表映射)
│ └── vo // 视图对象(返回给前端)
│
└── sky-server
└── src/main/java/com/sky
├── annotation // 自定义注解
├── aspect // AOP切面
├── config // 配置类(Spring配置等)
├── controller // 控制层
├── handler // 处理器(如全局异常处理)
├── interceptor // 拦截器
├── mapper // MyBatis接口
├── service // 业务逻辑层
├── task // 定时任务
├── websocket // WebSocket相关
└── SkyApplication // 启动类
主要的不同点在于:
- 将通用能力单独拆分为 `sky-common` 模块
- 包含工具类、常量、上下文(ThreadLocal)、返回封装等
- ⭐ 审计时需要额外关注这些“公共能力”是否被不当使用,例如一些默认密码、通用逻辑可能会在这一层定义
- 将数据结构单独拆分为 `sky-pojo` 模块
- 明确区分 `dto`(输入)、`entity`(数据库)、`vo`(输出)
- ⭐ 更方便梳理数据流向,但也需要关注数据在不同层之间传递时,是否进行了必要的校验与过滤
- 核心业务集中在 `sky-server` 模块
- 包含 controller / service / mapper 等典型结构
- 同时引入了 AOP、拦截器、WebSocket、定时任务等机制
- ⭐ 审计范围更广,不仅需要关注接口本身,还需要注意切面、任务等“非直接入口”的执行逻辑
总体来说,相比 3.1.1,这类项目:
结构更加规范的项目,往往也意味着逻辑被拆分得更加分散。在审计时,需要花费更多精力跨模块跟踪调用链进行分析,否则容易遗漏细节,从而产生误判。
### 3.2 全局配置与安全基线(配置类)
### 3.3 认证与权限机制(登录接口 + 权限控制代码)
在分析认证与权限机制时,首先需要明确两个核心问题:
- 该机制能够提供哪些安全保障?
- 该机制在何种前提下才能实现这些安全保障?
反之,我们可以发现,出现漏洞的情况无非:
- 对安全机制功能的误解,将其用于无法提供保护的场景;
- 未正确遵循安全机制的使用条件,导致其安全保障失效。
本小节前半部分主要聚焦于认证凭证与会话实现层,即 Session、Cookie、JWT 等能够直接承载登录状态的具体机制;后半部分则主要聚焦于认证授权框架与权限模型层,即 OAuth2/OIDC、SSO、RBAC 等更偏向认证流程组织、信任边界划分与授权决策设计的内容。
#### 3.3.1 JWT
我们首先来分析JWT(JSON Web Token)的结构。JWT主要由三个部分组成,每部分使用 Base64Url 编码后以`.`分隔
..
- 头部 (header)用于描述令牌元信息,包括签名算法和令牌类型。典型示例如下:
{
"alg": "HS256",
"typ": "JWT"
}
- 载荷 (payload)包含实际要传递的信息,是 JWT 的核心部分,主要包括:
- 注册声明(Registered Claims):标准字段,如
- iss(Issuer)签发者
- sub(Subject)主题,通常为用户唯一标识
- aud(Audience)接收方
- exp(Expiration Time)过期时间
- nbf(Not Before)生效时间
- iat(Issued At)签发时间
- jti(JWT ID)唯一标识符
- 公共声明(Public Claims):应用可自定义字段,用于存放角色、权限、部门 ID 等信息。
- 私有声明(Private Claims):完全由应用定义的字段,用于携带业务特定信息,如用户偏好、单点登录标识等。
- 签名(signature)主要由一下公式来声明,从算法上来看,很明显,这个是为了防篡改,以及完整性的。
Signature = HMACSHA256( base64UrlEncode(Header) + "." + base64UrlEncode(Payload), secret )
签名部分是JWT做无状态认证的关键,服务器无需保存任何会话的信息,只需要把header+payload丢进签名算法里面,然后比对和签名是否一致就行为了。
我们依据这个结构来一次回答上面的问题:
- JWT能够提供哪些安全保障?
- 身份认证:通过载荷中的`sub`等身份标识符来确认请求者的身份
- 数据完整性校验:篡改载荷之中的字段,会导致后面的签名校验不通过
- 权限控制:But✳这个不是天然就能够做的,需要在公共声明部分定义比如role一类的权限信息,系统才能够依赖这个做资源授权控制。
- 无状态安全管理:即上面说的仅依赖 JWT 本身即可完成认证与授权,实现安全的分布式访问。
- JWT提供安全保障的前提是什么?
- ⭐ 其实,你也发现了,JWT token几乎所有的部分,都是由用户报文提供了,仅有secret(密钥)是秘密。那么很显然,JWT的安全性几乎完全依赖于secret。这要求:
- secret不适用弱密钥,不硬编码,最好还需要轮换
- ⭐ 另外,很显然JWT的header和payload仅仅进行了base64编码,并没有加密,因此,绝对不要再这两个部分放隐私数据,比如密码,以及其他的不应该泄露的数据。
- ⭐ 其实,你也发现了一点,在JWT验证的过程之中,我们只看了token本身,没有看任何其他的东西。这意味着,不管是谁拿着A同学的token,系统都会认为此人是合法的A,并为之提供A的服务。所以,聪明的你发现了,JWT其实不防中间人攻击。需要在HTTPS或者其他安全通道使用。
下面也提供了一段JWT的生成代码,可以结合代码看看原理。
@Service
public class JwtService {
// 建议把它放到配置文件里,存 Base64 编码后的强随机密钥
@Value("${jwt.secret}")
private String secret;
private SecretKey getKey() {
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
}
public String generateToken(String username, List roles) {
Instant now = Instant.now();
return Jwts.builder()
.subject(username)
.claim("roles", roles)
.issuedAt(Date.from(now))
.expiration(Date.from(now.plus(2, ChronoUnit.HOURS)))
.signWith(getKey())
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.verifyWith(getKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
public boolean isValid(String token, String username) {
Claims claims = parseToken(token);
return username.equals(claims.getSubject())
&& claims.getExpiration().after(new Date());
}
}
从原理的角度出发了之后,我们在再看实际中的问题案例
##### 第一关:只做了解码,没有验签
@Component
public class JwtUtil {
public String getUsername(String token) throws Exception {
String[] parts = token.split("\\.");
String payloadJson = new String(
Base64.getUrlDecoder().decode(parts[1]),
StandardCharsets.UTF_8
);
ObjectMapper mapper = new ObjectMapper();
JsonNode payload = mapper.readTree(payloadJson);
return payload.get("sub").asText();
}
public List getRoles(String token) throws Exception {
String[] parts = token.split("\\.");
String payloadJson = new String(
Base64.getUrlDecoder().decode(parts[1]),
StandardCharsets.UTF_8
);
ObjectMapper mapper = new ObjectMapper();
JsonNode payload = mapper.readTree(payloadJson);
List roles = new ArrayList<>();
payload.get("roles").forEach(node -> roles.add(node.asText()));
return roles;
}
}
@GetMapping("/admin/users")
public String adminApi(@RequestHeader("Authorization") String authHeader) throws Exception {
String token = authHeader.substring(7);
List roles = JwtUtil.getRoles(token);
if (roles.contains("ADMIN")) {
return "管理员接口数据";
}
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}
在这个例子里面,服务器拿到token,只对header和payload部分做了解码,拿出了里面的role,但是没有进行验签。在这种情况下,用户随意地篡改token之中地userid,role等参数,服务器都会为之提供服务。
Signature = HMACSHA256( base64UrlEncode(Header) + "." + base64UrlEncode(Payload), secret )
##### 第二关:虽然验签了,但没有校验`exp`/`iss`/`aud`
@Service
public class JwtValidationService {
@Value("${jwt.secret}")
private String secret;
private SecretKey getKey() {
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
}
public boolean isValid(String token) {
Claims claims = Jwts.parser()
.verifyWith(getKey())
.build()
.parseSignedClaims(token)
.getPayload();
// 只要 sub 不为空就算合法
return claims.getSubject() != null;
}
}
这将导致:
- 过期 token 可能仍被接受;
- 别的系统签发的 token 可能被误接受;
- 原本签发给系统 A 的 token,可能在系统 B 被错误使用。
改进地写法是,比如你可以手动校验一下
@Service
public class BetterJwtValidationService {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.issuer}")
private String expectedIssuer;
@Value("${jwt.audience}")
private String expectedAudience;
private SecretKey getKey() {
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
}
public boolean isValid(String token) {
Claims claims = Jwts.parser()
.verifyWith(getKey())
.build()
.parseSignedClaims(token)
.getPayload();
Date now = new Date();
if (claims.getExpiration() == null || claims.getExpiration().before(now)) {
return false;
}
if (!expectedIssuer.equals(claims.getIssuer())) {
return false;
}
Object aud = claims.get("aud");
if (aud == null || !aud.toString().contains(expectedAudience)) {
return false;
}
return claims.getSubject() != null;
}
}
或者像spring security之中的写法
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt())
.build();
}
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder decoder =
(NimbusJwtDecoder) JwtDecoders.fromIssuerLocation("https://idp.example.com/issuer");
OAuth2TokenValidator withIssuer = JwtValidators.createDefaultWithIssuer(
"https://idp.example.com/issuer"
);
OAuth2TokenValidator audienceValidator =
new JwtClaimValidator
- >("aud", aud ->
aud != null && aud.contains("order-service"));
decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator));
return decoder;
}
}
##### 第三关:把敏感数据塞到payload里面去了
public String generateToken(User user) {
return Jwts.builder()
.subject(user.getUsername())
.claim("phone", user.getPhone())
.claim("idCard", user.getIdCard())
.claim("passwordHash", user.getPassword()) // 严重错误
.claim("salary", user.getSalary())
.issuedAt(new Date())
.expiration(Date.from(Instant.now().plus(2, ChronoUnit.HOURS)))
.signWith(getKey())
.compact();
}
这个问题很显然,JWT的载荷之中时绝不应该放敏感数据的,因为这个地方本身没有加密。 HMACSHA256在这里只提供签名的作用。
##### 第四关:不当的密钥
按照我们上面所说的,secret时JWT安全的关键。secret是JWT安全的必要不充分条件。
因此不应该:
❌ 硬编码弱密钥
private static final String SECRET = "jwt-secret";
❌ 把普通字符串直接 getBytes()
private SecretKey getKey() {
return Keys.hmacShaKeyFor("mysecret123456".getBytes(StandardCharsets.UTF_8));
}
##### 第五关:超长的有效期
public String generateToken(String username, List
标签:ASN解析, JS文件枚举, SQL查询