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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值