在阅读本文之前,强烈建议你自己动手部署一下springcloud相关的模块,例如eureka server,config server,zuul等。已经搞过的大神请忽略。
本文使用的测试环境为:
springboot:1.5.6.RELEASE
springcloud:Dalston.SR3
这里我要强烈鄙视一下那些文章中不给出版本号,jar包完整路径等关键信息的注水肉们!
ok,闲话扯完,进入正文。
前几天和小伙伴们聊了聊app安全访问接口的事情,大家纷纷吐槽,说以前用http协议访问接口,最大的问题就是用fiddler这样的抓包工具,直接就能看到明文的访问url,参数列表和参数值。熊孩子们用脚本写个小机器人,就能像app一样完整地体验我们的应用,随意抓取我们的数据,然后很轻松地就能山寨出我们的app产品。除此之外,熊孩子们也可以随意修改请求中的参数值来尝试攻击和破解我们的后端接口,找到侵入点,然后就……。艾玛,画面太美我都不敢想了。
基于对安全性的担心,有必要将app以前的http协议访问的方式改造为更为安全的https协议访问的方式。在和小伙伴们开会讨论过后,我们捋出了下面几个改造需求:
1. 将http请求改造为https请求(废话,不改造还折腾个啥)
2. 将要访问的api接口作为请求参数传递给app后端系统,后端系统重新拼接url后访问接口,并且原请求中的参数,除了api接口之外,必须原样带过去。
3. 返回的数据经过加密处理后给到app。
在明确了这几点需求后,这两天就在进行针对性的改造。唉,2018年的粽子节就这样过了。
1. 将http请求改造为https请求
关于http请求和https请求的区别,请参考下面这个帖子,说得很明白。
http://412887952-qq-com.iteye.com/blog/2400846
第一步,先生成证书。可以使用java自带的keytool,也可以使用其他工具生成证书后再导入到java的keystore。这里我使用的是一个叫openssl的工具。给你们个传送门
我是64位的win7系统,所以我下载的是 Win64 OpenSSL v1.1.0h 这个版本。下载完成安装好以后,就开始根据教程生成秘钥。openssl的教程参考这个帖子 openssl 生成keystore 。这个帖子里面也有将生成的秘钥导入keystore的方法。
刚安装好openssl以后,本来兴冲冲地以为三下五除二就能搞定秘钥的事儿了,结果cmd里面一敲命令,直接报错
D:\OpenSSL-Win64\bin>openssl genrsa -out zydc.pem Generating RSA private key, 2048 bit long modulus .............................+++ ...........................................................+++ e is 65537 (0x010001) D:\OpenSSL-Win64\bin>openssl req -x509 -new -key zydc.pem -out zydc.crt Can't open D:\OpenSSL-Win64\bin\openssl.cnf for reading, No such file or directo ry 13928:error:02001002:system library:fopen:No such file or directory:crypto\bio\b ss_file.c:74:fopen('D:\OpenSSL-Win64\bin\openssl.cnf','r') 13928:error:2006D080:BIO routines:BIO_new_file:no such file:crypto\bio\bss_file. c:81: unable to find 'distinguished_name' in config problems making Certificate Request 13928:error:0E06D06A:configuration file routines:NCONF_get_string:no conf or env ironment variable:crypto\conf\conf_lib.c:272:
根据出错提示给出的信息,是因为没找到openssl.cnf文件。根据百度上搜出来的帖子里面说的,先去看了一下有没有 OPENSSL_CONF 这个环境变量,然后又看了一下这个环境变量指向的 openssl.cnf 文件有没有。结果坑爹的是没有。不过还好openssl的安装目录中有这个文件,所以就把 OPENSSL_CONF 里面的路径改成了 openssl.cnf 文件的实际所在位置。
然后我索性又配置了一个 OPENSSL_HOME 的环境变量,然后就想配置java环境变量一样,将其配置进path里面,这样我在任何目录下都能调用openssl的可执行文件了。
下图是我生成的秘钥列表
其中 zydc.pem 是首先生成出来的私钥,主要用于对其它秘钥的签名操作。htx-server.pem 是为app的后端网关,即zuul开启https所生成的,将它用pkcs12的格式导出后,用keytool导入到keystore中,便得到了 htx-server.jks。这个 htx-server.jks 便是要放入到zuul当中的秘钥。而 htx-server.crt 是app端用于访问后端接口的时候使用的公钥文件。
生成秘钥的完整的命令记录如下:
D:\OpenSSL-Win64\bin\mycerts>openssl genrsa -out zydc.pem Generating RSA private key, 2048 bit long modulus .................+++ ..........................................................+++ e is 65537 (0x010001) D:\OpenSSL-Win64\bin\mycerts>openssl req -x509 -new -key zydc.pem -out zydc.crt You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:beijing Locality Name (eg, city) []:beijing Organization Name (eg, company) [Internet Widgits Pty Ltd]:zydc Organizational Unit Name (eg, section) []:zydc Common Name (e.g. server FQDN or YOUR name) []:zydc Email Address []:[email protected] D:\OpenSSL-Win64\bin\mycerts>dir 驱动器 D 中的卷没有标签。 卷的序列号是 1670-14B9 D:\OpenSSL-Win64\bin\mycerts 的目录 2018-06-17 22:58 <DIR> . 2018-06-17 22:58 <DIR> .. 2018-06-17 22:58 1,402 zydc.crt 2018-06-17 22:57 1,702 zydc.pem 2 个文件 3,104 字节 2 个目录 32,437,641,216 可用字节 D:\OpenSSL-Win64\bin\mycerts>openssl genrsa -out htx-server.pem Generating RSA private key, 2048 bit long modulus .................................................................+++ ................................+++ e is 65537 (0x010001) D:\OpenSSL-Win64\bin\mycerts>openssl req -new -key htx-server.pem -out htx-serv r.csr You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:beijing Locality Name (eg, city) []:beijing Organization Name (eg, company) [Internet Widgits Pty Ltd]:zydc Organizational Unit Name (eg, section) []:htx Common Name (e.g. server FQDN or YOUR name) []:htx Email Address []:[email protected] Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []: D:\OpenSSL-Win64\bin\mycerts>dir 驱动器 D 中的卷没有标签。 卷的序列号是 1670-14B9 D:\OpenSSL-Win64\bin\mycerts 的目录 2018-06-17 23:01 <DIR> . 2018-06-17 23:01 <DIR> .. 2018-06-17 23:01 1,080 htx-server.csr 2018-06-17 22:59 1,706 htx-server.pem 2018-06-17 22:58 1,402 zydc.crt 2018-06-17 22:57 1,702 zydc.pem 4 个文件 5,890 字节 2 个目录 32,437,620,736 可用字节 D:\OpenSSL-Win64\bin\mycerts>openssl x509 -req -in htx-server.csr -CA zydc.crt CAKey zydc.pem CAcreateserial -days 3650 -out htx-server.crt x509: Unknown digest CAKey x509: Use -help for summary. D:\OpenSSL-Win64\bin\mycerts>openssl x509 -req -in htx-server.csr -CA zydc.crt CAkey zydc.pem CAcreateserial -days 3650 -out htx-server.crt x509: Unknown parameter CAcreateserial x509: Use -help for summary. D:\OpenSSL-Win64\bin\mycerts>openssl x509 -req -in htx-server.csr -CA zydc.crt CAkey zydc.pem -CAcreateserial -days 3650 -out htx-server.crt Signature ok subject=C = CN, ST = beijing, L = beijing, O = zydc, OU = htx, CN = htx, emailA dress = [email protected] Getting CA Private Key D:\OpenSSL-Win64\bin\mycerts>openssl pkcs12 -export in htx-server.crt -inkey ht -server.pem -out htx-server.p12 pkcs12: Use -help for summary. D:\OpenSSL-Win64\bin\mycerts>openssl pkcs12 -export -in htx-server.crt -inkey h x-server.pem -out htx-server.p12 Enter Export Password: Verifying - Enter Export Password: D:\OpenSSL-Win64\bin\mycerts>dir 驱动器 D 中的卷没有标签。 卷的序列号是 1670-14B9 D:\OpenSSL-Win64\bin\mycerts 的目录 2018-06-17 23:04 <DIR> . 2018-06-17 23:04 <DIR> .. 2018-06-17 23:03 1,278 htx-server.crt 2018-06-17 23:01 1,080 htx-server.csr 2018-06-17 23:04 2,485 htx-server.p12 2018-06-17 22:59 1,706 htx-server.pem 2018-06-17 22:58 1,402 zydc.crt 2018-06-17 22:57 1,702 zydc.pem 2018-06-17 23:03 18 zydc.srl 7 个文件 9,671 字节 2 个目录 32,437,616,640 可用字节 D:\OpenSSL-Win64\bin\mycerts>keytool -importkeystore -srckeystore htx-server.p1 -destkeystore htx-server.jks -srcstoretype pkcs12 输入目标密钥库口令: 再次输入新口令: 它们不匹配。请重试 输入目标密钥库口令: 再次输入新口令: 输入源密钥库口令: keytool 错误: java.io.IOException: keystore password was incorrect D:\OpenSSL-Win64\bin\mycerts>keytool -importkeystore -srckeystore htx-server.p1 -destkeystore htx-server.jks -srcstoretype pkcs12 输入目标密钥库口令: 再次输入新口令: 输入源密钥库口令: 已成功导入别名 1 的条目。 已完成导入命令: 1 个条目成功导入, 0 个条目失败或取消 D:\OpenSSL-Win64\bin\mycerts>dir 驱动器 D 中的卷没有标签。 卷的序列号是 1670-14B9 D:\OpenSSL-Win64\bin\mycerts 的目录 2018-06-17 23:08 <DIR> . 2018-06-17 23:08 <DIR> .. 2018-06-17 23:03 1,278 htx-server.crt 2018-06-17 23:01 1,080 htx-server.csr 2018-06-17 23:08 2,234 htx-server.jks 2018-06-17 23:04 2,485 htx-server.p12 2018-06-17 22:59 1,706 htx-server.pem 2018-06-17 22:58 1,402 zydc.crt 2018-06-17 22:57 1,702 zydc.pem 2018-06-17 23:03 18 zydc.srl 8 个文件 11,905 字节 2 个目录 32,437,612,544 可用字节 D:\OpenSSL-Win64\bin\mycerts>keytool -importcert -keystore htx-server.jks -file zydc.crt 输入密钥库口令: 所有者: [email protected], CN=zydc, OU=zydc, O=zydc, L=beijing, ST=bei ing, C=CN 发布者: [email protected], CN=zydc, OU=zydc, O=zydc, L=beijing, ST=bei ing, C=CN 序列号: e03ef1d889178f42 有效期开始日期: Sun Jun 17 22:58:07 CST 2018, 截止日期: Tue Jul 17 22:58:07 CST 2018 证书指纹: MD5: 41:A4:11:EF:BA:8A:0C:30:92:40:F4:2D:35:D5:7A:43 SHA1: 46:BF:5E:4A:1C:41:F2:3A:0A:F2:F9:B4:3E:DD:AC:52:42:CA:E7:CC SHA256: 9E:85:A4:70:99:D3:B9:F0:41:BB:27:8E:D3:2A:38:72:7D:F5:16:E9:66 16:D8:02:16:BE:4B:98:10:5A:AB:CD 签名算法名称: SHA256withRSA 版本: 3 扩展: #1: ObjectId: 2.5.29.35 Criticality=false AuthorityKeyIdentifier [ KeyIdentifier [ 0000: B3 B2 49 FA 24 2E 79 BE 18 4B 2B 8D 9D 9F AF E2 ..I.$.y..K+..... 0010: 44 C0 F1 DD D... ] ] #2: ObjectId: 2.5.29.19 Criticality=true BasicConstraints:[ CA:true PathLen:2147483647 ] #3: ObjectId: 2.5.29.14 Criticality=false SubjectKeyIdentifier [ KeyIdentifier [ 0000: B3 B2 49 FA 24 2E 79 BE 18 4B 2B 8D 9D 9F AF E2 ..I.$.y..K+..... 0010: 44 C0 F1 DD D... ] ] 是否信任此证书? [否]: yes 错误的答案, 请再试一次 是否信任此证书? [否]: 是 证书已添加到密钥库中 D:\OpenSSL-Win64\bin\mycerts>
至此,秘钥的准备工作就完成了。将 htx-server.jks 复制到zuul模块的resources目录下面,就可以配置zuul开启https访问模式了。关于zuul开启https,我在百度上找了很久都没有找到相关的资料。有一位赵姓同学,中国好爸爸,他启发了我。他说zuul是基于springboot的项目,可以试试springboot开启https的方法。这句话顿时让我茅塞顿开,任督二脉一下打通。感谢赵同学的点拨,我在 http://412887952-qq-com.iteye.com/blog/2400846 这篇帖子中还真就找到了关于开启https的配置
#https端口号. server.port: 443 #证书的路径. server.ssl.key-store: classpath:keystore.p12 #证书密码,请修改为您自己证书的密码. server.ssl.key-store-password: 123456 #秘钥库类型 server.ssl.keyStoreType: PKCS12 #证书别名 server.ssl.keyAlias: tomcat
将其添加进application.yml文件中,并修改 server.ssl.key-store 项为 server.ssl.key-store: classpath:htx-server.jks后,启动zuul。谁知好事多磨,console中抛异常说443端口被占用了。好吧,我把端口号修改为8443,再次启动zuul,结果又抛异常
2018-06-18 22:37:41.055 ERROR 14888 --- [ main] o.a.coyote.http11.Http11NioProtocol : Failed to start end point associated with ProtocolHandler ["https-jsse-nio-8443"] java.lang.IllegalArgumentException: java.io.IOException: DerInputStream.getLength(): lengthTag=109, too big. at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:115) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.AbstractJsseEndpoint.initialiseSsl(AbstractJsseEndpoint.java:86) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:225) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.AbstractEndpoint.start(AbstractEndpoint.java:990) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.coyote.AbstractProtocol.start(AbstractProtocol.java:635) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.catalina.connector.Connector.startInternal(Connector.java:1022) [tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.catalina.core.StandardService.addConnector(StandardService.java:225) [tomcat-embed-core-8.5.23.jar:8.5.23] at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer.addPreviouslyRemovedConnectors(TomcatEmbeddedServletContainer.java:250) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer.start(TomcatEmbeddedServletContainer.java:193) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.startEmbeddedServletContainer(EmbeddedWebApplicationContext.java:297) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.finishRefresh(EmbeddedWebApplicationContext.java:145) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546) [spring-context-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at com.ServiceZuulApp.main(ServiceZuulApp.java:18) [classes/:na] Caused by: java.io.IOException: DerInputStream.getLength(): lengthTag=109, too big. at sun.security.util.DerInputStream.getLength(DerInputStream.java:599) ~[na:1.8.0_121] at sun.security.util.DerValue.init(DerValue.java:365) ~[na:1.8.0_121] at sun.security.util.DerValue.<init>(DerValue.java:320) ~[na:1.8.0_121] at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1914) ~[na:1.8.0_121] at java.security.KeyStore.load(KeyStore.java:1445) ~[na:1.8.0_121] at org.apache.tomcat.util.net.SSLUtilBase.getStore(SSLUtilBase.java:139) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.SSLHostConfigCertificate.getCertificateKeystore(SSLHostConfigCertificate.java:187) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.jsse.JSSEUtil.getKeyManagers(JSSEUtil.java:183) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:113) ~[tomcat-embed-core-8.5.23.jar:8.5.23] ... 19 common frames omitted 2018-06-18 22:37:41.056 ERROR 14888 --- [ main] o.apache.catalina.core.StandardService : Failed to start connector [Connector[HTTP/1.1-8443]] org.apache.catalina.LifecycleException: Failed to start component [Connector[HTTP/1.1-8443]] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:167) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.catalina.core.StandardService.addConnector(StandardService.java:225) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer.addPreviouslyRemovedConnectors(TomcatEmbeddedServletContainer.java:250) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer.start(TomcatEmbeddedServletContainer.java:193) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.startEmbeddedServletContainer(EmbeddedWebApplicationContext.java:297) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.finishRefresh(EmbeddedWebApplicationContext.java:145) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546) [spring-context-4.3.12.RELEASE.jar:4.3.12.RELEASE] at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE] at com.ServiceZuulApp.main(ServiceZuulApp.java:18) [classes/:na] Caused by: org.apache.catalina.LifecycleException: service.getName(): "Tomcat"; Protocol handler start failed at org.apache.catalina.connector.Connector.startInternal(Connector.java:1031) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) ~[tomcat-embed-core-8.5.23.jar:8.5.23] ... 13 common frames omitted Caused by: java.lang.IllegalArgumentException: java.io.IOException: DerInputStream.getLength(): lengthTag=109, too big. at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:115) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.AbstractJsseEndpoint.initialiseSsl(AbstractJsseEndpoint.java:86) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:225) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.AbstractEndpoint.start(AbstractEndpoint.java:990) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.coyote.AbstractProtocol.start(AbstractProtocol.java:635) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.catalina.connector.Connector.startInternal(Connector.java:1022) ~[tomcat-embed-core-8.5.23.jar:8.5.23] ... 14 common frames omitted Caused by: java.io.IOException: DerInputStream.getLength(): lengthTag=109, too big. at sun.security.util.DerInputStream.getLength(DerInputStream.java:599) ~[na:1.8.0_121] at sun.security.util.DerValue.init(DerValue.java:365) ~[na:1.8.0_121] at sun.security.util.DerValue.<init>(DerValue.java:320) ~[na:1.8.0_121] at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1914) ~[na:1.8.0_121] at java.security.KeyStore.load(KeyStore.java:1445) ~[na:1.8.0_121] at org.apache.tomcat.util.net.SSLUtilBase.getStore(SSLUtilBase.java:139) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.SSLHostConfigCertificate.getCertificateKeystore(SSLHostConfigCertificate.java:187) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.jsse.JSSEUtil.getKeyManagers(JSSEUtil.java:183) ~[tomcat-embed-core-8.5.23.jar:8.5.23] at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:113) ~[tomcat-embed-core-8.5.23.jar:8.5.23] ... 19 common frames omitted 2018-06-18 22:37:41.071 INFO 14888 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat] 2018-06-18 22:37:41.080 WARN 14888 --- [ost-startStop-1] o.a.c.loader.WebappClassLoaderBase : The web application [ROOT] appears to have started a thread named [spring.cloud.inetutils] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread: sun.misc.Unsafe.park(Native Method) java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) java.lang.Thread.run(Thread.java:745) 2018-06-18 22:37:41.085 INFO 14888 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_SERVICE-ZUUL-WITH-HTTPS/192.168.1.104:service-zuul-with-https:8443 - registration status: 204 2018-06-18 22:37:41.103 INFO 14888 --- [ main] utoConfigurationReportLoggingInitializer : Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled. 2018-06-18 22:37:41.113 ERROR 14888 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPLICATION FAILED TO START ***************************
根据异常提示中的信息,我判断应该是秘钥文件格式的问题。于是将 server.ssl.keyStoreType: PKCS12 这一项注释掉,再次启动zuul,这一次终于正常启动起来了,并且也正确地在eureka server上注册了。
为了测试zuul确实能接收到https请求并进行转发,我就写了个ZuulFilter,类型为pre
package com.filters; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import okhttp3.Headers; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.internal.http.HttpMethod; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.StreamUtils; import com.common.models.RetCode; import com.common.models.RetResult; import com.common.util.redis.RedisUtils; import com.google.gson.Gson; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; @Component public class MyHttpsFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(MyHttpsFilter.class); @Override public String filterType() { // return "pre"; return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return FilterConstants.SEND_FORWARD_FILTER_ORDER; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString())); String requestURL = request.getRequestURL().toString(); String apiName = request.getParameter("apiName"); String data = request.getParameter("data"); log.info("ok"); return null; } }
这里有个细节说一下,我之前测试环境用的springboot版本是1.4.2.RELEASE,springcloud版本是Camden.SR3,这个版本中没有FilterConstants这个东西。我在参考了别人的代码之后,换了springboot和springcloud的版本后才搞定的。再次鄙视那些不说版本号的注水肉们
秘钥有了,zuul也准备好了,然后就是模拟https请求来测试zuul了。这方面相关的工具,不是fiddler就是postman,没有之三。我使用的postman,因为它更专业。关于postman请求https接口,可以参考这篇帖子
http://developer.huawei.com/ict/forum/thread-21465.html
其中配置证书的环节,crt是证书文件,即公钥,key是私钥文件。由于我们访问接口只需要公钥,所以只配置crt文件就可以了。
ok,万事俱备,在zuul的过滤器的run方法中打上断点,在postman中填入url、端口号和请求参数,点击send,成功地在zuul的过滤器中断下来了,并且请求参数都正确拿到了。
至此,我们的第1步,将http请求改造为https请求就完成了。有的同学可能会说,那只是在zuul的过滤器里面断下来了,到底有没有走到应用节点的controller并返回数据呢?别急,且听我慢慢道来。
刚才在postman构建https请求中,请求参数里面有一个apiName,那个参数就是为第2步的工作准备的。当然这里我们也可以直接把apiName拼接到请求的url后面,直接请求应用节点的controller。
所以,第1步确实真真的已经搞定了哦
2. 将要访问的api接口作为请求参数传递给app后端系统
这一步的工作也非常的蛋疼,折腾了好长时间。我一开始的想法是在zuul的过滤器中修改request的url,然后百度了一下,找了这篇帖子 https://www.jb51.net/article/123405.htm
兴冲冲地按照帖子中的方法试了一下,直接抛异常。调试以后发现
Object originalRequestPath = context.get(FilterConstants.REQUEST_URI_KEY);
这里取出的 originalRequestPath 直接是个null。所以这个方法被pass了。
然后我又发现HttpServletRequest对象的getRequestURL方法返回的是个StringBuffer,于是我又兴冲冲地调用StringBuffer的append方法把apiName附加到URL的后面,还是抛异常。然后我又尝试将修改后的url塞回到HttpServletRequest里面,结果人家的属性只能让你往外取,不能修改,而且还变态地加了lock。唉,看来在zuul的过滤器里面改url然后再让zuul去转发,这条路是没戏了。正当我愁眉不展白发生的时候,我看到了这篇帖子
Tomcat容器下Zuul网关加解密后的第一次请求出现400错误的问题
我也不知道我是怎么翻出来的这篇帖子,但是里面的tomcat这个关键词让我灵光一闪。也许在应用节点上可以做一个过滤器来进行请求转发,就像以前运行在tomcat容器中的web项目做的那样。ok,心动立马行动,我找到了这篇帖子
按照帖子中的攻略,我立马写了一个过滤器UrlFilter,并写了一个配置类来加载UrlFilter过滤器。下面是代码
package com.config; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.common.filters.UrlFilter; /** * Created by qhong on 2018/5/16 15:28 **/ @Configuration public class FilterConfig { @Bean public FilterRegistrationBean registFilter() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new UrlFilter()); registration.addUrlPatterns("/*"); registration.setName("UrlFilter"); registration.setOrder(1); return registration; } }
package com.common.filters; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import com.common.util.ParameterRequestWrapper; public class UrlFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest)request; HttpServletResponseWrapper httpResponse = new HttpServletResponseWrapper((HttpServletResponse) response); System.out.println(httpRequest.getRequestURI()); String path=httpRequest.getRequestURI(); String apiName = httpRequest.getParameter("apiName"); if(null != apiName) { ParameterRequestWrapper wrapper = new ParameterRequestWrapper(httpRequest); wrapper.removeParameter("apiName"); String newPath = path + apiName; httpRequest.getRequestDispatcher(newPath).forward(wrapper,response); } else { chain.doFilter(request,response); } return; } }
在写UrlFilter时,还出现了一个问题。在取出apiName参数并重新拼接url后,我需要将HttpServletRequest中的apiName参数移除,否则过滤器会无休止地将apiName附加到url中,然后就……,你懂得,内存溢出。幸运的话抛个异常程序挂掉,不幸的话就跟死机没啥区别了。然而蛋疼的是,HttpServletRequest不允许我们修改它的参数列表,并且它的参数列表还是lock住的,它大爷的。所以我又自己写了个类来扩展HttpServletRequestWrapper这个类,并通过它来重新封装request对象。HttpServletRequestWrapper类的用法可以参考这个帖子
一顿折腾后,世界终于美好了。累死哥了!
至此,第2步工作也完成了。基本的app和后端交互的功能也都有了。
但是,仍然有许多问题还有待解决。比如第3步的工作。再有就是用fiddler来抓包的话,仍然可以抓到明文的请求参数和返回数据。如果有熊孩子用模拟器来模拟运行app程序,并通过fiddler来进行抓包的话,那我们还是相当于裸奔。
所以,路还长着呢