java编程规范每行代码窄字符,wiki/0xFE_编程规范.md at master · islibra/wiki · GitHub
0xFE_編程規范
使用UTF-8編碼
使用空格縮進
命名
清晰表達意圖, 少用縮寫(行業通用除外, 如: request=req, response=resp, message=msg), 不應使用特殊前綴或后綴
用復數形式代表集合
\w{2,64}, 除循環變量i, j, k, 異常e外
類型
命名風格
包
全小寫, 點號分割, 允許數字, 無下劃線
類, 接口, 枚舉, 注解
名詞/形容詞, 大駝峰, 縮寫也用大駝峰, 測試類加Test后綴
字段, 局部變量, 方法, 方法參數
介詞/動詞, 小駝峰, 測試方法可有下劃線_
靜態常量, 枚舉
全大寫, 下劃線分割, 常見的Logger, Lock可除外
泛型
單個大寫字母, 可接一個數字
異常
加后綴Exception
數據庫
全小寫下劃線
表名
全大寫下劃線
列名
全大寫下劃線
注釋
Javadoc用于public/protected
排版
文件非空非注釋不超過2000行, 每行120窄字符, 方法不超過50行
換行開始:
.
::
&
|
修飾符
public
abstract, static
final transient volatile synchronized
void
數字后綴大寫
long value = 1000000000L
數組聲明
String[] args = {"a", "b"}
條件和循環必須使用大括號, 左括號在行尾
變量
一個局部變量只表達一種含義, 避免前后不一致
浮點數不能使用==判等, 不能精確計算, 不能作為循環變量
BigDecimal income = new BigDecimal("1.03");
BigDecimal expense = new BigDecimal("0.42");
// 0.61
LOG.info("BigDecimal: " + income.subtract(expense));
// 0.61
LOG.info("1.03f - 0.42f: " + (1.03f - 0.42f));
方法
包含方法本身, 嵌套深度不超過4
參數不超過5個, 異常不超過5個
類,接口
接口中屬性缺省public static final, 方法缺省public abstract
異常
在finally中不要使用return, break, continue
日志
使用SLF4J+LogBack
private static final Logger LOG = LoggerFactory.getLogger(Xxx.class)
日志級別
條件或占位符
不要使用foreach remove, 可以使用iterator.remove()
安全編程
數據校驗
在信任邊界以內(如Web服務端)進行數據校驗
輸入校驗
輸出校驗
接收白名單: Pattern.matches("^[0-9a-zA-Z_]+$", "abc_@123")
拒絕黑名單, 白名單凈化(對所有非字母數字刪除/編碼/替換), 黑名單凈化(對某些特殊字符刪除/編碼/替換)
禁止使用assert校驗
防止命令注入
Runtime.exec()
java.lang.ProcessBuilder
防止SQL注入
參數化查詢PreparedStatement, {==參數下標從1開始==}: stmt.setString(1, userName);
存儲過程conn.prepareCall()也不能拼接SQL再執行
Hibernate 原生SQLsession.createSQLQuery()應使用參數化查詢, HQLsession.createQuery()應使用基于位置/名稱的參數化查詢
iBatis禁止使用$拼接SQL
白名單校驗(表名/字段名)
轉碼
文件路徑校驗前必須先進行標準化
等價路徑: 軟鏈接
目錄遍歷: 路徑跨越../
必須使用getCanonicalPath(), 其他方法getPath(), getParent(), getAbsolutePath()均不會歸一化
解壓
目錄遍歷
DoS
錯誤示例
public class IODemo {
private static final Logger log = Logger.getLogger(IODemo.class.getName());
public static void zipIO(String path) {
FileInputStream fin = null;
BufferedInputStream bin = null;
ZipInputStream zin = null;
FileOutputStream fout = null;
BufferedOutputStream bout = null;
try {
File zipFile = new File(path);
// 解壓到當前目錄
String parent = zipFile.getParent() + File.separator;
fin = new FileInputStream(zipFile);
bin = new BufferedInputStream(fin);
zin = new ZipInputStream(bin);
ZipEntry entry = null;
int count;
final int BUFFER_SIZE = 512;
byte data[] = new byte[BUFFER_SIZE];
// 對壓縮包中的每個文件
while ((entry = zin.getNextEntry()) != null) {
// toString()調用了getName()
log.info("Extracting: " + entry);
File unzipFile = new File(parent + entry.getName());
if (unzipFile.isDirectory()) {
// 目錄
unzipFile.mkdir();
} else {
final int FILE_MAXSIZE = 0x6400000; // 100MB
// 判斷文件大小, 可以被偽造
if (entry.getSize() == -1 || entry.getSize() > FILE_MAXSIZE) {
throw new IllegalArgumentException("File is too big.");
}
fout = new FileOutputStream(unzipFile);
bout = new BufferedOutputStream(fout, BUFFER_SIZE);
while ((count = zin.read(data, 0, BUFFER_SIZE)) != -1) {
bout.write(data, 0, count);
bout.flush();
}
}
zin.closeEntry();
}
} catch (IOException e) {
log.severe(e.getMessage());
} finally {
try {
bout.close();
fout.close();
zin.close();
bin.close();
fin.close();
} catch (IOException e) {
log.severe(e.getMessage());
}
}
}
public static void main(String[] args) {
zipIO("D:\\tmp\\io.zip");
}
}
推薦示例
public class IODemo {
private static final Logger log = Logger.getLogger(IODemo.class.getName());
public static void zipIO(String zipFilepath) {
FileInputStream fin = null;
BufferedInputStream bin = null;
ZipInputStream zin = null;
FileOutputStream fout = null;
BufferedOutputStream bout = null;
try {
File zipFile = new File(zipFilepath);
// 解壓到當前目錄
String parent = zipFile.getParent() + File.separator;
fin = new FileInputStream(zipFile);
bin = new BufferedInputStream(fin);
zin = new ZipInputStream(bin);
ZipEntry entry = null;
int count;
final int BUFFER_SIZE = 512;
byte data[] = new byte[BUFFER_SIZE];
// 總解壓文件數量
final int TOTAL_FILE_NUM = 1000;
// 總解壓文件大小, 100MB
final int TOTAL_FILE_MAXSIZE = 0x6400000;
int totalFileNum = 0;
int totalFileSize = 0;
while ((entry = zin.getNextEntry()) != null) {
// 安全編程1: 校驗解壓文件數量
if (totalFileNum > TOTAL_FILE_NUM) {
throw new IllegalArgumentException("Too many files.");
}
// toString()調用了getName()
log.info("Extracting: " + entry);
File unzipFile = new File(parent + entry.getName());
// 安全編程2: 校驗解壓文件路徑
String unzipFilepath = unzipFile.getCanonicalPath();
if (!unzipFilepath.startsWith(parent)) {
throw new IllegalArgumentException(
"File is outside extraction target directory");
}
if (unzipFile.isDirectory()) {
// 目錄
unzipFile.mkdirs();
} else {
File dir = new File(unzipFile.getParent());
if (!dir.exists()) {
dir.mkdirs();
}
fout = new FileOutputStream(unzipFile);
bout = new BufferedOutputStream(fout, BUFFER_SIZE);
while ((count = zin.read(data, 0, BUFFER_SIZE)) != -1) {
// 安全編程3: 校驗解壓文件總大小
if (totalFileSize > TOTAL_FILE_MAXSIZE) {
throw new IllegalArgumentException("File is too big.");
}
bout.write(data, 0, count);
bout.flush();
totalFileSize += count;
}
}
zin.closeEntry();
totalFileNum++;
}
} catch (IOException e) {
log.severe(e.getMessage());
} finally {
try {
if (bout != null) {
bout.close();
}
if (fout != null) {
fout.close();
}
if (zin != null) {
zin.close();
}
if (bin != null) {
bin.close();
}
if (fin != null) {
fin.close();
}
} catch (IOException e) {
log.severe(e.getMessage());
}
}
}
public static void main(String[] args) {
zipIO("D:\\tmp\\io.zip");
}
}
防止CRLF和敏感信息記錄日志
接收白名單
黑名單凈化: message = message.replace('\n', '_').replace('\r', '_');
防止拼接格式化字符串造成敏感信息泄露
// 敏感信息: 信用卡失效時間
Calendar expirationDate = Calendar.getInstance();
expirationDate.set(2020, Calendar.FEBRUARY, 20);
// 客戶端輸入
// String input = "12";
// poc
String input = "Date: %1$tY-%1$tm-%1$te";
if (!String.valueOf(expirationDate.get(Calendar.DAY_OF_MONTH)).equals(input)) {
// 存在格式化字符串注入
System.out.printf(input + " did not match! HINT: It was issued in month "
+ "%1$tm.\n", expirationDate);
// 正確使用
System.out.printf("%s did not match! HINT: It was issued in month "
+ "%2$tm.\n", input, expirationDate);
}
防止異常泄露敏感信息
敏感的異常消息
敏感的異常類型
FileNotFoundException
捕獲并拋出IOException
自定義SecurityIOException繼承IOException
不拋出異常, 只打印簡單日志
白名單
異常名稱
信息泄露或威脅描述
java.io.FileNotFoundException
泄露文件系統結構和文件名列舉
java.util.jar.JarException
泄露文件系統結構
java.util.MissingResourceException
資源列舉
java.security.acl.NotOwnerException
所有人列舉
java.util.ConcurrentModificationException
可能提供線程不安全的代碼信息
javax.naming.InsufficientResourcesException
服務器資源不足(可能有利于DoS攻擊)
java.net.BindException
當不信任客戶端能夠選擇服務器端口時造成開放端口列舉
java.lang.OutOfMemoryError
DoS
java.lang.StackOverflowError
DoS
java.sql.SQLException
數據庫結構,用戶名列舉
JSONException
-
防止空指針
調用null的方法, 如obj=null; obj.equals(xxx);, String s=null; s.split(" ");
訪問null的屬性
獲取null數組的長度
訪問數組中的null元素
防止除0
除法: /
模: %
多線程
防止鎖暴露
同步方法
同步this的代碼塊
同步public static鎖的代碼塊
正確示例:
public class LockDemo {
private final Object LOCK = new Object();
public void changeValue() {
synchronized (LOCK) {
// ...
}
}
}
鎖類型
錯誤示例:
Boolean只有兩個值
基礎數據類型的包裝類自動裝箱
private int count = 0;
private final Integer LOCK = count;
字符串常量: private final String LOCK = "LOCK";
Interned String對象: private final String LOCK = new String("LOCK").intern();, 在常量池中
getClass(): 子類和基類, 類和內部類獲取到的對象不同
內置鎖
private final Lock LOCK = new ReentrantLock();
public void changeValue() {
synchronized (LOCK) {
// ...
}
}
正確示例:
基礎數據類型的包裝類
private int count = 0;
private final Integer LOCK = new Integer(count);
字符串實例: private final String LOCK = new String("LOCK");
Object
Base.class/Class.forName("Base"): 明確類名
保護靜態數據
錯誤示例:
// 靜態數據
private static volatile int counter;
// 非靜態鎖對象
private final Object LOCK = new Object();
private static volatile int counter;
public synchronized void run() {
// ...
}
正確示例:
private static volatile int counter;
private static final Object LOCK = new Object();
保證順序獲得和釋放多個鎖
在finally中釋放鎖
禁止調用Thread.run()在當前線程中執行run()
禁止調用Thread.stop()導致線程非正常釋放鎖
通過修改volatile變量終止線程中的循環
調用Thread.interrupt()終止線程中的循環
禁止非線程安全的方法覆寫線程安全的方法
IO
使用File.createTempFile創建臨時文件, finally刪除
使用asReadOnlyBuffer()返回Buffer的只讀視圖
防止阻塞在外部進程
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("notepad.exe");
// java.lang.IllegalThreadStateException: process has not exited
int exitVal = proc.exitValue();
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("notepad.exe");
// 一直阻塞到外部進程終止
int exitVal = proc.waitFor();
使用int保存read()的返回值
序列化
使用transient保護敏感信息
序列化敏感數據時先簽名后加密, 防止簽名被篡改后正常數據校驗不通過
禁止序列化非靜態的內部類
如果某敏感操作使用安全管理器檢查, 防止反序列化繞過
防止反序列化注入
二進制
xml
XMLDecoder, 無消減措施
XStream, setupDefaultSecurity()或addPermission白名單
json: fastjson, jackson
type白名單
類加載
存在安全管理器檢查的方法要聲明為private或final, 防止子類復寫
自定義類加載器覆寫getPermissions()方法時需要調用super.getPermissions()
使用本地KeyStore中的證書校驗jar中的簽名
加解密
算法javax.crypto
SHA256 + 8Bytes salt + iterator 10000 => 256bit(32Bytes)
AES/GCM/NoPadding
RSA
DSA
ECDSA
禁止
DES
AES ECB
強隨機數SecureRandom
javax.net.ssl.SSLSocket
總結
以上是生活随笔為你收集整理的java编程规范每行代码窄字符,wiki/0xFE_编程规范.md at master · islibra/wiki · GitHub的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么在电脑上任意截屏_草地上打滚、墙上任
- 下一篇: java质数判断程序_java判断一个数