java代码常见题目及优化建议
1、不充分的SQL参数验证(代码注入)详细形貌:攻击者可以在输入中注入恶意代码,没有对用户输入举行充分的验证,导致恶意输入可以绕过安全机制,从而执行未经授权的操作
举例阐明:
SELECT * FROM users WHERE username = '输入的用户名' AND password = '输入的暗码';
1、使用OR关键字
用户输入' OR '1' = '1', 可将原有SQL转换为:
SELECT * FROM users WHERE username = '' OR '1' = '1' AND password = ''; 查询将返回全部用户,攻击者可以绕过身份验证。
2、使用UNION关键字
用户输入' UNION SELECT email FROM users --
可将原有SQL转换为: SELECT * FROM users WHERE username = '' UNION SELECT email FROM users -- AND password = '';
查询将返回users表中的全部email字段,攻击者可以盗取用户的电子邮件地址
修改建议:
使用参数化查询、对用户输入举行验证和转义、限定数据库权限等
2、执行由前端提供的完整SQL
服务端执行由接口参数传递的完整sql
举例阐明:
public ResultData executeSql(String sql, String limit) {
if (ObjectUtils.isEmpty(limit)) {
limit = "limit 20";
}
List linkedHashMaps = sqlScriptMapper.executeSql(sql + " " + limit);
return ResultData.data(linkedHashMaps);
}
修改意见:
不允许在服务端执行前端完整sql,应在服务端预界说sql,前端只传递sql标识及参数,再由服务端执行,同时需要对参数举行注入风险查抄。
3、索引大概失效
在where中对字段举行函数操作、盘算操作会导致字段索引失效。
举例阐明:
SELECT MAX(CAST(SUBSTRING(sr.remark,13) AS INTEGER)) FROM flfc_supervision_record sr WHERE SUBSTRING(sr.remark,1,8) = #{date}
修改意见:
只管在SQL中不对字段时间函数、盘算操作,避免索引失效。
4、数据事务范围过大或缺失事务控制
在类上声明@Transaction,在查询方法上启用事务注解,对多表数据变更服务未启用事务。
举例阐明:
在类上声明事务注解
@Service
@Transactional
public class OperateTicketServeImpl implements IOperateTicketServer {}
修改建议:
1.禁止在声明类级的事务注解
2.不在查询类接口上声明事务注解
3.建议在多表数据变更服务上使用事务注解
4.设置合理的rollbackFor条件
5、未加密存储,明文暗码(用户暗码、数据库暗码、FTP暗码、应用秘钥等)
暗码明文存储,即将敏感数据(如暗码)直接以明文存储,或在代码中设置暗码明文为变量值或在@Value界说为默认值
举例阐明:
例如在User对象中,Password被不加处理的明文存储
java
复制代码
public class User {
private String username;
private String password; // 暗码以明文形式存储
// 构造函数
public User(String username, String password) {
this.username = username;
this.password = password; // 直接存储明文暗码
}
// 获取用户名
public String getUsername() {
return username;
}
// 设置用户名
public void setUsername(String username) {
this.username = username;
}
// 获取暗码(这是不安全的,因为返回的是明文暗码)
public String getPassword() {
return password;
}
// 设置暗码(直接设置明文暗码)
public void setPassword(String password) {
this.password = password;
}
}
修改建议:
为了改进安全性,应该采取以下步伐:
暗码哈希:存储暗码的哈希值而不是明文暗码。当验证暗码时,哈希用户提供的暗码并与存储的哈希值举行比较。
使用盐:为每个用户生成一个唯一的盐值,并将其与暗码联合,然后哈希这个组合。这可以防止使用预先盘算的哈希值举行攻击。
选择强哈希算法:使用像Bcrypt或Argon2如许的暗码哈希算法,它们专门计划用于暗码存储,而且比SHA-256等通用哈希算法更安全。
保护存储的暗码:确保只有授权的服务或人员可以访问存储暗码的数据库或系统。
禁止在代码中存储暗码、应用暗码等信息。
6、HTTP接口参数缺少验证
在RestController或Service中未对接口参数举行必要的可空、有效值范围等验证
举例阐明:
@PostMapping("/listConfig")
public R listConfig(@RequestBody SysConfig config) {
return configService.listConfig(config);
}
修改建议:
使用SpringValidation框架或自界说校验代码对入参举行必要的查抄,并向调用者返回合理的提示
7、服务端向调用方返回不必要的服务端信息
服务端向调用方返回了数据库信息(表名、字段名、数据错误形貌等)、异常堆栈、操作系统信息等。
举例阐明:
WrappedResult.failedWrappedResult(e.getMessage())
修改建议:
对服务端异常同一拦截,同一编码及形貌,不返回异常详细信息。
8、代码硬编码URL、IP等信息
在代码中以变量、常量或配置项默认值方式硬编码了URL、IP等信息,一方面目面貌易走漏各类环境的IP,另一方面代码在各现场容易出错,顺应性差。
举例阐明:
private static String dcloudUrl="http://172.16.221.133:1080/dcloudmodelservice";
修改建议:
接纳配置文件管理外部系统URL、IP等信息。
9、空指针异常
是java中常见的运行时异常,它发生在程序试图在需要对象的地方使用null时。重要有以下几种常见情形:
1.对象未初始化:在调用对象的方法或访问其属性之前,对象没有被正确初始化。
2.返回null的方法调用:如果一个方法返回null,而调用该方法的代码没有查抄null值就直接使用,就会抛出空指针异常。
3.数组元素未初始化:实验访问数组中未初始化的元素,即数组元素为null.
举例阐明:
变量空指针异常,该变量赋值为空,之后遍历该变量
List exsitsVersionRecordList == null;
for(VersionRecord versionRecord : versionRecordList) {
for(VersionRecord exsitsVersionRecord : exsitsVersionRecordList)
修改建议:
开发者应该在使用对象之前举行非空判断,或者确保对象在使用之前已经被初始化。此外,还可以使用Optional类来避免空指针异常,Optional类是一种可以为null的容器对象,如果值存在则可以直接获取,否则返回一个默认值。
10、线程、线程池不合理使用
线程的不合理使用包括:
1.创建过多线程:在这个例子中,我们创建了10个线程,但实际上大概并不需要这么多线程。过多的线程会斲丧系统资源,并大概导致性能降落。
2.缺乏同步控制:Task类中的run方法访问了共享资源(在这里是system.out),但没有举行任务同步控制。这大概导致多个线程同时访问和修改同一资源,从而产生不可推测的结果。
3.没有使用线程池:在这个例子中,我们直接创建了新的线程来执行任务。这大概导致线程创建和销毁的开销很大。在实际应用中,应该考虑使用线程池来管理线程,如允许以复用线程,减少创建和销毁的开销。
4.缺乏异常处理:在Task的run方法中,我们捕获了InterruptedException但只是简单的打印了堆栈跟踪。在实际应用中,应该更适当的处理这种异常,例如重新中断线程或举行其他恢复操作。
5.线程走漏:由于我们直接创建线程而没有适本地管理它们,如果应用程序长时间运行并持续创建新线程,最终大概会导致线程走漏,即系统资源耗尽。
举例阐明:
// 创建过多的线程
for (int i = 0; i < NUM_THREADS; i++) {
new Thread(new Task()).start();
}
// 创建方法级局线程池
private void test(){
ThreadPoolTaskExecutor taskExecutor = Utils.createThreadPoolExecutor();
...
}
修改建议:
1.使用线程池来管理线程,应使用可共享的全局线程池,应按需创建差别用途的线程,禁止使用局部线程池。
2.对共享资源的访问举行同步控制,例如使用synchronized关键字或java.util.concurrent包的锁。
3.适当处理异常,避免线程意外终止。
4.确保线程在完成任务后正确终止,避免线程走漏。
11、内存走漏
内存走漏通常发生在持续性的创建对象而忘记开释它们所占用的内存时,大概导致应用程序崩溃或性能降落。
举例阐明:
List leadedList = new ArrayList();
public static void main(String[] args){
//持续向列表中添加对象
while(判断条件){
Object obj = new Object();
leadedList.add(obj);
}
}
修改建议:
确保在不再需要对象时开释他们的引用。或者使用可以或许主动管理资源的框架和库。此外,使用内存分析工具,可以帮助你检测和定位内存走漏题目。
12、并发异常
controller、service等单例对象中使用可变类属性,会导致数据共享或状态管理等题目;未使用并发读写安全的类,如使用HashMap
举例阐明:
public class MyService{
private List sharedList = new ArrayList();
public void addToList(String item){
sharedList.add(item);
}
public List getList(){
return sharedlist:
}
}
@RestController
@RequestMapping(“/api”)
public class MyController{
@Autowired
private MyService myservice;
@PostMapping("/items")
public ResponseEntity addItem(@RequestBody String item){
myservice.addToList(item);
return ResponseEntity.ok("Item added");
}
@GetMapping("/items")
public ResponseEntity getItems(){
return ResponseEntity.ok(myService.getList());
}
}
在这个例子中,MyService 类有一个私有的 sharedList 属性,这是一个ArrayList,是一个可变对象。因为MyService是单例的,这意味着无论有多少请求同时到达,它们都会操作同一个sharedList实例。
这大概会导致以下题目:
1、数据共享:差别的请求大概会同时修改sharedList,导致数据混乱或不可推测的行为。
2、线程安全性:sharedList没有举行线程安全处理。如果在多个线程同时修改sharedList时,大概会导致ConcurrentModificationException异常或数据不一致。
3、状态管理:sharedList的状态会在整个应用的生命周期内持续存在,这大概会导致难以调试的题目,因为状态大概会在不期望的时候被改变。
修改建议:
在Spring单例bean中不应使用可变类属性,或类属性的变化对服务请求是无状态的。使用并发安全的类(java.util.concurrent),Map使用ConcurrentHashMap,List使用Concurrent.SynchronizdList(new ArrayList());
13、数据库连接未开释或连接未池化,或使用后未归还连接池,或还池连接未恢复原始状态
数据库连接如果不被适本地开释,大概会导致资源走漏,因为数据库连接通常是有限的,而且长时间占用连接大概会耗尽连接池。
数据库连接未使用连接池。从池中获取连接,改变了连接的状态(如关闭主动提交),归还连接池时未将连接状态重置(如未还原为主动提交)
举例阐明:
1、未关闭连接、未池化连接
public static void main(String[] args) {
Connection connection = null;
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 建立数据库连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");
// 执行数据库操作...
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
// 忘记开释数据库连接(导致资源走漏)
// 在实际应用中,大概由于异常、逻辑错误或其他原因忘记关闭连接
2、池化对象未归还连接池
private void connDemo(){
Connection conn=dataSource.getConnection();
//数据库的操作后,未执行conn.close()
}
3、池化连接还池前未重置状态
private void connDemo(){
Connection conn=dataSource.getConnection();
//数据库事务操作后,归还连接前未调用conn.setAutoCommit(true);
conn.setAutoCommit(false);
try{
//事务操作
conn.commit();
}catch(Exception e){
conn.rollback();
}
//连接池池
conn.close();
}
修改建议:
1.确保在finally块中正确关闭全部资源,纵然发生异常。
2.使用连接池,他们可以主动管理连接的创建、使用和开释。
3.从连接池获取的连接对象,在使用后必须关闭,否则不能归还连接池。
4.归还连接池的连接,必须重置改变的状态属性。
14、不合适的比较
不合适的比较操作通常会导致逻辑错误、程序崩溃或不可推测的行为。
举例阐明:
1.字符串比较用==导致与预期结果不一致;
2.Integer与long型equals比较永世为false;
3、Java中int类型的变量number和一个String类型的变量text比较是不可以的,会报错,应先举行类型转换。
int number = 10;
String text = "1000";
// 不合适的比较:整数和字符串之间的比较
if (number == text) {
System.out.println("Comparison succeeded!");
} else {
System.out.println("Comparison failed.");
}
修改建议:
我们使用Integer.parseInt(text)方法实验将字符串text转换为一个整数。如果转换乐成,我们就可以安全地举行比较。如果字符串不能转换为整数(例如,它包含非数字字符),Integer.parseInt会抛出一个NumberFormatException异常,我们需要捕获这个异常并适当处理
14、移除无用的System.out、printStackTrace等控制台输出
过多的System.out、e.printStackTrace输出大概会产生大量的日志噪音,乃至大概走漏敏感信息。因此,在发布生产版本时,通常需要将它们移除或禁用。
修改建议:
1.手动删除或注释:
最直接的方法是在源代码中手动找到并删除或注释掉全部不需要的System.out、e.printStackTrace语句。这种方法固然简单,但容易出错,尤其是当项目规模较大时。
2.使用日志框架:
使用像Log4j, SLF4J, Logback等日志框架,它们允许你通过配置文件机动地控制日志的输出级别和输出目标。例如,你可以将System.out输出更换为日志框架的输出,并在生产环境中将日志级别设置为ERROR或WARN,如许就可以忽略掉全部的INFO和DEBUG级别的日志。
3.使用条件编译:
你可以利用Java的条件编译特性来移除System.out、e.printStackTrace。例如,你可以界说一个常量DEBUG,并在需要输出日志的地方使用if (DEBUG)来包裹System.out、e.printStackTrace语句。在编译生产版本时,你可以通过界说-DDEBUG=false来禁用这些输出。
15、未使用的声明变量或未调用的方法,大段已注释的代码或类
移除未使用的声明变量或未调用的方法、类,有助于保持代码的清晰和整洁,减少不必要的混淆,提高代码的可读性和可维护性。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]