加密通信软件Signal 2.92版本编译安装折腾手记(Ubuntu 18.04)

前言

加密通信软件Signal是开源的,安全性很高,号称斯诺登也推荐大家使用。既然这么好,那必然会有不少人去尝试复制修改这个软件。但是有个很大的问题,这个软件的官网和github仓库中都没有看到安装文档,尝试的人基本上都是铩羽而归。据Reddit上有人说联系过Signal的开发人员,开发人员说他们的精力要放在优先级更高的事情上,所以顾不上安装部署文档。在我看来,Signal的意思是“只要我放出来全套源代码来让大家检查,对我的安全性有信心就行。要想复制我,没门,或者代价高”(但也可能真的人手不够)。Reddit原帖在此

顺带说一下,另一款加密通信软件“密信”也是开源的,但是貌似没有看到其REST API服务器的代码。

既然部署Signal服务器的难度很高,那基本上也就没什么人去给它的服务端贡献代码了,因为没法调试、测试,怎么能发起有效的PR?所以Signal的Desktop/iOS/Android客户端提供了开发和测试文档,让人能给客户端贡献代码。不过这个指南不是在客户端的README.md中,而是在CONTRIBUTING.md中,这是我先是试图用npm编译它的Desktop客户端、发现编不过去后才注意到的,得用yarn编译。

编译

言归正传,继续说Signal服务端。首先,Signal Server目前的代码是2.92版本,既然有工程文件pom.xml,那就用Maven编译好了。仓库地址是https://github.com/signalapp/Signal-Server/

mvn install -DskipTests

编译这个版本的代码需要用JDK 11+(Ubuntu 18.04默认的版本就是11),如果用低版本的JDK(比如Ubuntu 16.04中的),Maven会报一个和数字11有关的错误,如果不熟悉的话,这个看似莫名其妙的错误信息可能会让你挠头半天,算一个坑。所以折腾的环境定为Ubuntu 18.04。编译的最终产物是TextSecureServer-2.92.jar。

寻找别人的安装经验指南

编译好了,接下来要部署了。先谷歌一下关键字Signal Sever + Install/Deploy,引用最多的是一篇1.88版本的安装指南:《Signal Server Installation Guide》。看下面的评论,有人说此人是骗子,因为省略了很多步骤和细节,无法依葫芦画瓢。注意看这个指南的最后一句,作者说自己是个自由职业者,有问题可以邮件联系他。猜测这位同学可能是要用这个帖子做导流来收取一些咨询费的,所以没写那么细。但在没有官方文档的情况下,这个指南也是一个很好的开始了(此处掌声致谢),只能先按这个搞。

配置的总入口:模板文件sample.yml

把Signal Server代码仓库中的配置模板文件sample.yaml拿出来,改名为config.yml,按上面这个指南配置,会发现2.92比1.88的配置项有了一些变化(那是自然)。后面的事实会证明,代码仓库中的这个yml模板,是很老的版本,和代码根本不匹配,照这个配几乎无用。

第三方接口的帐号申请

先搜罗一下yml中需要申请的第三方接口的帐号,挨个申请。
要申请的帐号清单:

  • Twilio           用于短信等功能。messagingServicesId的申请页面和account的页面不在一起,万一没找到,messagingServicesId先填个假的。
  • S3               亚马逊S3,用于图床CDN、聊天的附件等。
  • SQS             亚马逊SQS,消息队列。
  • reCAPTCHA  谷歌reCAPTCHA,用于识别机器人。
  • GCM            谷歌推送服务。谷歌早已从GCM切换到FCM,无法再申请到GCM帐号。试了一下用谷歌FCM的帐号填写在那里也没报错,但没实际测试对安卓手机的推送功能是否正常。
  • APN             苹果推送服务。

配置服务器的域名

没有域名的话,买一个。HTTPS/WSS(WebSocket Secure)服务都是需要校验域名对应的证书的,Twilio服务的反向连接也需要配置域名。

安装Redis

sudo apt-get install -y redis-server

修改配置文件:

sudo vi /etc/redis/redis.conf

1、把 supervised no 改为 supervised systemd

2、把 bind 127.0.0.1 ::1 改为 bind 127.0.0.1

重启Redis:

sudo systemctl restart redis.service

安装PostgreSQL

安装数据库软件,创建数据库,创建帐号:

sudo apt-get install postgresql postgresql-contrib -y
sudo -u postgres psql -c "CREATE DATABASE accountdb"
sudo -u postgres psql -c "CREATE DATABASE messagedb"
sudo -u postgres psql -c "CREATE DATABASE abusedb"
sudo -u postgres psql -c "CREATE DATABASE keysdb"
sudo -u postgres psql -c "CREATE DATABASE readdb"
sudo -u postgres psql -c "CREATE USER signal \
WITH ENCRYPTED PASSWORD 'password'"

修改数据库配置:

sudo vi /etc/postgresql//main/postgresql.conf

1、将 listen_addresses='localhost'  改为  listen_addresses='*'

2、启用数据库的密码认证。尾部追加一行,内容为:
     host all all * md5

重启数据库:

sudo systemctl restart postgresql

安装coturn

TURN协议是给NAT打洞,让聊天信息能穿透到内网的。coturn是TURN的一个实现。

安装coturn并生成证书:

sudo apt-get install -y coturn
sudo openssl req -x509 -newkey rsa: \
-keyout /etc/turn_server_pkey.pem \
-out /etc/turn_server_cert.pem -days -nodes

生成证书时要输入服务器的完整域名。

编辑配置文件:

sudo vi /etc/turnserver.conf

配置下面的字段:

listening-ip=
external-ip=
use-auth-secret
static-auth-secret=
user=user1:password1
realm=服务器的完整域名
cert=/etc/turn_server_cert.pem
pkey=/etc/turn_server_pkey.pem
cli-password=

配置好之后,重启coturn服务,在防火墙上对外开放coturn的端口(默认是3478端口),然后用下面的页面测试coturn的基本功能:

https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/

注意:coturn有两套认证机制,一套是用户名、密码的,一套是共享密钥(secret)。其中共享密钥是给REST接口使用的,Signal Server要用到共享密钥。而测试coturn基本功能时使用的页面只支持用户名+密码的认证方式,而且coturn是优先使用共享密钥的,也就是说如果同时配了共享密钥、用户名+密码的话,在上面这个测试页面中是测试不过的。所以要先禁用共享密钥的设置,等在上面的这个页面中测试通过后,再把共享密钥的配置恢复。

配置好共享密钥后,启动daemon:

sudo turnserver -a -o -v -n -u test:test -r "服务器的完整域名"

安装Apache2

在防火墙上对外开放服务器的80端口,因为获取免费证书时需要回连80端口来验证。

下面用a2enmod打开需要的几种Apache代理模块,用certbot获取域名对应的免费HTTPS证书,最后会提示将80端口重定向到443端口。

sudo install apache2
sudo a2enmod proxy proxy_http proxy_wstunnel
sudo add-apt-repository ppa:certbot/certbot -y
sudo apt-get update -y
sudo apt-get install python-certbot-apache -y
sudo certbot --authenticator standalone --installer apache -d "你的服务器完整域名" --pre-hook "systemctl stop apache2" --post-hook "systemctl start apache2"

编辑Apache的HTTPS站点的配置文件:

sodu vi /etc/apache2/sites-available/-default-le-ssl.conf

设置反向代理,将Apache的443端口来的客户端请求反向代理到Signal Server的8080端口。443的wss://反向代理到8080的ws://,443的https://反向代理到8080的http://。Signal Server除了用8080端口来监听ws://和http://,还会起一个8081的对内管理端口,可以用浏览器打开http://127.0.0.1:8081查看。

HTTPS站点的配置样本如下:

<IfModule mod_ssl.c>
<VirtualHost *:>
  ServerAdmin webmaster@excmple.com
  DocumentRoot /var/www/html   AllowEncodedSlashes NoDecode   RewriteEngine on
  RewriteCond %{HTTP:Upgrade} websocket [NC]
  RewriteCond %{HTTP:Connection} upgrade [NC]
  RewriteRule .* "ws://localhost:8080$0" [P,L]   ProxyRequests off
  ProxyPass / http://localhost:8080/
  ProxyPassReverse / http://localhost:8080/   #LogLevel trace6   ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined   ServerName 你的服务器完整域名
  SSLCertificateFile /etc/letsencrypt/live/你的服务器完整域名/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/你的服务器完整域名/privkey.pem
  Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

其中HTTPS证书是Signal客户端也需要用到的。

上述反向代理的规则是通过谷歌抄自"How to Reverse Proxy Websockets with Apache 2.4"。这个配置首先用RewriteEngine匹配客户端来的wss://协议中的协议提升字段,匹配上的就转发到ws://,没匹配的用ProxyPass反向代理到http://。但是原始帖子中的配置有三处错误导致Signal的客户端和服务器的通信不成功,此处给出的是已经踩坑修正过的(有兴趣的可以自行对比一下错误在哪里)。

此处既然遇到问题,就需要用到码农的两大利器:日志、调试器。通过修改上述站点的日志配置字段为LogLevel trace6来打开Apahe的详细日志并重启Apache,进而分析/var/log/access.log、/var/log/error.log来确定是Apache的问题,还是8080端口的Signal Server的问题。

AllowEncodedSlashes NoDecode这句就是根据日志分析出来要加上的,因为iOS客户端发给Signal Server的URI中包含特殊字符如斜杠%2F、加号%2B等,如果没有这句的话,Apache可能直接对这些URI返回404,并不会转发到Signal Server的8080端口。

安装CDS

Signal的CDS(联系人发现服务)对于部署者应该是个大坑。为什么这么说呢?CDS一般并不和Signal Server部署在一台机器上,而是使用独立的特殊硬件来保证其安全性,也就是Intel SGX。SGX能创建硬件隔离区域,称为enclave(中文名叫“飞地”)。目前云服务提供商中只有微软Azure和Intel合作提供了云上的SGX的试用环境,而且有地理区域限制。如果搞不到Azure的SGX环境,只能购买支持SGX的Intel机器。这次我没有配置SGX,而是在config.yml中配置了假数据。

有兴趣的可以参考折腾过CDS的人写的指南,这个指南也不再维护了:https://github.com/on-premise-signal/signal-setup-guide
CDS的代码仓库是:https://github.com/signalapp/ContactDiscoveryService

Signal Server的处女Run

初始化数据库,建表(此处暂未深究为什没有处理名为readdb的数据库):

java -jar TextSecureServer-2.92.jar  messagedb  migrate  config/config.yml
java -jar TextSecureServer-2.92.jar accountdb migrate config/config.yml
java -jar TextSecureServer-2.92.jar keysdb migrate config/config.yml
java -jar TextSecureServer-2.92.jar abusedb migrate config/config.yml

填满config.yml的各个字段后,尝试运行Signal Server:

java -jar ./service/target/TextSecureServer-2.92.jar server config/config.yml

结果不出意料:是跑不起来的。好在打印的错误信息明确指明了是哪个配置项有问题,只能去抠代码了(因为Signal的开发人员都说了,有代码不就有了一切嘛),从此要开始“看错误信息 -> 谷歌搜错误/看代码/调代码 -> 改配置”的循环,这是依赖谷歌搞开发的时代下码农的宿命,还得准备好一个比较隐蔽、速度又比较快的ladder以保证谷歌24小时在线。

新的配置模板文件config.yml

好在Signal Server的配置项解析代码是集中在service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java这个文件中的。用IDEA打开工程,翻到这个文件,找到每个配置项的类型声明和解析代码,统一扒出来,做一个新的config.yml模板:

twilio:
  accountId:
  accountToken:
  numbers: # array
  localDomain:
  messagingServicesId: push:
  queueSize: 200 attachments:
  accessKey:
  accessSecret:
  bucket:
  region: cdn:
  accessKey:
  accessSecret:
  bucket:
  region: cache:
  url:
  replicaUrls: # array directory:
  redis:
    url:
    replicaUrls: # array
  sqs:
    accessKey:
    accessSecret:
    queueUrl:
    region: us-east-1
  client:
    userAuthenticationTokenSharedSecret: # hex string
    userAuthenticationTokenUserIdSecret: # hex string
  server:
    replicationUrl:
    replicationPassword:
    replicationCaCertificate: accountDatabaseCrawler:
  chunkSize: 1000
  chunkIntervalMs: 8000 pushScheduler:
  url:
  replicaUrls: # array messageCache:
  redis:
    url:
    replicaUrls: # array
  persistDelayMinutes: 10 messageStore:
  driverClass: org.postgresql.Driver
  user:
  password:
  url: # example: jdbc:postgresql://127.0.0.1:5432/messagedb abuseDatabase:
  driverClass: org.postgresql.Driver
  user:
  password:
  url: # example: jdbc:postgresql://127.0.0.1:5432/abusedb testDevices: # array: [ { "number" => "", "code" => "" }, { "number" => "", "code" => "" }, ... ] maxDevices: # array: [ { "number" => "", "count" => "" }, { "number" => "", "count" => "" }, ... ] keysDatabase:
  driverClass: org.postgresql.Driver
  user:
  password:
  url: # example: jdbc:postgresql://127.0.0.1:5432/keysdb accountsDatabase:
  driverClass: org.postgresql.Driver
  user:
  password:
  url: # example: jdbc:postgresql://127.0.0.1:5432/accountdb read_database:
  driverClass: org.postgresql.Driver
  user:
  password:
  url: # example: jdbc:postgresql://127.0.0.1:5432/readdb #limits: #use default values in source code #httpClient: #use default values in source code #webSocket: #use default values in source code turn:
  secret:
  uris: # array gcm:
  senderId:
  apiKey: apn:
  pushCertificate:
  pushKey:
  bundleId:
  sandbox: false unidentifiedDelivery:
  certificate:
  privateKey:
  expiresDays: voiceVerification:
  url:
  locales: # array recaptcha:
  secret: storageService:
  userAuthenticationTokenSharedSecret: # hex string backupService:
  userAuthenticationTokenSharedSecret: # hex string transparentDataIndex: # Map<String, String> #server: #use default values in source code
#logging: #use default values in source code
#metrics: #use default values in source code

重新把这个config.yml的各个字段填满,别高兴太早,依然是跑不起来的,因为有些字段有严格的要求,是在代码中通过Java标注来要求的。一开始肯定是想当然地按自己的字面理解去填写的,无法满足要求。

比如testDevices这个配置项,在WhisperServerConfiguration.java中是这么写的:

@Valid
@NotNull
@JsonProperty
private List<TestDeviceConfiguration> testDevices = new LinkedList<>();

说明testDevices是json格式,代码直接给new了一个空集作为默认值,可以不配置,但是如果你在yml文件中配置了这项的话,一定要有值,也就是要给它配上至少一个{number, code}对。所以干脆在yml中注释掉它好了。maxDevices等配置项也类似。所以为什么说在一开始捋这些字段时就要注意一下Java标注和对应的使用代码。

再比如storageService和backupService下面的userAuthenticationTokenSharedSecret这个字段,解析代码如下。显然这是一个十六进制字符串格式的字段,而且不能为空串。

@NotEmpty
@JsonProperty
private String userAuthenticationTokenSharedSecret; public byte[] getUserAuthenticationTokenSharedSecret() throws DecoderException {
return Hex.decodeHex(userAuthenticationTokenSharedSecret.toCharArray());

配置APN证书

稍麻烦一点的配置是APN证书,先要用Apple Developer帐号申请拿到证书文件,然后把证书文件转换为PEM格式,然后把PEM文件中的字符串贴在yml文件中。注意不是在yml中配置PEM文件的路径。为什么?Read The F*cking Code! 代码中是直接读取PEM串的内容,而不是读取PEM文件,除非把代码改为从PEM文件中读取(1.88安装指南那个帖子的评论里面已经有人这么干了)。

Apple给的证书文件转换为PEM的步骤:
  文件一:aps.cer (证书文件)
  文件二:push-pro.p12 (私钥文件,可能设置有密码保护)

1、把APN证书转换为PEM格式:

openssl x509 -in aps.cer -inform der -out aps.pem

2、把APN私钥转换为PEM格式:

输入.p12文件的密码,再设定一个PEM密码,转PEM格式:

openssl pkcs12 -nocerts -out push-pro.pem -in push-pro.p12

输入上一步设定的PEM密码,去除密码:

openssl rsa -in push-pro.pem -out push-pro-key-noenc.pem

yml文件中最终配置的APN证书格式如下。APN私钥类似。

apn:
  pushCertificate: |
    -----BEGIN CERTIFICATE-----
    MIIGCDCCBPCgAwIBAgIId9kHAiU6ZxIwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
    (此处省略五千字。。。注意对齐)
    mYm1yVxw4lLirPPyva9YOqX626RzyKQ0mcOiV0vURjALRiAkNklfpgr877wilvdS
    dynVUqLbErSibtAh
    -----END CERTIFICATE-----

配置Sealed Sender的密钥

更麻烦一点的是unidentifiedDelivery这个功能的配置。这个功能又称为Sealed Sender,即匿名发聊天信息。功能的设计参考https://signal.org/blog/sealed-sender/。这个功能需要证书certificate、私钥privateKey,但并不是PEM等标准格式的。仍然翻一下相关的解析代码,最后会发现Signal Server自带了生成这些数据的命令行参数。具体的代码在service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java这文件中,从这代码可以看出,需要两步走。

第一步:生成CA的证书和私钥。这个CA证书(注意不是CA的私钥)在Signal的客户端中可能也要用到,客户端访问服务器时可以走HTTPS的代理。

java -jar TextSecureServer-2.92.jar certificate --ca

第二步:利用CA的私钥,生成服务器的证书。

java -jar TextSecureServer-2.92.jar certificate --key XXX --id NNNN

其中XXX是CA的私钥,NNNN是自己决定的一个数字编号。

最终的配置文件config.yml

最后得到的config.yml的内容。用这个配置,仅仅可以跑通手机客户端的初步注册,其他很多功能仍需要调测。Signal客户端也有一些配置要修改。先搞到这里,不进一步折腾了。

twilio:
accountId: ACf1111111111111111111111111111
accountToken: 3e22222222222222222222222222
numbers:
- +1923456789
localDomain: 你的完整域名
messagingServicesId: MG3333333333333333333333333333 push:
queueSize: 200 attachments:
accessKey: AKIAAAAAABBBBBCCCCCCC
accessSecret: QhABCCCCCCCCCCCCCDDDDDDDDDDDDDDDD
bucket: fake
region: ap-southeast-1 cdn:
accessKey: AKIAAAAAABBBBBCCCCCCC
accessSecret: QhABCCCCCCCCCCCCCDDDDDDDDDDDDDDDD
bucket: fake
region: ap-southeast-1 cache:
url: http://127.0.0.1:6379
replicaUrls: [ "http://127.0.0.1:6379" ] directory:
redis:
url: http://127.0.0.1:6379
replicaUrls: [ "http://127.0.0.1:6379" ]
sqs:
accessKey: AKIAAAAAABBBBBCCCCCCC
accessSecret: QhABCCCCCCCCCCCCCDDDDDDDDDDDDDDDD
queueUrl: https://sqs.ap-southeast-1.amazonaws.com/17093374723/fake
region: ap-southeast-1
client:
userAuthenticationTokenSharedSecret: 33445566 #fake
userAuthenticationTokenUserIdSecret: 22117788 #fake
server:
replicationUrl: http://127.0.0.1 #fake
replicationPassword: fake
replicationCaCertificate: | #fake
-----BEGIN CERTIFICATE-----
MIIGCDCCBPCgAwIBAgIId9kHAiU6ZxIwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
(此处省略五千字。。。)
mYm1yVxw4lLirPPyva9YOqX626RzyKQ0mcOiV0vURjALRiAkNklfpgr877wilvdS
dynVUqLbErSibtAh
-----END CERTIFICATE----- accountDatabaseCrawler:
chunkSize: 1000
chunkIntervalMs: 80000000 pushScheduler:
url: http://127.0.0.1:6379
replicaUrls: [ "http://127.0.0.1:6379" ] messageCache:
redis:
url: http://127.0.0.1:6379
replicaUrls: [ "http://127.0.0.1:6379" ]
persistDelayMinutes: 10 messageStore:
driverClass: org.postgresql.Driver
user: signal
password: password
url: jdbc:postgresql://127.0.0.1:5432/messagedb abuseDatabase:
driverClass: org.postgresql.Driver
user: signal
password: password
url: jdbc:postgresql://127.0.0.1:5432/abusedb keysDatabase:
driverClass: org.postgresql.Driver
user: signal
password: password
url: jdbc:postgresql://127.0.0.1:5432/keysdb accountsDatabase:
driverClass: org.postgresql.Driver
user: signal
password: password
url: jdbc:postgresql://127.0.0.1:5432/accountdb read_database:
driverClass: org.postgresql.Driver
user: signal
password: password
url: jdbc:postgresql://127.0.0.1:5432/readdb #limits: #use default values in source code #httpClient: #use default values in source code #webSocket: #use default values in source code turn:
secret: turnsecret
uris:
- turn:127.0.0.1:3478?transport=udp gcm:
senderId: 71722022434122
apiKey: AAAfffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaakkkkkkkkkkkkkkkkeeeeeeeeeeeee apn:
bundleId: fake
pushCertificate: |
-----BEGIN CERTIFICATE-----
MIIGCDCCBPCgAwIBAgIId9kHAiU6ZxIwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
(此处省略五千字。。。)
mYm1yVxw4lLirPPyva9YOqX626RzyKQ0mcOiV0vURjALRiAkNklfpgr877wilvdS
dynVUqLbErSibtAh
-----END CERTIFICATE-----
pushKey: |
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAsH9uXZaGp2DZ0sfsSI9ovrv4hI2OjEDbzefv5+ZWy0kVdL/r
   (此处省略五千字。。。)
2UtkH7KXehd9wueaIaZ5+j9PKy3t6x4GPZE4b/fkuXToCijOsbCfhA==
-----END RSA PRIVATE KEY----- unidentifiedDelivery:
certificate: |
CiYI5A8SIQUO+IlR4c9wwDNmYkD4RRJAob3q9UG4vwkaV1k9iiBPhAXv5liN85Q8vFwEJawOWxIng6WwXo/hoXNFb/TinsRPlI5XuSfQs23FDUVRVXZggQ== #fake
privateKey: |
iCTXE4i2P1t5Y7Vy7SujSQmFfW1E4= #fake
expiresDays: 365 voiceVerification:
url: http://127.0.0.1 #fake
locales: [ "en-US" ] #fake recaptcha:
secret: 6LfFFFFFFAAAAAAKKKKKKEEEEEEgJ7kX-pSL storageService:
userAuthenticationTokenSharedSecret: 112233445566 #fake backupService:
userAuthenticationTokenSharedSecret: 778899AABBCC #fake transparentDataIndex: # Map<String, String> #server: #use default values in source code
#logging: #use default values in source code
#metrics: #use default values in source code

服务端和客户端代码版本不匹配的问题

最后还有一个坑需要说明:目前(2020-03-16)github上,Signal Server的master分支最新代码和iOS客户端的master分支最新代码并不是匹配的。证据呢?依然是通过日志、调试器这两大利器。

iOS手机端在保存用户的姓名时,会给服务器发一个这样的PUT请求,服务器返回HTTP 400,

/v1/profile/name/MN329VW0j6XOVNH8pgqLOyjmYTdddBp8cBE5B2mPILy4jzAb8PTABCcfep4FfQwPJIRJKQqnhooZjuQrzRSeCeC%2FUzWRqDMN9XFEDIvoCFWrl 

Apache的详细log中会显示是Signal Server返回的HTTP 400。Signal Server用的容器框架是Ketty,指定的logger类是SLF4J,而SLF4J的日志开关配置要看它绑定的具体实现,如果要打开日志开关看日志,又要折腾半天,还是用调试器吧。操起IDEA远程调试,在服务器上放开5005调试端口,以调试监听方式启动Signal Server:

java -Xdebug -Xrunjdwp:transport=dt_socket,address=0.0.0.0:,server=y,suspend=y -jar TextSecureServer-2.92.jar server config/config.yml

在开发机上的IDEA中设置连接服务器的5005端口调试,在Ketty框架的内部代码中转一大圈,最终会发现,/v1/profile/name/这个URI需要的参数在base64解码后的长度必须是72,否则Ketty直接返回400,根本不会调用setProfile()函数。

@Timed
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Path("/name/{name}")
public void setProfile(@Auth Account account, @PathParam("name") @UnwrapValidatedValue(true) @Length(min = 72,max= 72) Optional<String> name) {
account.setProfileName(name.orElse(null));
accountsManager.update(account);
}

但是,iOS客户端的代码显示编码后的长度是108,也就是解码后的长度是81,不匹配。需要用较早一点的iOS客户端代码版本才行。

结语

一坑复一坑,坑坑何其多!正所谓:山穷水尽疑无路,柳暗花明又一坑。

加密通信软件Signal 2.92版本编译安装折腾手记(Ubuntu 18.04)的更多相关文章

  1. 在Ubuntu 18.04上安装Tensorflow

    我们将经历几个阶段,安装cuda-9.0,cudnn和tensorflow cpu以及tensorflow gpu版本.最后我们将用cuda-9.0安装pytorch.在MARVEl电影中黑寡妇的“我 ...

  2. Ubuntu 18.04 安装 python3.7

    Ubuntu 18.04系统内置了Python 3.6和Python 2.7版本,以下是在Ubuntu 18.04系统中安装Python 3.7版本的方法. 1. 执行所有升级# sudo apt u ...

  3. Ubuntu 18.04 Server上安装LAMP

    由于要进行渗透测试,所以这两天就在搭LAMP的环境(过程及其痛苦) 这里分享一些我遇到的问题. 首先介绍一下我的使用环境  VM虚拟机,ubuntu 与主机NAT连接 由于之前一直使用的是kali(默 ...

  4. Ubuntu 18.04上安装 phpMyAdmin

    我们将安装 phpMyAdmin 在 Ubuntu18.04 上配合 Apache 一起工作. 在安装 phpMyAdmin 之前需要已经安装了LAMP栈并提供了web页面. 如果没有安装可以参照 U ...

  5. Win10+WSL2+Ubuntu 18.04(WSL下)+VS Code(Win10下)+TexLive 2019(Ubuntu下)安装和配置

    本人手头电脑是Win10 Home版全新安装的系统,由于不想在新系统盘里面安装TexLive导致固态硬盘不断扩大,所以,考虑安装Ubuntu做为WSL,然后把TexLive安装在Ubuntu,并通过V ...

  6. 在Ubuntu 18.04中安装Wine QQ、微信、TIM

    近日重新安装了Ubuntu 18.04,因此要重新安装一下Wine QQ.微信之类的,完整安装Wine系列软件一直是一个老大难的问题,网上搜集到的博客也比较零散,因此这里特此写篇博客记录一下 0. 这 ...

  7. Linux下指定版本编译安装LAMP

    说明: 操作系统:CentOS 6.5 64位 需求: 编译安装LAMP运行环境 各软件版本如下: MySQL:mysql-5.1.73 Apache:httpd-2.2.31 PHP:php-5.2 ...

  8. 记录:Ubuntu 18.04 安装 tensorflow-gpu 版本

    狠下心来重新装了系统,探索一下 gpu 版本的安装.比较令人可喜的是,跟着前辈们的经验,还是让我给安装成功了.由于我是新装的系统,就像婴儿般纯净,所以进入系统的第一步就是安装 cuda,只要这个不出错 ...

  9. [笔记] Ubuntu 18.04源码编译安装OpenCV 4.0流程

    标准常规安装方法安装的OpenCV版本比较低,想尝鲜使用4.0版本,只好源码安装. 安装环境 OS:Ubuntu 18.04 64 bit 显卡:NVidia GTX 1080 CUDA:10.0 c ...

随机推荐

  1. 老男孩Linux运维50期 --于海科--决心书

    1.我叫于海科,来自于甘肃省天水市,之前就读于兰州石化职业技术学院,我是听之前的学长说老男孩教育出来就业不错,我特此来这培训希望出来能够找到一份不错的工作.2.五个月学完,目标薪资是11k.3.达到目 ...

  2. sphinx的使用

    1.下载地址 http://sphinxsearch.com/downloads/release/ 2.将其解压到D:\sphinx,并在D:\sphinx下新建目录data(用来存放索引文件)与lo ...

  3. Leo2DNT(雷傲论坛转DiscuzNT)1.0转换程序发布

    数据转换程序 雷傲论坛(Leobbs4.x) -> Discuz!NT V1.0    本转换程序基于Leobbs4.x设计     声明: 1.本程序只对数据作转换,不会对原来的雷傲论坛(数据 ...

  4. 地表最简单安装MySQL及配置的方法,没有之一

    第一步下载我的压缩包 链接:https://pan.baidu.com/s/1EE40dU0j2U1d-bAfj7TeVA 提取码:n25c 复制这段内容后打开百度网盘手机App,操作更方便哦 第二步 ...

  5. P6474 [NOI Online #2 入门组] 荆轲刺秦王

    P6474 [NOI Online #2 入门组] 荆轲刺秦王 bfs+差分+卡常 本来我其实是场内选手,但是因为记错提交时间,晚了半小时才交,交不上了,就自动降级为了场外选手 题面复杂,不简述了 首 ...

  6. linux上github的简单使用

    Git是一个分布式的版本控制系统,最初由Linus Torvalds编写,用作Linux内核代码的管理.在推出后,Git在其它项目中也取得了很大成功,尤其是在Ruby社区中.目前,包括Rubinius ...

  7. 7) 项目准备流程 和 django权限六表

    一.项目准备 1. 创建django项目 2. 创建数据库 —— init文件中声明mysql —— settings中配置数据库 import pymysql pymysql.install_as_ ...

  8. TCP 可靠传输

    TCP报文段首部 序号: TCP是面向字节流的.在一个TCP连接中传送的字节流中的每一个字节都按顺序编号.整个要传送的字节流的起始序号必须在连接建立时设置.首部中的序号字段值则指的是本报文段所发送的数 ...

  9. [Linux] Hexo 搭建个人博客

    不做笔记出了bug就得重新再看一遍视频 视频来源: https://www.bilibili.com/video/BV1Yb411a7ty?t=75 安装 先安装 nodejs,npm, git 安装 ...

  10. P2308 添加括号(dfs记录dp路径)

    传送门 \(一看肯定是区间DP(因为和和合并石子很相似,都要加n-1次)\) \(转移方程为(其中he[i][j]是i到j的和)\) \[dp[i][j]=min(dp[i][j],dp[i][k]+ ...