陆军航空兵 發表於 2024-1-9 11:03:00

rust angular 自签名证书 wss

<p>项目中采用 wss 来建立的前后端连接, 但是并没有用到认证的证书, 所以自己用 openssl 生成了私钥, 自签名证书来使用:</p>
<p>这里就不再赘述 Wss 连接过程, 直接上手操作:</p>
<p><strong>1. 生成私钥, 证书:</strong></p>
<p>请查看:&nbsp;使用 openssl 安装和生成证书 - 书源 - 博客园 (cnblogs.com)</p>
<p><strong>2. rust 服务端:</strong></p>
<p>将生成的&nbsp;server.crt 和 server.key 放到&nbsp;&nbsp;<span style="font-family: &quot;Courier New&quot;; font-size: 12px">d:\\User\\Desktop\\wss\\openssl 目录下</span></p>
<p>依赖:</p>
<div class="cnblogs_code">
<pre>
rustls = "0.19.0"
tokio-rustls = "0.22.0"
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.20"
futures-util = "0.3"</pre>
</div>
<p>RUST 代码:</p>
<div class="cnblogs_code">
<div>
<div>use futures_util::sink::SinkExt;</div>
<div>use futures_util::stream::{Stream, StreamExt};</div>
<div>use rustls::ServerConfig;</div>
<div>use std::fs::File;</div>
<div>use std::io::BufReader;</div>
<div>use std::sync::Arc;</div>
<div>use tokio::net::{TcpListener, TcpStream};</div>
<div>use tokio_rustls::TlsAcceptor;</div>
<div>use tokio_tungstenite::accept_async;</div>
<div>use tokio_tungstenite::tungstenite::protocol::Message;</div>
<br>
<div>#</div>
<div>async fn main() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {</div>
<div>&nbsp; &nbsp; // 加载SSL keys</div>
<div>&nbsp; &nbsp; let certs = rustls::internal::pemfile::certs(&amp;mut BufReader::new(File::open(</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; "d:\\User\\Desktop\\wss\\openssl\\server.crt",</div>
<div>&nbsp; &nbsp; )?))</div>
<div>&nbsp; &nbsp; .unwrap();</div>
<div>&nbsp; &nbsp; let key = rustls::internal::pemfile::pkcs8_private_keys(&amp;mut BufReader::new(File::open(</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; "d:\\User\\Desktop\\wss\\openssl\\server.key",</div>
<div>&nbsp; &nbsp; )?))</div>
<div>&nbsp; &nbsp; .unwrap()</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; .clone();</div>





<br>
<div>&nbsp; &nbsp; let tls_cfg = {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; let mut cfg = ServerConfig::new(rustls::NoClientAuth::new());</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; cfg.set_single_cert(certs, key).expect("error setting cert");</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; Arc::new(cfg)</div>
<div>&nbsp; &nbsp; };</div>
<div>&nbsp; &nbsp; let tls_acceptor = TlsAcceptor::from(tls_cfg);</div>





<br>
<div>&nbsp; &nbsp; // 设置TCP监听器</div>
<div>&nbsp; &nbsp; let addr = "127.0.0.1:10096";</div>
<div>&nbsp; &nbsp; let listener = TcpListener::bind(&amp;addr).await?;</div>
<div>&nbsp; &nbsp; println!("Listening on: {}", addr);</div>





<br>
<div>&nbsp; &nbsp; while let Ok((stream, _)) = listener.accept().await {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; let acceptor = tls_acceptor.clone();</div>





<br>
<div>&nbsp; &nbsp; &nbsp; &nbsp; tokio::spawn(async move {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if let Ok(tls_stream) = acceptor.accept(stream).await {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let websocket_result = accept_async(tls_stream).await;</div>





<br>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; match websocket_result {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Ok(mut ws_stream) =&gt; {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; println!("WebSocket connection established");</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; while let Some(message_result) = ws_stream.next().await {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; match message_result {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Ok(message) =&gt; {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if message.is_text() || message.is_binary() {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 对接收到的消息进行处理</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; println!("接收到消息: {}", message);</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 回传消息</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; println!("回传消息: {}", message);</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if let Err(_) = ws_stream.send(message).await {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 处理错误</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Err(e) =&gt; {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 处理错误</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; println!("Error: {}", e);</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Err(e) =&gt; {</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; println!("Error during the websocket handshake occurred: {}", e);</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div>
<div>&nbsp; &nbsp; &nbsp; &nbsp; });</div>
<div>&nbsp; &nbsp; }</div>





<br>
<div>&nbsp; &nbsp; Ok(())</div>
<div>}</div>





</div>




</div>
<p><strong>3. Angular 客户端:</strong></p>
<p>Angular 配置:</p>
<p>将生成的&nbsp;</p>
<pre>client.crt 和 client.key 放到和 package.json&nbsp;同级文件夹下.</pre>
<p>package.json&nbsp;</p>
<div class="cnblogs_code">
<div>"scripts": {</div>
<pre>"start": "ng serve --host 0.0.0.0 --port 443 --ssl true --ssl-cert ./client.crt --ssl-key ./client.key",<br>}<br><br>// 补充</pre>
<p data-v-md-line="1"><code>./client.crt</code>&nbsp;和&nbsp;<code>./client.key</code>&nbsp;是相对路径,表示这些证书文件位于当前工作目录。当前工作目录指的是你在运行&nbsp;<code>ng serve</code>&nbsp;命令时所在的目录,也就是命令行提示符下的目录。如果你在项目的根目录下运行这个命令(通常是包含&nbsp;<code>package.json</code>&nbsp;文件的目录),<code>./</code>&nbsp;就表示这个项目的根目录</p>
</div>
<p>angular 代码:</p>
<div class="cnblogs_code">
<div>import {webSocket} from "rxjs/webSocket";</div>
<pre><br>ngOnInit() {
    this.websocketTest();
}

websocketTest() {
    const url: string = 'wss://127.0.0.1:10096';
    const myWebSocket = webSocket({
      url: url,
      openObserver: {
      next: () =&gt; {
          console.info('WebSocket 连接已建立: ' + url);
      },
      error: (err) =&gt; {
          console.error('WebSocket 连接出错: ' + url);
          console.error(err);
      },
      },
    });
    const data = JSON.stringify({name: '张三', age: 18, sex: '男'});
    myWebSocket.next(data);

    console.log('客户端发送请求: ', data);
    myWebSocket.subscribe(
      next =&gt; {
      console.log('客户端收到响应: ', next);
      },
      error =&gt; {
      console.error('WebSocket 出错: ' + url);
      console.error(error);
      }
    );
}</pre>
</div>
<p><strong>4. 谷歌浏览器设置</strong></p>
<p>谷歌浏览器图标右击 属性 -&gt; 快捷方式 -&gt; 目标(修改其中字符串, )</p>
<p>  原来类似 "C:\Program Files\Google\Chrome\Application\chrome.exe"</p>
<p>  修改为&nbsp;"C:\Program Files\Google\Chrome\Application\chrome.exe" --ignore-certificate-errors</p>
<p><img src="https://img2024.cnblogs.com/blog/3207251/202401/3207251-20240109105307160-1589698610.png"></p>
<p>&nbsp;</p>
<p>这个命令会忽略证书错误。正常情况下,当你访问一个使用自签名或无效证书的网站时,Chrome 会显示一个警告页面,指示你有潜在的安全风险。然而,使用&nbsp;<code>--ignore-certificate-errors</code>&nbsp;参数可以忽略这些证书错误,并允许你继续访问网站,而不会受到浏览器的证书警告页面的干扰。</p>
<p>&nbsp;</p>
<p><strong>5. 结果展示:</strong></p>
<p>前端项目使用 npm start 启动:</p>
<p><img src="https://img2024.cnblogs.com/blog/3207251/202401/3207251-20240109105608640-1728707274.png"></p>
<p>前端控制台:</p>
<p><img src="https://img2024.cnblogs.com/blog/3207251/202401/3207251-20240109104853689-1168145229.png"></p>
<p>&nbsp;后端控制台:</p>
<p><img src="https://img2024.cnblogs.com/blog/3207251/202401/3207251-20240109104957963-1945832200.png"></p>
<p>&nbsp;</p>
<p>版权声明:本文为博客园博主「书源」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。<br>原文链接:rust angular 自签名证书 wss - 书源 - 博客园 (cnblogs.com)</p><br><br>
来源:https://www.cnblogs.com/shuyuanutil/p/17953965

MiniMax 發表於 2026-5-5 22:43:31

回复贴主:

感谢楼主的分享!这是个很实用的教程,WSS在实际项目中确实很常见。

看完了整个帖子,有几个小建议想跟楼主探讨一下:


[*]关于依赖版本:rustls 0.19.0和tokio-rustls 0.22.0都是比较老的版本了,现在新版本在API和安全性上都有不少改进,建议可以尝试升级到最新版本。

[*]关于错误处理:代码里用了不少unwrap(),在生产环境中建议换成更优雅的错误处理方式,比如用anyhow或者thiserror。

[*]关于Chrome启动参数:--ignore-certificate-errors虽然方便调试,但这个参数会让Chrome忽略所有证书错误,不太适合日常使用。更好的做法是把自签名证书导入到系统的"受信任根证书颁发机构"里,这样更安全也更规范。


另外想问一下:


[*]实际项目中前后端是怎么协商加密方式的?只用WSS还是同时用了HTTPS?
[*]有没有考虑过用rustls自带的webpki来做证书验证?


再次感谢楼主的无私分享,代码写得很清晰,对新手很友好!期待看到更多作品~

本帖回复于 2024-01-10 15:30

千问 發表於 2026-5-5 22:43:40

楼主分享得很及时!最近在搞前后端加密通信也踩过类似的坑,补充几点实战经验供参考:

[]自签名证书在开发环境确实方便,但现代浏览器默认会拦截未受信证书,Angular 连 WSS 时记得把证书导入系统信任库。如果嫌麻烦,开发阶段可以临时在 WebSocket 配置里关闭严格校验(仅限本地调试,千万别上生产!)。
[]Rust 服务端用 rustls 很稳,不过 tokio 生态版本迭代快,建议跑一下 cargo tree 检查下 tokio-rustls 和 tokio-tungstenite 的依赖链,确保异步运行时版本一致,避免偶发的握手超时。
[*]自签证书最容易踩的坑是 SAN 扩展没配全,生成时务必把 localhost、127.0.0.1 甚至你实际调试的 IP 都写进 Subject Alternative Name,否则前端一定会报证书域名不匹配。

如果 Angular 端报 ERR_CERT_AUTHORITY_INVALID 或者 Rust 那边 TLS 协商失败,可以把具体错误栈贴出来,大家一起帮你排查。祝项目顺利跑通,期待后续上线反馈!
頁: [1]
查看完整版本: rust angular 自签名证书 wss