java常见bugs与安全漏洞
欢迎你来读这篇博客,这篇博客主要是关于java常见bug与安全漏洞及避免方法的介绍
。
其中包括了关于我的见解和收集的知识分享。
序言
Bug
NPE 场景
- 调用了空对象的实例方法
- 访问空对象的属性
- 数组为空的时候,取长度
- 不能抛出空异常对象
- 方法的返回值是null,调用方法直接使用
- 自动拆箱导致NPE
- 空包装对象赋值给基本数据类型时
- 方法传参时
- 空对象进行比较大小
避免方法
- 使用之前初始化
- 避免返回null
- 外部传值,一定要及时判断
- 基本数据类型优于包装类型,优先使用基本数据类型
- 不确定的包装类型,先校验后使用
- 对于Null值的包装类型,赋值为0
字符串、数组、集合场景
- null字符串调用equals方法
- 对象数组new出来,但元素没有初始化
- list的addAll方法,传递null
Optional
- 不能作为类字段使用,没有实现序列化接口
- 容器类,代表存在和不存在
- orElse(new Object()) 存在返回,空提供默认值
- orElseGet() 存在返回,空由函数去产生 // 配合单例
- orElseThrow 存在返回,空抛出异常
- ifPresent 存在处理
- map 执行操作后返回一个optional对象
- 解决了什么问题
- 使代码更加优雅简洁
- 减少npe
- 解决不了什么问题
- 不具备传导性
- 何时使用
- Optional的预期用途主要是作为返回类型。
- 何时不要使用
- 不要将其用作类中的字段,因为它不可序列化。如果确实需要序列化包含Optional值的对象,则Jackson库提供了将Optionals视为普通对象的支持。这意味着Jackson将空对象视为空,将具有值的对象视为包含该值的字段。
- 不要将其用作构造函数和方法的参数,因为这会导致不必要的复杂代码。
1 |
|
Optional 源码与注释
1 |
|
正确处理异常
- 使用异常,而不是返回码(或类似),因为异常会更加的详细
- 主动捕获检查性异常,并对异常信息进行反馈(日志或标记)
- 保持代码整洁,一个方法中不要有多个 try catch 或者嵌套的 try catch
- 捕获更加具体的异常,而不是通用的 Exception
- 合理的设计自定义的异常类
常见异常
- 并发修改:可迭代对象在遍历的同时做修改,则会报并发修改异常
- 类型转换:类型转换不符合 Java 的继承关系,则会报类型转换异常
- 枚举查找:枚举在查找时,如果枚举值不存在,不会返回空,而是直接抛出异常
资源管理
try-with-resources
java计算、接口、集合
BigDecimal精度问题
- 初始化设置精度需要匹配
- bigDecimal.setScale(2) 可大不能小
- bigDecimal.setScale(2,BigDecimal.ROUND_HALF_UP)
- 除法结果需要精度
- 数值比较需要精度匹配
日期
SimpleDataFormat
- 可以解析大于或等于它定义的时间精度,但不能解析小于它定义的精度
- 线程不安全,多线程会抛出异常
- 原因:内部有一个calendar
- 解决方法:定义为局部变量、使用ThreadLoacl、Synchronize
迭代问题
- for-each优于for
集合判等问题
- equals和hashcode与其可能带来的潜在问题
- 类实现了 compareTo 方法,就需要实现 equals 方法
- compareTo 与 equals 的实现过程需要同步
Lombok
- @EqualsAndHashCode(callSuper=true)
- 命名问题
抽象类和接口
抽象类、接口的含义和特性
- 抽象类是子类的通用特性,包含了属性和行为;接口是定义行为,并不关心谁去实现
- 抽象类是对类本质的抽象,表达的是 is a 的关系;接口是对行为的抽象,表达的是like a 的关系
抽象类、接口的相同点
- 接口中的方法(java8 改变了这一语法)和抽象类中的抽象方法都不能有方法体,并且必须在子类中实现
- 都可以被继承,但是不能被实例化
抽象类、接口的不同点
- 使用时语法不同,抽象类使用 extends,接口则使用implements
- 接口中只能定义常量,所以,不能表达对象状态,而抽象类可以
- 接口中的方法必须是 public类型的,而抽象类则没有限制
- 类可以同时实现多个接口(间接解决了 Java 不支持多继承的we难题),但是只能继承一个抽象类
默认方法与静态方法
lambda与函数式接口
Stream和lambda真的高效吗
迭代过多可能会导致计算低效
序列化
- 父类不可序列化,子类实现了序列化接口也不行
- 需要提供父类的默认无参构造器
- 类中存在引用对象
- 所有属性都是可序列化的才可以序列化
- 同一个对象多次序列化,直接有更新
- 不会重复序列化,会影响结果
泛型、反射、编译优化
泛型
玩转泛型的前提是理解泛型擦除。
泛型的特性
- 先检查再编译
- 泛型不支持继承
- 泛型的类型变量不能是基本类型
- 泛型的类型参数只能是类类型,不能是简单类型
使用原始类型,可能会带来灾难性后果 不声明泛型类型
。主要是为了兼容旧代码。
反射
- 应用场景
- 开发通用框架
- 动态代理
- 注解
- 可扩展性功能
- 缺点
- 性能开销
- 破坏封装性
- 内部曝光
什么情况下反射获取不到Method
- 当方法是基本类型时,反射获取 Method 的参数类型也必须一致
- 如果调用的方法属于当前对象的父类,那么 getDetlaredMethod 获取不到Method
字符串
可以看我的Java日志
和Java·奇技淫巧
这两篇文章,有关于字符串拼接的讨论。
深拷贝与浅拷贝
Object类的clone()是浅拷贝
线程安全
synchronized
- 不会被继承,子类需要重新指定
- 标记位置:方法声明、方法体
- JDK实现:对象头标记,字节码标记 monitor
- JDK优化:偏向锁-轻量级锁-重量级锁
多线程原子更新变量值
- countDownLatch
- Atomic系列
阻塞队列
阻塞队列:支持两个附加操作的队列。队列空则等待、队列满则等待。
方法 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入 | add | offer | put | offer(time) |
移除 | remove | poll | take | poll(time) |
检查 | element | peek | - | - |
copy-on-write容器
主要目的是解决并发读写List异常
优点:并发读不需要加锁,提高进程的并发度
缺点:内存占用问题,一致性问题
应用场景:读多写少,如黑名单,白名单。
使用的时候,需要注意减少扩容开销,使用批量添加
线程池
TODO:自定义线程池,相关文章太多了,不再这里写了,有时间了,补充。
ThreadLocal
threadlocal并不是来解决并发或者共享问题的。
使用问题:
- 不支持继承
- 如果不及时清理现场,会造成数据混乱。
Spring 常见的坑
Spring Bean 默认名称生成策略导致空指针
Spring 对 Class前两个字母都是大写的,进行了特殊处理,把大写变成小写。但是默认策略是第一个字母会变成小写。
如果类名是QWERService。GetBean(qWERService) 就找不到。
解决方案:
- 使用类型获取
- getBean传正确
- 类名规避这种情况
- 根据类型获取Bean getBean(QWERService.class)
使用@Autowired依然空指针
- 属性对象注入了,但是对象没被注册成bean
- 虽然标记成SpringBean,属性也注入了,但是使用new去获取类对象。
- 正确做法应该是,Bean的整个生命周期都应该被Spring容器管理
- Spring的包扫描机制,没扫描到
不使用自动注入,如何获取上下文
- ApplicationContextInitializer
- ApplicationListener
- SpringApplication.run()的返回值
什么场景需要使用获取并使用上下文?
多线程下SpringBean的数据不符合预期怎么办
- 默认单例Bean
- 如果有全局属性,造成线程间通信,如果不做线程安全,就会有问题。
- 优势
- 减少新生成实例的小号
- 减少JVM垃圾回收
- 快速获取Bean
- 劣势
- 线程不安全
- 默认单例的理由
- 少创建实例
- 垃圾回收便捷
- 使用缓存快速获取
- 优势
- 如果有全局属性,造成线程间通信,如果不做线程安全,就会有问题。
- 原型
报错:存在多个Bean异常
- @Autowired:属于 Spring框架,默认使用类型(byType)进行注入
- @Qualifier:结合 @Autowired 一起使用,自动注入策略由 byType 变成 byName
- @Resource:JavaEE 自带的注解,默认按 byName 自动注入。JDK9-11某个版本已经移除。目前遵循JakartaEE标准,而非JavaEE标准。
- @Primary:存在多个相同类型的 Bean m @Primary 用于定义首选项
Bean注入常见异常
- 只定义了接口,没有具体实现
- 解决方法 require 参数设置为false
- 定义了接口的多个实现类,只使用@Autowire注入
循环依赖怎么办
解决方式,三级缓存。只能解决单例模式下的循环依赖。
- field的方式
- set方式
循环依赖问题最终解决方案,还是要通过避免循环依赖进行解决。出现循环依赖是设计的问题。
循环依赖通常意味着类之间的耦合过强,可能需要重新审视和调整设计。例如:
- 单一职责原则:确保每个类只负责一个职责,避免一个类同时依赖多个功能,这样可以降低类之间的相互依赖。
- 依赖反转:如果类 A 依赖类 B,而类 B 又依赖类 A,考虑引入接口或抽象类,反转依赖方向。通过接口使类之间的依赖关系更加灵活。
- 抽象工厂模式:引入工厂类来管理对象的创建,而不是直接在类之间相互依赖。这样可以让依赖关系更加清晰。
- 事件驱动:如果两个组件之间的依赖是为了处理某些事件或操作,可以考虑使用事件驱动的机制,解耦它们之间的关系。
如何利用Bean生命周期
@Transactional
- 捕获异常,标记回滚(编程式事务)
- 捕获异常,抛出Check异常,不能回滚
- 捕获异常,抛出UnCheck异常,可以回滚
- 捕获特定异常,根据异常,可以选择性回滚
- 一个没标注事务的方法,调用一个标注事务的方法,事务失效
Spring MVC 的坑
自定义返回
- 使用 ResponseEntity 类:标识整个 HTTP 响应(状态码、头部信息、响应体
- 异常类或 Controller 方法上标识 @ResponseStatus 注解
- 使用 @ControllerAdvice(@RestControllerAdvice)和 @ExceptionHandler 注解
时间序列化和反序列化的问题
- @DateTimeFormat
- FE2BE
- Get 可以正常,Post不行
- @JsonFormat
- 自定义实现Json序列化格式转换
日志放在拦截器还是过滤器
- Filter
- Interceptor
读取Request输入流,请求数据就不见了
SpringBoot 中的坑
配置出错
- 配置文件加载顺序
- 配置文件优先级
- 推荐使用同一种格式yml
定时任务
- @EnableScheduling
- @Scheduled
- fixedDelay
- fixedRate
- initialDelay
- cron
- spring.task.scheduling
- ScheduleConfig
线程池
- @EnableAsync
- @Async
- 有没有返回值
异步任务如果抛出异常,spring框架只打印了一行日志,并没有做处理。开发中需要捕获异常,并处理
异步任务如果超时了。一直阻塞不太好。选择超时时间,超时不做处理。
自定义线程池,配置自定义异常处理。
Jackson
- ObjectMapper线程安全
- 尽量注入使用,尽可能重用。
- 注解
- JsonIgnore
- JsonProperty
- JsonFormat
- JsonInclude
- JsonIgnoreProperties
- JsonSerialize
Security
xss
主要思路
- 严格校验长度
- 严格根据需求校验DTO
- 长文本必须转义
1 |
|
SQL Inject
目前此漏洞很少了,如果代码中有涉及到硬编码sql的情况,使用#{},而非${}。
防盗链
目前云厂商的OSS基本上都送防盗链防护或者卖这个服务。接入就行了,便宜又方便。
底层实现原理:根据黑白名单,判断HTTP请求头中的Referer字段。
CSRF
- Spring Security CSRF Token
幂等
- Token令牌
- 获取token,判断是否缓存中有,没有直接报错,有则执行,执行完业务逻辑后删除。
- MVCC
- 去重表
- 悲观锁
防止伪造Token请求
核心接口做多因子认证
- 短信验证码
- 人脸识别
防止机器人
- 图形验证码,限流,黑白名单
忘记密码漏洞
- 多因子验证
- 多因子验证码接口调用前,图形验证码拦截,防止机器模拟
- 重试次数限制
- 防止DDos
- 黑白名单
隐藏域漏洞
- 值传递不要用隐藏域
任意文件上传漏洞
- 文件格式限制
- 文件格式校验
- 前端校验扩展名
- 服务器校验扩展名
- content-type校验
- 文件上传目录限制,权限限制
- 文件重命名
其他漏洞
- 直接异常信息,会给攻击者以提示。要做隔离
- 上线去除所有注释
网站安全漏洞扫描
信息加密与密钥管理
接口幂等
通过token机制进行幂等和防重。
参考资料
启示录
富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。