附飛書provider地址:https://github.com/tedgxt/keycloak-service-social-lark
場景介紹
在keycloak 8.0.1版本中添加飛書的identity provider,實現基于飛書的第三方免登陸授權。
問題現象
在調用飛書接口獲取用戶信息時,http response打印出來的名字信息(中文)是亂碼。
確定問題邊界
步驟一
由于keycloak的框架中,封裝了一個SimpleHttp
對象,用于http調用。因此得先判斷下http client是否該存在問題。
首先通過Postman去調用相同接口,得到response的數據如下。可以看到在postman里顯示的中文名字是正常顯示的,且response的header中,Content-Type
的值為application/json; charset=utf-8
。
{
"code": 0,
"data": {
"email": "xiatao.guan@longbridge.sg",
"en_name": "希夏(管峽濤)",
"expires_in": 6900,
"mobile": "+8615968878843",
"name": "希夏(管峽濤)",
"open_id": "ou_a2eba3085ce4b126891bcca48fe23eb3",
"refresh_expires_in": 2591700,
"refresh_token": "ur-D99OWLBARc3ZlD1Js0yqSg",
"tenant_key": "2c81b678d5cf975e",
"token_type": "Bearer",
"union_id": "on_b2455e0574333e08a0ecea8b932deac9",
"user_id": "fg7fe7f5"
},
"msg": "success"
}
步驟二
更進一步,在idea中新開一個java測試工程,導入keycloak相關的maven依賴包,通過keycloak的SimpleHttp
client進行接口調用,發現也可以打印出正常的中文。
String profileStr = SimpleHttp.doGet(PROFILE_URL, httpClient).auth(accessToken).asString();
logger.info(profileStr);
步驟三
在keycloak代碼中,添加測試代碼logger.info("中文")
以及System.Out.Println("中文")
,查看日志中打印出來的中文也是亂碼。
定位結果
綜合以上定位過程可以發現,在keycloak的中文字符都不能正常顯示。基本可以判斷亂碼問題與飛書的http調用無關,造成異常的原因應該在運行環境上。
通過對比keycloak和測試工程的環境發現,keycloak本身是運行在jboss中的,而測試工程里代碼是跑在idea啟動的JVM上。下一步就是要解決jboss的編碼問題。
通過Google發現,要解決JVM中的中文編碼問題,可以通過配置參數-Dfile.encoding=XXX
在啟動JVM的時候來指定編碼格式,從而不受操作系統和語言環境的影響。file.encoding
會影響未指定編碼的字符串、讀寫文件、URL編碼、打印等內容。
在jdk源碼中搜索file.encoding
,發現被defaultCharset()
方法使用了,打開對應文件Charset.java
:
/**
* Returns the default charset of this Java virtual machine.
*
* <p> The default charset is determined during virtual-machine startup and
* typically depends upon the locale and charset of the underlying
* operating system.
*
* @return A charset object for the default charset
*
* @since 1.5
*/
public static Charset defaultCharset() {
if (defaultCharset == null) {
synchronized (Charset.class) {
String csn = AccessController.doPrivileged(
new GetPropertyAction("file.encoding"));
Charset cs = lookup(csn);
if (cs != null)
defaultCharset = cs;
else
defaultCharset = forName("UTF-8");
}
}
return defaultCharset;
}
這段注釋說明了defaultCharset
返回的是JVM的默認編碼格式。且該編碼格式是在jvm啟動時便確定了,且依賴于jvm所在的操作系統的區域以及字符集。從代碼中可以看出,defaultCharset()
的值就是從 file.encoding
這個屬性中獲取的。
于是在keycloak代碼中加入如下代碼段,打印出當前的編碼配置:
// fix 中文字符問題
Properties initProp = new Properties(System.getProperties());
logger.info("current file.encoding:" + initProp.getProperty("file.encoding"));
logger.info("current Charset.defaultCharset :" + Charset.defaultCharset());
根據打印結果顯示,file.encoding
和Charset.defaultCharset
打印的值都是ASCII
,并非期望的UTF-8
,這就解釋了為何打印中文在日志中顯示的都是亂碼。
解決過程
知道了要改的內容,接下來解決的過程就比較簡單了。通過Google搜索到,jboss是使用standalone.xml
作為其啟動配置文件。在路徑/opt/jboss/keycloak/standalone/configuration
中找到了該文件。按照網上的方法,在standalone.xml
添加了相關編碼配置,然而打開日志一看并沒有作用。
于是逐步分析keycloak的啟動過程,通過對keycloak的dockerfile層層抽絲剝繭,發現entrypoint調用了standalone.sh
,在standalone.sh
中又調用了standalone.conf
。
bash-4.4$ cat standalone.sh|grep standalone
# Usage : standalone.sh --debug
# standalone.sh --debug 9797
RUN_CONF="$DIRNAME/standalone.conf"
...
...
...
# Read an optional running configuration file
if [ "x$RUN_CONF" = "x" ]; then
RUN_CONF="$DIRNAME/standalone.conf"
fi
再一次Google一下jboss的相關資料,發現jboss是使用standalone.xml
作為默認配置,而把standalone.conf
作為配置首選項(Linux環境中是standalone.conf
,Windows環境是standalone.bat
)。這也就說明了為什么修改了standalone.xml
后,并沒有起作用,還需要檢查standalone.conf
的配置。
The standalone startup script i.e. *standalone.sh* for OSX/Linux and *standalone.bat* for Windows, utilizes:
- *standalone.conf/standalone.conf.bat*: defines the JVM preferences for the standalone server instance
- *standalone.xml*: defines the default configurations for the server; we can find it under *$JBOSS_HOME/standalone/configuration*.
因此在standalone.conf
中添加上配置JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8"
,重新構建keycloak鏡像并啟動容器。
在未添加JVM的編碼格式時,keycloak的啟動日志中JAVA_OPTS
如下:
=========================================================================
JBoss Bootstrap Environment
JBOSS_HOME: /opt/jboss/keycloak
JAVA: java
JAVA_OPTS: -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED
=========================================================================
指定了file.encoding=UTF-8
后,keycloak的啟動日志中可以看到JAVA_OPTS
中編碼格式為UTF-8
。
=========================================================================
JBoss Bootstrap Environment
JBOSS_HOME: /opt/jboss/keycloak
JAVA: java
JAVA_OPTS: -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true -Dfile.encoding=UTF-8 --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED
=========================================================================
打開日志,發現已經可以打印出中文,大功告成。
總結思考
對keycloak、jboss以及jvm基本上算是小白,看到編碼問題更是慌了手腳,最終花了一整天的時間才徹底解決這個問題。回想起來,解決該問題的根本因素就是jvm的編碼配置。
總結一下幾點事后經驗:
碰到未知問題,首先確定問題邊界,縮小問題定位范圍。如用postman調用飛書接口排除飛書server端問題;用logger打印中文字符排除http調用的問題。
要敢于了解系統原理,最好直接去閱讀相關源碼(時間充足的情況)。本項目最初預期只是寫點java代碼去開發一個keycloak的provider用于對接飛書,開發、解決問題的過程中卻額外涉及了編碼、jboss、jvm等新知識,人總是喜歡蹲在熟悉的環境中,為了解決問題只好硬著頭皮上了。
構建知識領域地圖,不斷補全相關知識漏洞。如果對jvm、jboss一開始就很熟悉,想必解決問題的時間可以減半;知識掌握的越多,后續遇到新問題的解決過程就會越順利。
Google大法好!