Spring Boot YAML配置文件设置字段默认值

SpringBoot中application.yaml如何设置配置项默认值一直困扰了我很久,官方文档也没有写如何设置默认值,今天有时间研究了下,发现只要加个冒号就可以了,如${配置名:默认值}.

application.yml

1
2
3
appconfig:
fileServerAccessKey: ${FILE_SERVER_ACCESS_KEY:default_access_key}
...

配置文件类

1
2
3
4
5
6
@Component
@ConfigurationProperties(prefix = "appconfig")
class Config {
String fileServerSecretKey
...
}

如果项目未设置FILE_SERVER_ACCESS_KEY环境变量,则fileServerSecretKey字段会以default_access_key为默认值.

研究了下源码发现 PropertyPlaceholderHelper.java#L147这个类先会检查配置值中是否包含valueSeparator这个字符串,如果有,则会用valueSeparator来分割字符,从中取出默认值,而valueSeparator默认值就是:.

Linux下启动Java守护进程方法

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
36
37
38
39
40
41
42
43
44
45
46
47
#!/bin/sh

DESC="Java Service"
NAME=java-service
PIDFILE=/tmp/$NAME.pid
PATH_TO_JAR=~/java-service.jar
PATH_TO_JAVA=/usr/local/jdk1.8.0_131/bin/java
SERVICE_CONFIG='-Dserver.port=8080 -DLOG_PATH=/var/log'
COMMAND="$PATH_TO_JAVA -- $SERVICE_CONFIG -jar $PATH_TO_JAR"


d_start() {
start-stop-daemon --start --quiet --background --make-pidfile --pidfile $PIDFILE --exec $COMMAND
}

d_stop() {
start-stop-daemon --stop --quiet --pidfile $PIDFILE
if [ -e $PIDFILE ]
then rm $PIDFILE
fi
}

case $1 in
start)
echo -n "Starting $DESC: $NAME"
d_start
echo "."
;;
stop)
echo -n "Stopping $DESC: $NAME"
d_stop
echo "."
;;
restart)
echo -n "Restarting $DESC: $NAME"
d_stop
sleep 1
d_start
echo "."
;;
*)
echo "usage: $NAME {start|stop|restart}"
exit 1
;;
esac

exit 0

HPKP(HTTP Public Key Pinning)详解

HPKP是什么

HPKP(HTTP Public Key Pinning)又名公钥打孔,可以通过告知客户端将特定的加密公钥与特定服务器关联,以减少通过伪造证书进行中间人攻击(MITM)的风险.

HPKP原理

HPKP是一种首次信任技术,当客户端第一次访问服务器的时候,服务器通过特定的HTTP头来告知客户端哪些公钥是属于它的,客户端会将该信息存储一段时间,当客户端第二次访问服务端的时候,它会期望当前证书链中至少有一个证书的公钥指纹与通过HPKP已知的公钥指纹相匹配,如果没有找到匹配的公钥指纹,客户端应该警告用户.

生成HPKP

公钥指纹可以通过openssl命令来成成:

1
2
3
4
5
6
7
#!/bin/sh

openssl s_client -connect [YOUR_DOMAIN]:443 -showcerts | awk '/-----BEGIN/{f="cert."(n++)} f{print>f} /-----END/{f=""}'

for c in cert.*; do
openssl x509 <$c -noout -pubkey | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
done

上面的命令会将当前证书链中的所有公钥指纹以base64格式打印出来.

公钥指纹也可以通过在线网站REPORT URI来生成

备份密钥(Backup Key)

什么是备份密钥

备份密钥是不属于当前证书链的公钥指纹

为什么要备份密钥

假设你只对你的子证书(leaf certificate)做了公钥指纹,你发现你的证书私钥被泄露了,这时你不得不更换当前的证书,这时唯一能恢复的办法就是将当前证书切换到备份密钥指向的备份证书.

其他类似备份密钥的方法

备份密钥的缺点也很明显,你需要同时支付至少两份证书的钱.

另一个办法就是你可以拿另一家证书颁发机构(CA)的公钥指纹做为备份密钥,当你要更换证书的时候,你只需要在这家CA申请一份新的证书.当然这么做也有缺点,当这家CA被黑,攻击者可以给自己颁发一份你网站的证书同时又通过你的HPKP验证,应为这份恶意证书也在CA的证书链上.

MDN上建议网站要有一个备份密钥

HPKP has the potential to lock out users for a long time if used incorrectly! The use of backup certificates and/or pinning the CA certificate is recommended.

但在Chrome(59.0.3071.115)下测试,如果HPKP头不包含备份密钥Chrome将忽略该头.

添加HPKP头

1
response.setHeader('Public-Key-Pins', 'pin-sha256="base64+primary=="; pin-sha256="base64+backup=="; max-age=5184000; includeSubDomains')

sha256
公钥的哈希值,base64编码

max-age
在浏览器的存储时间

includeSubDomains
对子域名是否有效

report-uri
验证失败后调用的URL(注:必须不同域名)

总结

用HPKP的网站比较少,就发现github在用.

HPKP缺点也很明显:出错的成本太高,当你不得不切换到备份密钥指向的证书时,如果备份密钥配错了而且你只有一个备份密钥,你的网站将会无法被访问,无法访问的时间取决于max-age的值,比如上面的例子max-age配了两个月,那你的用户将在两个月内无法访问你的网站,这后果是灾难性的.

个人认为HPKP带来的风险大于收益,不建议使用.

外部链接

HTTP Public Key Pinning (HPKP)

HTTP PUBLIC-KEY-PINNING EXPLAINED

Guidance on setting up HPKP

Is HTTP Public Key Pinning Dead?

PostgreSQL JDBC 时间类型存取细节

  • PostgreSQL中默认时区为UTC
  • record表中有一类型为Time without timezone的字段event_time
  • 本机的时区为UTC+8

现象:

  • 在页面提交的时间为 15:00:00+0
  • 到了数据库时间却显示为 23:00:00+0
  • 查询出来后页面显示的时间是 15:00:00+0
  • 控制台打印出来的SQL如下:
    1
    insert into record (id, event_time) values (nextval ('hibernate_sequence'), '23:00:00.000000 +08:00:00')

原因:
查看源代码,发现在生成insert语句时,JDBC会通过toString方法将Time类型转成String,而问题就出现在Time转String这里,cal对象是取的JVM的默认时区,而JVM默认时区取的应是本机的时区UTC+8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public synchronized String toString(Calendar cal, Time x) {
if(cal == null) {
cal = this.defaultCal;
}

cal.setTime(x);
this.sbuf.setLength(0);
appendTime(this.sbuf, cal, cal.get(14) * 1000000);
if(this.min74) {
this.appendTimeZone(this.sbuf, cal);
}

showString("time", cal, x, this.sbuf.toString());
return this.sbuf.toString();
}

所以15:00:00+0在调用toString方法后返回值是23:00:00.000000 +08:00:00,但是数据库中event_time类型时Time without timezone,所为23后面的时区信息将会被忽略,最终数据库的时间变成了UTC+0 的23:00:00点

而将event_time从数据库中查询出来时,又将其当成了UTC+8的23:00:00,换算成UTC+0时间正好是UTC+0 15:00:00

所以要避免页面时间与数据库时间不统一的办法就是设置JVM的时区

1
-Duser.timezone=UTC

Hibernate学习

占位符(named parameters)对order by无效

1
2
//对于最后两字段sortField和sortOrder设值时是没有效果的
SELECT * FROM t_table WHERE column=:param1 ORDER BY :sortField :sortOrder

isDirty细节

1
2
3
4
5
6
7
8
9
def product = Product.get(xxx)
product.name = 'anotherName'
product.save()
product.isDirty() //true

def product = Product.get(xxx)
product.name = 'anotherName'
product.save(flush:true)
product.isDirty() //false

hibernate 中entity get时会有个loadstate 来记录entity的初始状态,调用isDirty方法将比较当前 entity和loadstate来判断entity是否发生改变,然而当flush:true时,将刷新loadstate状态,从时调用isDirty将反回true.

Session Flush

  • HQL查询会导致session flush
    所以在调用isDirty前进行HQL查询会导致isDirty始终返回false

flush与transaction的联系

flush后Hibernate会生成SQL语句并执行,但只有环绕的transaction提交后才会真正的同步到数据库

Groovy学习

sort方法作用于Set和List时的区别

1
2
3
4
5
6
7
8
def a = new HashSet()
a << [3, 1, 2]

def b = new ArrayList()
b << [2, 1, 3]

assert b.sort().is(b) //true
assert a.sort().is(a) //false

sum and flatten 作用于Collection时的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[].sum() returns null
[].flatten() returns []
//原因在于调用sum时默认initvalue为null,当list为空时直接返回initvalue
private static Object sum(Iterable self, Object initialValue, boolean first) {
Object result = initialValue;
Object[] param = new Object[1];
for (Object next : self) {
param[0] = next;
if (first) {
result = param[0];
first = false;
continue;
}
MetaClass metaClass = InvokerHelper.getMetaClass(result);
result = metaClass.invokeMethod(result, "plus", param);
}
return result;
}

@EqualsAndHashCode注解

当exclude类所有field及property时,会导制所有实例都相等

1
2
3
4
5
6
7
8
9
10
11
@EqualsAndHashCode(excludes = ['x', 'y'])
static class Point {
int x
int y
}

public static void main(String[] args) {
def point0 = new Point(x: 1, y: 1)
def point1 = new Point(x: 2, y: 2)
assert point0 == point1
}

对ORM类慎用该注解,可能会导制关联丢失,当mutable字段变化时,会导制hash值改变,导制无法定位到槽位,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@EqualsAndHashCode
static class Point {
int x
int y
}

public static void main(String[] args) {
def point0 = new Point(x: 1, y: 1)
def point1 = new Point(x: 2, y: 2)
def set = new HashSet()
set << point0
set << point1

assert set.contains(point0)

point0.x = 6
assert set.contains(point0) //false !!!
}

Tomcat传递环境变量

  1. 在Tomcat bin目录下新建setenv.sh, 编辑setenv.sh,添加如下语句
1
export JAVA_OPTS="-DKEY1=VALUE1 -DKEY2=VALUE2 -DKEY3=VALUE3"
  1. Tomcat启动时会自动加载setenv.sh

  2. 通过如下代码中获取环境变量

1
System.getProperty("KEY1")