找回密码
 加入怎通
查看: 291|回复: 8

Nginx okhttp 双向验证 配置(nginx负载均衡有几种方式)

[复制链接]
我来看看 发表于 2023-03-22 08:37:41 | 显示全部楼层 |阅读模式
( u3 h' e) v. }( L5 D! V# c" a" w9 Z

最近在忙App安全,而双向验证是第一步,本文主要记录我在双向验证过程中遇到的坑,以及解决虽然网上类似的教程贼多,但大部分基于 Tomcat… 小打小闹尚可,毕竟反代还是 Nginx 好通过本文,你将顺利获知如何实现 android 与 nginx 的双向验证。

+ y% m" P5 Q" d) T! c

我们以一段 Nginx 的配置开始吧:server {listen 443 ssl;server_name www.kpromise.top;ssl_certificate /etc/letsencrypt/live/kpromise.top/fullchain.pem;

4 ^1 a) t) E3 n; f9 @

ssl_certificate_key /etc/letsencrypt/live/kpromise.top/privkey.pem;... // 此处省略 N 多配置这里,域名是本站域名,另外配置了证书和key,这是普通的 https 配置了。

; h7 I8 u& p$ [1 h+ |; ^1 t, d; u5 q

接下来,我们看另外一个配置:server {listen 443 ssl;server_name api.kpromise.top;ssl_certificate /root/ssl/server.crt;

3 H- m+ E: S# J5 g* y8 `

ssl_certificate_key /root/ssl/server.key;ssl_password_file /root/ssl/password_file;ssl_client_certificate /root/ssl/ca.crt;

5 M" j1 |0 U: I0 {; b z/ W

ssl_verify_client optional_no_ca;location / {root /root/ssl/api/;}}这是一段中规中矩的服务端验证客户端的配置,主要变化是多了 ssl_client_certificate 这行。

* p2 t! _" V& W# z

具体请看 http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_client_certificate那么,这里的ca.crt 又是如何产生的呢,下面进入主题,生成证书:

- B5 ^6 ]! P+ x! X

首先,生成 ca.key 接下来签名证书的时候会用到:1、openssl genrsa -des3 -out ca.key 4096接着,生成 ca.crt 文件,crt 文件是客户端认证的证书文件,同样的,在你签名证书的时候会用到

; J+ q; ^: G3 F4 X

2、openssl req -new -x509 -days 365 -key ca.key -out ca.crt3、生成证书:openssl genrsa -des3 -out server.key 1024

: U3 I6 Y6 d0 Z; O- z; w

openssl req -new -key server.key -out server.csr会提示你输入 Common Name 一般是你的域名哦4、用 ca.key 和 ca.crt 签名证书:openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

/ f8 O8 y4 O; d5 _. h0 @9 q0 E

5、重复 3-4 两步,但是 -out 改为 client.x 来生成并签署另一份证书,比如:openssl genrsa -des3 -out client.key 1024openssl req -new -key client.key -out client.csr

7 ^! n) a9 ~& j

openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt6、修改 Nginx 配置为 我刚开始提供的第二份配置那样,即:

) w! z) Y7 R$ b. H* W$ h5 `

server {listen 443 ssl;server_name api.kpromise.top;ssl_certificate /root/ssl/server.crt;ssl_certificate_key /root/ssl/server.key;

" e7 T1 f* Q5 O" t- n% y! A

ssl_password_file /root/ssl/password_file;ssl_client_certificate /root/ssl/ca.crt;ssl_verify_client on;

& ^. n j& a5 h7 Y- [: Q

location / {root /root/ssl/api/;}}然后 把相关文件移入配置所列的目录,并执行 nginx -s reload,此时,服务端就会校验客户端证书了这里的 ssl_password_file 对应的是一个普通文件,里面是 创建证书时设置的密码哦。

# a" h/ t$ ~9 D

接下来,我们看下 android 端的修改首先查看 android 支持 的 keystore 类型: https://developer.android.com/reference/java/security/KeyStore#summary 大致如下:。

# X% j4 n& \1 J. {) ]& G$ N2 D

AndroidCAStore 14+AndroidKeyStore 18+BCPKCS12 1-8BKS 1+BouncyCastle 1+PKCS12 1+PKCS12-DEF 1-8可以看得出,AndroidCAStore、PKCS12、BouncyCastle、BKS 都是不错的选择,但是我看网上资料,基本都说 转为 android 支持的类型 BKS 这又是什么鬼?明明有 PKCS12 可以选择啊。

g. G7 z7 D9 M. B

我们接下来导出 client.p12 以及 server.p12 文件,命令如下:openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "client"

. Q2 ] ^6 U& x" F

openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -name "client"然后把 client.p12 和 server.p12 都给android 客户端,android 客户端将他们放到 /src/main/assets/ 下面。

( ^4 _* Z( B- Z! v

最后,我们修改 okhttp 吧:class SSLHelper {private var clientPw: String? = nullprivate var clientPKCSFileName: String = "client.p12"

* Y: w# K7 k4 Q5 g j U/ @& n( L, a

private var serverPKCSFileName: String = "server.p12"private var serverPw: String? = nullprivate val protocolType = "TLS"

; N0 b" F/ I2 H/ T- J: W

private val keyStoreType: String = "PKCS12"private val certificateFormat: String = "X509"fun initClient

* {4 g9 ?- {$ v( G

(clientPKCSFileName: String, clientPw: String): SSLHelper {this.clientPKCSFileName = clientPKCSFileName

4 A1 y, D& O; ?" K! t! n

this.clientPw = clientPwreturn this}fun initServer(serverPKCSFileName: String, serverPw: String): SSLHelper {

: \* A- S+ I3 Q; d1 {

this.serverPKCSFileName = serverPKCSFileNamethis.serverPw = serverPwreturn this}fun getSSLCertification

* P4 E, U- W1 e- v; O

(context: Context): SSLSocketFactory? {if (clientPw == null) throw RuntimeException("please call initClient first...")

2 J# ]* d' M {; |& g

if (serverPw == null) throw RuntimeException("please call initServer first...")var sslSocketFactory: SSLSocketFactory? = null

/ N- a( \0 p ^7 R

try {val clientKeyStore = KeyStore.getInstance(keyStoreType)val serverKeyStore = KeyStore.getInstance

7 ^; Q7 o5 d/ ]% Q3 m

(keyStoreType)val clientPrivateKeyInputStream = context.assets.open(clientPKCSFileName)val serverPublicKeyInputStream = context.assets.

: z4 t' Z; v* `4 E

open(serverPKCSFileName)clientKeyStore.load(clientPrivateKeyInputStream, clientPw?.toCharArray())serverKeyStore.

/ v, q3 d2 X" k* T+ P$ A9 T1 W

load(serverPublicKeyInputStream, serverPw?.toCharArray())clientPrivateKeyInputStream.close()serverPublicKeyInputStream.

9 I0 U6 @4 ^% Y) i# K+ e

close()val sslContext = SSLContext.getInstance(protocolType)val trustManagerFactory = TrustManagerFactory.

8 [& G6 Y" _- U7 W) k8 ?

getInstance(certificateFormat)val keyManagerFactory = KeyManagerFactory.getInstance(certificateFormat)

1 i+ k8 v* _7 q) U- J, O

trustManagerFactory.init(serverKeyStore)keyManagerFactory.init(clientKeyStore, clientPw?.toCharArray())

" n7 H2 o& M4 a9 W2 q) O

sslContext.init(keyManagerFactory.keyManagers,trustManagerFactory.trustManagers, null)sslSocketFactory = sslContext.socketFactory

" B6 {+ R, M* j

} catch (e: Exception) {e.printStackTrace()}return sslSocketFactory}}fun getBuilder(showLog: Boolean): OkHttpClient.Builder {

& U7 G& S" z" R4 V s7 @: d* J

val builder = OkHttpClient.Builder()val sslSocketFactory = this.sslSocketFactoryif (sslSocketFactory != null) {

# n' t& a" H/ O! @

builder.sslSocketFactory(sslSocketFactory)}builder.hostnameVerifier { _, _ ->true}... // 此处省略 N 多 代码完美了,当然,hostnameVerifier 你也可以指定自己的域名啊。

) o5 H) H9 s9 I8 B# R e+ ]

遇到的天坑:在生成 ca server 以及 client 时 会 要求输入 CN 即:Common Name (e.g. server FQDN or YOUR name) 这行,请注意这三处虽然都是域名,但千万别设一样,否则会出现 400 The SSL certificate error

) N" B- q& y" y$ l, @ - d1 B% I& I$ J2 I2 a . |' g; N! L$ R' t8 V+ b* N ) q$ Q! y; b8 q j# R / I$ B! A) f& E; ^( ]! T% \) {+ ]

暂时无法加载帖子列表

回复

使用道具 举报

残阳 发表于 2026-01-05 12:57:05 | 显示全部楼层
这个思路很新颖,打开了新世界的大门,谢谢分享
回复

使用道具 举报

熊猫 发表于 2026-01-12 21:15:02 | 显示全部楼层
学习到了,之前一直没注意过这个点,受教了
回复

使用道具 举报

加菲猫 发表于 2026-01-17 04:21:24 | 显示全部楼层
楼主辛苦了,整理这么多内容,必须点赞收藏
回复

使用道具 举报

的的的 发表于 2026-03-14 15:04:27 | 显示全部楼层
完全赞同,我也是这么认为的,英雄所见略同~
回复

使用道具 举报

-小赫 发表于 2026-03-16 04:57:44 | 显示全部楼层
内容很干货,没有多余的废话,值得反复看
回复

使用道具 举报

qiuqian 发表于 2026-07-10 05:56:23 | 显示全部楼层
蹲了这么久,终于看到有价值的讨论,支持一下!
回复

使用道具 举报

三千尺电器001 发表于 2026-07-12 19:43:31 | 显示全部楼层
楼主太厉害了,整理得这么详细,必须支持
回复

使用道具 举报

vx13365383987 发表于 2026-07-14 06:38:13 | 显示全部楼层
刚好遇到类似问题,看完这个帖子心里有底了
回复

使用道具 举报

    您需要登录后才可以回帖 登录 | 加入怎通

    本版积分规则

    QQ|手机版|小黑屋|网站地图|真牛社区 ( 苏ICP备2023040716号-2 )

    GMT+8, 2026-10-1 06:34 , Processed in 0.038254 second(s), 25 queries , Gzip On.

    免责声明:本站信息来自互联网,本站不对其内容真实性负责,如有侵权等情况请联系420897364#qq.com(把#换成@)删除。

    Powered by Discuz! X3.5

    快速回复 返回顶部 返回列表