HTTPS安全通信

这篇具有很好参考价值的文章主要介绍了HTTPS安全通信。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

HTTPS,TLS/SSL

Hyper Text Transfer Protocol over Secure Socket Layer,安全的超文本传输协议,网景公式设计了SSL(Secure Sockets Layer)协议用于对Http协议传输的数据进行加密,保证会话过程中的安全性。

使用TCP端口默认为443

TLS:(Transport Layer Security,传输层安全协议),用于两个应用程序之间提供保密性和数据完整性。

SSL:(Secure Socket Layer,安全套接字层),位于可靠的面向连接的网络层协议和应用层协议之间的一种协议层。SSL通过互相认证、使用数字签名确保完整性、使用加密确保私密性,以实现客户端和服务器之间的安全通讯。

SSL协议即用到了对称加密也用到了非对称加密(公钥加密),在建立传输链路时,SSL首先对对称加密的密钥使用公钥进行非对称加密,链路建立好之后,SSL对传输内容使用对称加密。

对称加密 速度高,可加密内容较大,用来加密会话过程中的消息

公钥加密 加密速度较慢,但能提供更好的身份认证技术,用来加密对称加密的密钥

HTTPS安全通信,Java Security,安全,网络,java,https

HTTPS单向认证

Https在建立Socket连接之前,需要进行握手,具体过程如下:

HTTPS安全通信,Java Security,安全,网络,java,https

 

  1. 客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息;

  2. 服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书;

  3. 客户端使用服务端返回的信息验证服务器的合法性,包括:

    • 证书是否过期;

    • 发行服务器证书的CA是否可靠;(通过查询浏览器或本机内的CA证书)

    • 返回的公钥是否能正确解开返回证书中的数字签名;(通过使用本机或浏览器内置的CA公钥进行解密)

    • 服务器证书上的域名是否和服务器的实际域名相匹配;

    • 验证通过后,将继续进行通信,否则,终止通信;

  4. 客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择;

  5. 服务器端在客户端提供的加密方案中选择加密程度最高的加密方式;

  6. 服务器将选择好的加密方案通过明文方式返回给客户端;

  7. 客户端接收到服务端返回的加密方式后,使用该加密方式生成产生随机码,用作通信过程中对称加密的密钥,使用服务端返回的公钥进行加密,将加密后的随机码发送至服务器;

  8. 服务器收到客户端返回的加密信息后,使用自己的私钥进行解密,获取对称加密密钥; 在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全;

HTTPS双向认证

双向认证和单向认证类似,它额外增加了服务端对客户端的认证:

HTTPS安全通信,Java Security,安全,网络,java,https

  1. 客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息;

  2. 服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书;

  3. 客户端使用服务端返回的信息验证服务器的合法性,包括:

    • 证书是否过期;

    • 发行服务器证书的CA是否可靠;(通过查询浏览器或本机内的CA证书)

    • 返回的公钥是否能正确解开返回证书中的数字签名;(通过使用本机或浏览器内置的CA公钥进行解密)

    • 服务器证书上的域名是否和服务器的实际域名相匹配;

    • 验证通过后,将继续进行通信,否则,终止通信;

  4. 服务端要求客户端发送客户端的证书即客户端证书公钥,客户端会将自己的证书发送至服务端;

  5. 验证客户端的证书,通过验证后,会获得客户端的公钥;

  6. 客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择

  7. 服务器端在客户端提供的加密方案中选择加密程度最高的加密方式;

  8. 将加密方案通过使用之前获取到的公钥进行加密,返回给客户端

  9. 客户端收到服务端返回的加密方案密文后,使用自己的私钥进行解密,获取具体加密方式,而后,产生该加密方式的随机码,用作加密过程中的密钥,使用之前从服务端证书中获取到的公钥进行加密后,发送给服务端;

  10. 服务端收到客户端发送的消息后,使用自己的私钥进行解密,获取对称加密的密钥,在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全;

 

java.https加载证书的API

一般的证书加载过程

  • 用Certificate、KeyStore生成创建KeyManagerFactory和TrustManagerFactory

  • KeyManagerFactory和TrustManagerFactory用来创建KeyManager和TrustManager

  • 而KeyManager和TrustManager用来初始化SSLContext

  • 然后使用SSLContext,创建实际实现SSL/TLS协议的对象(SSLSocketFactory、SSLSocket或者SSLEngine)

  • SSLSocket和SSLEngine可以直接在通信对象中使用

  • KeyManager和TrustManager作用:

    • KeyManager负责向对等端显示使用的凭证(使用的密码标准、加密算法、证书、公钥、签名等)

    • TrustManager负责验证从对等端收到的凭证,验证凭证有多种方式:其中之一是创建CertPath对象,并让JDK的内置公钥基础结构(PKI)框架处理验证。 在内部,CertPath实现可能会创建一个Signature对象,并使用它来验证证书链中的每个签名

  • 示例:生成SSLContext,并使用SSLContext初始化apache-httpClient

public static String postWithSSL(String url, String jsonBody) throws Exception {
    SSLContext sslContext = getSslContext();
    SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
            sslContext, new String[]{"TLSv1.2", "TLSv1.1", "TLSv1"}, null,
            SSLConnectionSocketFactory.getDefaultHostnameVerifier());
    RequestConfig config = RequestConfig.custom()
            .setConnectTimeout(3000)
            .setSocketTimeout(3000)
            .build();
    CloseableHttpClient client = HttpClients.custom()
            .setSSLSocketFactory(sslConnectionSocketFactory)
            .setDefaultRequestConfig(config).build();
    HttpPost httpPost = new HttpPost(url);
    //httpPost.setHeaders(headers);
    httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
    httpPost.setHeader("Accept", "application/json");
    httpPost.setEntity(new StringEntity(jsonBody, StandardCharsets.UTF_8));
    HttpResponse response = client.execute(httpPost);
    HttpEntity responseEntity = response.getEntity();
    String result = EntityUtils.toString(responseEntity, "UTF-8");
    return result;
}
//双向加密 SSLContext
private static SSLContext getSslContext() throws Exception {
    //自身私钥
    KeyStore identityKeyStore = KeyStore.getInstance("jks");
    FileInputStream identityKeyStoreFile = new FileInputStream("/root/myServer.jks");
    identityKeyStore.load(identityKeyStoreFile, "password1".toCharArray());
    //服务端信任证书
    KeyStore trustKeyStore = KeyStore.getInstance("jks");
    FileInputStream trustKeyStoreFile = new FileInputStream("/root/trustKeyStore.jks");
    trustKeyStore.load(trustKeyStoreFile, "password".toCharArray());
    //构建SSLContexts
    return SSLContexts.custom()
            .loadKeyMaterial(identityKeyStore, "password1".toCharArray()) // load identity keystore
            .loadTrustMaterial(trustKeyStore, null) // load trust keystore
            .build();
}
//双向加密 SSLContext 方式二
private static SSLContext getSslContext2() throws Exception{
    //自身私钥
    KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    KeyStore keystore = KeyStore.getInstance("jks");
    keystore.load(new FileInputStream(new File("/root/myServer.jks")), "password".toCharArray());
    keyFactory.init(keystore, "password".toCharArray());
    KeyManager[] keyManagers = keyFactory.getKeyManagers();
    //服务端信任证书
    TrustManagerFactory trustFactory = TrustManagerFactory.getInstance("SunX509");
    KeyStore tsStore = KeyStore.getInstance("jks");
    tsStore.load(new FileInputStream(new File("/root/trustKeyStore.jks")), "password".toCharArray());
    trustFactory.init(tsStore);
    TrustManager[] trustManagers = trustFactory.getTrustManagers();
    //初始化SSLContext
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(keyManagers, trustManagers, null);
    return sslContext;
}

 

Java SSL 客户端和服务器示例

演示如何建立安全的 SSL 连接

案例一

服务器不校验客户端证书

SSL 服务器示例:
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;

public class SSLServerExample {
    public static void main(String[] args) {
        int port = 8888; // 监听的端口

        try {
            // 创建 SSL 上下文
            SSLContext sslContext = SSLContext.getInstance("TLS");

            // 初始化 SSL 上下文,使用自签名证书
            KeyStore keyStore = KeyStore.getInstance("JKS");
            InputStream keystoreInputStream = SSLServerExample.class.getResourceAsStream("server_keystore.jks");
            char[] password = "password".toCharArray();
            keyStore.load(keystoreInputStream, password);

            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, password);

            sslContext.init(keyManagerFactory.getKeyManagers(), null, null);

            // 创建 SSLServerSocket
            SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
            SSLServerSocket serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);

            System.out.println("SSL Server is running...");

            // 监听并处理连接
            while (true) {
                SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
                System.out.println("Client connected: " + clientSocket.getInetAddress());

                // 在这里处理客户端的输入输出流
                // InputStream inputStream = clientSocket.getInputStream();
                // OutputStream outputStream = clientSocket.getOutputStream();

                // 关闭客户端连接
                // clientSocket.close();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
SSL 客户端示例:

当你在客户端建立 SSL 连接时,一个关键的步骤是验证服务器的证书,以确保你正在与预期的服务器进行通信,而不是受到中间人攻击。通常情况下,客户端会验证服务器证书的合法性,包括证书是否由受信任的证书颁发机构签发,以及证书是否与服务器域名匹配。

不校验服务器证书
import javax.net.ssl.*;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;

public class SSLClientExample {
    public static void main(String[] args) {
        String serverAddress = "localhost"; // 服务器地址
        int serverPort = 8888; // 服务器端口

        try {
            // 创建 SSL 上下文
            SSLContext sslContext = SSLContext.getInstance("TLS");

            // 初始化 SSL 上下文,信任所有证书
            sslContext.init(null, new TrustManager[]{new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                }
                public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                }
            }}, new java.security.SecureRandom());

            // 创建 SSLSocket
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
            SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(serverAddress, serverPort);

            System.out.println("Connected to server...");

            // 在这里处理输入输出流
            // InputStream inputStream = socket.getInputStream();
            // OutputStream outputStream = socket.getOutputStream();

            // 关闭连接
            // socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在示例代码中,这一部分的目的是创建一个虚拟的 TrustManager,它会接受任何服务器证书,无论它是否由受信任的证书颁发机构签发。这是通过实现 X509TrustManager 接口的方法来实现的,但这些方法内部什么也不做,从而绕过了证书验证。

这种做法在测试环境中可能会有一些用途,但在实际生产环境中,强烈建议对服务器证书进行严格的验证,以确保与服务器的通信是安全的。如果不验证服务器证书,那么就容易受到中间人攻击,攻击者可以劫持通信并获取敏感信息。

验证服务器证书
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

public class MyTrustManager implements X509TrustManager {
    @Override
    public void checkClientTrusted(X509Certificate[] certs, String authType) {
        // 客户端证书验证逻辑,如果需要客户端证书验证
    }

    @Override
    public void checkServerTrusted(X509Certificate[] certs, String authType) throws javax.security.cert.CertificateException {
        // 服务器证书验证逻辑
        for (X509Certificate cert : certs) {
            try {
                cert.checkValidity(); // 检查证书是否有效
                // 在此处可以添加更多验证逻辑,例如是否由受信任的证书颁发机构签发,与预期的服务器域名匹配等
            } catch (Exception e) {
                throw new javax.security.cert.CertificateException("Server certificate is not valid.", e);
            }
        }
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}

然后,在客户端代码中,将 MyTrustManager 实例传递给 SSLContext 的初始化方法:

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new MyTrustManager()}, new java.security.SecureRandom());

案例二

SSL 服务器端示例,验证客户端证书:
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;

public class SSLServerWithClientCertificateExample {
    public static void main(String[] args) {
        int port = 8888; // 监听的端口

        try {
            // 创建 SSL 上下文
            SSLContext sslContext = SSLContext.getInstance("TLS");

            // 初始化 SSL 上下文,使用服务器证书和私钥
            KeyStore keyStore = KeyStore.getInstance("jks");
            InputStream keystoreInputStream = SSLServerWithClientCertificateExample.class.getResourceAsStream("server_keystore.jks");
            char[] password = "password".toCharArray();
            keyStore.load(keystoreInputStream, password);

            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, password);

            sslContext.init(keyManagerFactory.getKeyManagers(), null, null);

            // 创建 SSLServerSocket
            SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
            SSLServerSocket serverSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);

            System.out.println("SSL Server is running...");

            // 监听并处理连接
            while (true) {
                SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
                System.out.println("Client connected: " + clientSocket.getInetAddress());

                // 客户端证书验证
                clientSocket.startHandshake(); // 触发 SSL 握手,验证客户端证书
                System.out.println("Client certificate validated.");

                // 在这里处理客户端的输入输出流
                // InputStream inputStream = clientSocket.getInputStream();
                // OutputStream outputStream = clientSocket.getOutputStream();

                // 关闭客户端连接
                // clientSocket.close();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们使用了客户端发送的证书来进行客户端的认证。服务器端需要配置自己的证书和私钥(使用 server_keystore.jks 文件),以便进行安全的通信。在客户端握手之前,服务器会触发客户端证书验证,确保只有持有有效证书的客户端能够连接。

客户端证书验证通常包括以下几个步骤:

  1. 握手阶段触发验证: 在 SSL 握手阶段,当服务器请求客户端证书时,客户端会在 ClientKeyExchange 消息中发送其证书。服务器通过触发 startHandshake() 方法来启动握手,从而触发客户端证书的验证。

  2. 证书链验证: 服务器会验证客户端证书的整个证书链,从客户端证书本身开始,逐级验证其上层证书,直到根证书(通常是 CA 证书)。验证的过程包括检查每个证书的有效性、颁发机构的合法性等。

  3. 证书有效性检查: 服务器会检查客户端证书的有效期,确保证书在其有效期内。如果证书过期,验证会失败。

  4. 颁发机构检查: 服务器会检查颁发客户端证书的证书颁发机构(CA),以确保 CA 受信任且有效。

  5. 主题匹配: 服务器会检查证书的主题信息(通常包括域名等)是否与服务器期望的客户端匹配。这有助于防止使用错误证书的客户端连接。

如果在客户端证书验证过程中任何一个步骤失败,服务器会拒绝客户端连接或在一些情况下终止连接。这样,服务器就确保了只有合法的客户端才能与之通信。

在实际代码中,当服务器调用 startHandshake() 方法时,它会自动处理证书的验证过程,你不需要在代码中显式编写验证逻辑。服务器端的示例代码中的以下行触发了客户端证书验证:

clientSocket.startHandshake(); // 触发 SSL 握手,验证客户端证书

这将会启动握手过程,并在握手中验证客户端的证书。如果证书验证失败,startHandshake() 方法会抛出相应的异常。

客户端使用一个 KeyStore 文件来配置信任的服务器证书,并且向服务器发送客户端证书以进行客户端认证
import javax.net.ssl.SSLContext;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
import java.security.KeyStore;

public class SSLClientWithCertificateExample {
    public static void main(String[] args) {
        try {
            SSLContext sslContext = SSLContext.getInstance("TLS");

            // 初始化 SSLContext,配置客户端证书和用于服务器证书验证的 TrustManager
            KeyManager[] keyManagers = getKeyManagers();
            TrustManager[] trustManagers = getTrustManagers();
            sslContext.init(keyManagers, trustManagers, new java.security.SecureRandom());

            // 使用 sslContext 创建 SSLSocket 或者 HttpClient 等
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static KeyManager[] getKeyManagers() throws Exception {
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        KeyStore ks = KeyStore.getInstance("jks");
        ks.load(new FileInputStream(new File("/root/clientKeyStore.jks")), "password".toCharArray());
        keyManagerFactory.init(ks, "password".toCharArray());
        return keyManagerFactory.getKeyManagers();
    }

    private static TrustManager[] getTrustManagers() throws Exception {
        TrustManagerFactory trustFactory = TrustManagerFactory.getInstance("SunX509");
        KeyStore tsStore = KeyStore.getInstance("jks");
        tsStore.load(new FileInputStream(new File("/root/trustKeyStore.jks")), "password".toCharArray());
        trustFactory.init(tsStore);
        return trustFactory.getTrustManagers();
    }
}

参考:

Java Platform SE 8

一文看懂HTTPS、证书机构(CA)、证书、数字签名、私钥、公钥 - 简书

基础篇:java.security框架之签名、加密、摘要及证书文章来源地址https://www.toymoban.com/news/detail-637115.html

到了这里,关于HTTPS安全通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • linux【网络编程】之HTTPS协议,一文了解HTTPS是如何保证通信安全的

    在上篇文章中我们了解到什么事HTTP协议,HTTP协议内容都是按照⽂本的⽅式明⽂传输的.这就导致在传输过程中出现⼀些被篡改的情况,本期我们来探讨一下HTTPS协议。 HTTPS( 超文本传输安全协议 )也是⼀个应⽤层协议.是在HTTP协议的基础上引⼊了⼀个加密层. HTTPS:默认端口与

    2024年02月08日
    浏览(61)
  • 前端知识(七)———HTTPS:保护网络通信安全的关键

    当谈到网络通信和数据传输时,安全性是一个至关重要的问题。在互联网上,有许多敏感信息需要通过网络进行传输,例如个人身份信息、银行账户信息和商业机密等。为了保护这些信息不被未经授权的人访问和篡改,HTTPS(超文本传输安全协议)应运而生。 HTTPS是HTTP协议的

    2024年02月04日
    浏览(54)
  • [Java网络安全系列面试题] HTTP和HTTPS协议区别和联系都有哪些?

    2.1 HTTP特点 1.支持客户/服务器模式。( C/S 模式) 2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有 GET 、 HEAD 、 POST 。每种方法规定了客户与服务器联系的类型不同。由于 HTTP 协议简单,使得 HTTP 服务器的程序规模小,因而通信速度很快。

    2024年04月23日
    浏览(49)
  • Java中的HTTPS通信

    在Java中实现HTTPS通信,主要涉及到SSL/TLS协议的使用,用于提供数据传输的安全性。下面我们将深入探讨如何使用Java进行HTTPS通信。 一、基本概念 HTTPS,全称为Hypertext Transfer Protocol Secure,是HTTP的安全版本。它使用SSL/TLS协议对传输的数据进行加密,确保数据在传输过程中的安

    2024年01月25日
    浏览(36)
  • 前端知识笔记(三十八)———HTTPS:保护网络通信安全的关键

    当谈到网络通信和数据传输时,安全性是一个至关重要的问题。在互联网上,有许多敏感信息需要通过网络进行传输,例如个人身份信息、银行账户信息和商业机密等。为了保护这些信息不被未经授权的人访问和篡改,HTTPS(超文本传输安全协议)应运而生。 HTTPS是HTTP协议的

    2024年02月03日
    浏览(53)
  • 关于网络通信安全协议的一些知识(ssl,tls,CA,https)

    首先了解一下http协议的变迁。 http1.0默认短连接,1.1默认长连接并且可以管道传输,但是存在队头阻塞问题; https就是在tcp和http之间加了SSL/TLS层。 http2也是安全的,改进是hpack二进制和编码压缩减小体积,stream没有队头阻塞了(TCP层还有),以及服务器主动推送功能; http

    2024年02月15日
    浏览(56)
  • 常见网络通信协议(http、https、ws)及安全协议(SSL、TLS、XTLS)

    文章内容删除了一大半不合适的内容,发不出来,你懂得。🥰 HTTP和HTTPS都属于 应用层协议 ,它们都是用于从万维网(WWW)服务器传输超文本到本地浏览器的传送协议。它们都是 基于 TCP/IP 协议 来传递数据的,支持 客户端-服务器模式 的通信。 HTTP和HTTPS的区别主要在于HTT

    2024年02月10日
    浏览(51)
  • java https安全传输

    HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全 import java.io.*; import java.net.*; import java.security.*; import java.security.cert.*; import javax.net.ssl.*; public class HttpsURLConnectionTest { private String url = “https://127.0.0.1:8080/TestWeb/version”; private myX509TrustManager

    2024年04月16日
    浏览(41)
  • Java安全——SSL和HTTPS

    SSL和HTTPS SSL提供了在TCP套接字之上的对数据进行加密的方法,也是HTTPS协议的基础 利用JSSE(java安全套接字扩展包)可以像处理协议一样创建和使用SSL套接字,从而支持HTTPS协议 SSL和tcp套接字之间的紧密关系,本身并不是一个加密引擎,但确是Internet上广泛使用的加密技术。 SS

    2024年02月15日
    浏览(55)
  • Java feign使用okhttp跳过https安全校验

    今天调用微软Azure OpenAI时报错了,百度一番发现需要https证书,后来想能不能跳过校验呢,费心良苦实验一番终于实现 代码如下 OK,齐活~

    2024年02月16日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包