Java HTTPS Server Actual Warfare
Preface
I have a bad memory. I forget a lot of things if I don't look back for 2 - 3 months.
This article is the latest tidying up of SSL and HTTPS related knowledge points, worry about time and forget, I hereby record.
I also hope that I can help my friends who are learning relevant knowledge. Although the article is long, the code is annotated in detail. The project involves Java multithreading, abstraction, inheritance, interface, polymorphism, generics, network programming, HTTP, HTTPS, SSL and so on. Because of the time relationship, there are no applications: thread pool, NIO, anti-SSL. Shoot, etc.
preview
Let's see the effect first. It's not very good to post the code on the first place. Maybe you don't know what to do! __________
The following Html page simulates the dynamically generated page, which is the HTTP protocol server we will implement next.
Yes, we are not using third-party servers such as tomcat or nginx to implement the response, but the code needs to be implemented by ourselves later.
-
Browser Input https://www.tldiw.com/login.html We can see the following page.
-
The IDEA console prints the HTTP protocol header, which is the GET request header sent to us by the browser.
-
Click on the lock icon in front of the browser address bar https to view the certificate information. It shows that the connection is secure and the data transmitted is encrypted.
HTTPS Protocol
Introduction to SSL
SSL(Secure Sockets Layer Secure Socket Layer) and its successor Transport Layer Security (TLS) are security protocols that provide security and data integrity for network communications. TLS and SSL encrypt network connections at the transport layer.
Secure Socket Layer, developed for Netscape, guarantees the security of data transmission over the Internet. Encryption technology ensures that data will not be intercepted and eavesdropped during transmission over the network. The general safety standard is 40 bit s, while the United States has introduced 128 bit s higher safety standards, but restrictions on exit. Only I.E. or Netscape browsers with versions above 3.0 can support SSL.
The current version is 3.0. It has been widely used to authenticate identity and encrypt data transmission between Web browser and server.
SSL protocol is located between TCP/IP protocol and various application layer protocols, providing security support for data communication. The SSL protocol can be divided into two layers: the SSL Record Protocol, which is based on reliable transport protocols (such as TCP) and provides basic functions such as data encapsulation, compression and encryption for high-level protocols. SSL Handshake Protocol: It is based on the SSL record protocol. It is used for authentication, negotiation of encryption algorithm, exchange of encryption keys and so on before the actual data transmission begins.
HTTPS Principle:
- The client initiates the SSL communication to the server. The message contains the specified version of the SSL supported by the client, the list of encryption components, the encryption algorithm used and the length of the key.
- Server Response Client: Message Information, Server Public Key Certificate, SSL Version and Encryption Component (Server Encryption Component is filtered from Client Provided List).
- The client gets the server's public key certificate and verifies whether the certificate is trusted or expired.
- If the client successfully verifies the public key certificate, it will generate a random string, that is, the primary key. This random string is actually the symmetric key to be used for subsequent communication. The client uses the server public key to encrypt the random string, and then sends it to the server, which will be used for subsequent data. Numbers are symmetrically encrypted.
- The client calculates the handshake message with the agreed Hash algorithm, then encrypts it with the generated master key and sends it to the server together.
- The server receives the random number sent by the client, decrypts and restores the random number to plaintext with its own private key. The server obtains the master secret key and parses the handshake message with the master secret key to verify whether the Hash value is the same as that sent by the client. If consistent, the client is notified that the SSL handshake is successful.
- When the SSL handshake is successful, the data can be sent officially, and then the data will be symmetrically encrypted using the master secret key.
Symmetric encryption
- Symmetric encryption, also known as shared key encryption, uses the same key for encryption and decryption.
- Symmetrical encryption has high efficiency and is suitable for encrypting large amounts of data.
- Because encryption and decryption use the same secret key, once the secret key is leaked, it loses its encryption meaning, which is not conducive to transmission. It is only suitable for the scenario where the encryption and decryption parties keep the secret key.
Asymmetric encryption
- Asymmetric encryption is also known as public key encryption. Asymmetric encryption has a pair of secret keys: public key and private key. Public key is used for encryption and private key is used for decryption.
- Asymmetric encryption is inefficient and unsuitable for large amount of encrypted data.
- Client encrypts data with public key, server decrypts data with private key. When someone intercepts the data in the process of communication, he can not understand it unless he has private key to decrypt the data. Therefore, the public key can be transmitted without fear of public key leakage.
Combination of Symmetric Encryption and Asymmetric Encryption
We know that symmetric encryption is efficient and asymmetric encryption is inefficient. Symmetric key of symmetric encryption is not conducive to transmission. If it is intercepted by a third party, it will lose the meaning of encryption. In the face of the large amount of data encryption scenario, it is too slow to unilaterally use asymmetric encryption. Is there a way to ensure the efficiency of encryption and solve the secret? What about the security of key transfer?
At this time, smart people always have a way to use symmetric encryption and asymmetric encryption together to solve the problem.
Basic ideas:
- Client obtains asymmetric encryption public key from server
- The client generates the symmetric encryption key, which is encrypted and sent to the server using the asymmetric encryption public key on the server side.
- The server uses asymmetric encryption private key to decrypt symmetric encryption secret key.
- If there is a leak in the process of transferring symmetric secret keys, it's okay, because symmetric secret keys are encrypted by asymmetric encryption public key, only one party with asymmetric encryption private key can decrypt them.
- The real symmetric encryption key is known only by the server and client, and then the encrypted data is encrypted and decrypted using the symmetric key.
SSL certificate
SSL certificate is a kind of digital certificate, similar to electronic copies of driver's license, passport and business license. Because it is configured on the server, also known as the SSL server certificate.
SSL certificate is to abide by the SSL protocol, which is issued by the trusted digital certificate authority CA after verifying the identity of the server. It has the function of server authentication and data transmission encryption.
SSL certificate is designed and developed by Netscape Communication Corporation by establishing a Secure socket layer(SSL) security protocol between client browser and Web server. This security protocol is mainly used to provide authentication for users and servers, encrypt and hide the transmitted data, and ensure that the data is not changed in transmission, that is, data integrity, has become a global standard in this field. Since the technology of SSL has been established in all major browsers and WEB server programs, the function can be activated only by installing server certificates. That is to say, it can activate the SSL protocol, realize the encrypted transmission of data information between client and server, prevent the leakage of data information, and ensure the dual function. The user can verify the authenticity and reliability of the website he visits through the server certificate. Digital signature, also known as digital identification and signature (Digital ID), provides a method of online authentication. It is a digital information file used to mark and prove the identity of both parties in network communication. The concept of digital signature is similar to driver's license or ID card in daily life. Digital signature is mainly used for sending secure e-mail, visiting secure sites, inviting tenders and bidding on the Internet, signing contracts on the Internet, ordering on the Internet, safe transmission of official documents on the Internet, office work on the Internet, payment on the Internet, tax payment on the Internet and online shopping.
SSL Certificate Type
The main file types of SSL certificates are PEM, DER, PFX, JKS, KDB, CER, KEY, CSR, CRT, CRL, OCSP, SCEP, etc.
PEM: Text format. Certificates can be saved and private keys can be saved.
DER. CER: The file is in binary format. It only saves certificates, not private keys.
PFX.P12: Binary format, which contains both certificates and private keys, is generally password protected.
CRT: It can be in binary format or text format. It is the same as. DER format and does not save the private key.
JKS: Binary format, which contains both certificates and private keys and is generally password protected.
Key: Text format, save private key
PEM
Abbreviation for Privacy Enhanced Mail, stored as text. Open and see the text format to "-- BEGIN..." At the beginning, "- END..." At the end, the content is BASE64 encoding. See PEM format certificate information: OpenSSL x509-in certificate. pem-text-noout Apache and * NIX servers prefer to use this encoding format.
DER
The abbreviation of Distinguished Encoding Rules is stored in binary mode. The file structure can not be previewed directly. The information of DER certificates can be viewed: OpenSSL x509-in certificate.der-inform der-text-noout Java and Windows servers prefer to use this encoding format.
CER
Certificate, or certificate, is common in Windows systems. Similarly, it may be PEM coding or DER coding, most of which should be DER coding.
PFX/P12
Preecessor of PKCS#12. For Unix servers, CRT and KEY are stored separately in different files, but Windows IIS stores them in a PFX file (so this file contains certificates and private keys). Would this be unsafe? Should not, PFX usually has a "extract password". If you want to read out the contents, it requires you to provide the extract password.
CRT
Certificate is short for PEM or DER. See the first two formats for how to view them.
JKS
Java Key Storage, which is a patent of Java, has little to do with OpenSSL. Using a tool called "keytool" in Java, PFX can be converted to JKS. Of course, keytool can also directly generate JKS.
Free Application for SSL Certificate
-
Log on to the Aliyun console
-
Enter the SSL Certificate (Application Security)
-
Choose Free Certificate
-
Buy immediately
-
Enter the Certificate Console
-
Click Apply
-
Fill in the domain name, request information, click on the next step and wait for 5 minutes for approval.
-
Download Certificate
- Decompression Certificate
Certificate format explanation:
- 2611049_www.tldiw.com.pfx certificate contains the basic information of certificate, such as domain name, certificate duration and issuing institution. It also contains two main parts: public key and private key, which are included in 2611049_www.tldiw.com.pfx file. It needs password to obtain private key from 261109_www.tldiw.com.pfx file. This is the string in the pfx-password.txt file.
DNS parsing
Test domain name: www.tldiw.com
Test host: 192.168.1.102
Add parse records:
JSSE
Java provides the implementation of JSSE-Java Secure Socket Extension for SSL and TLS. It contains a set of packages to implement secure communication over the Internet. Through it, the functions of data encryption, server authentication and information integrity can be transparently provided. The use of Secure Sockets can be greatly reduced just like the use of ordinary sockets. The burden of developers is reduced, so that developers can easily integrate the SSL protocol into the program, and JSSE can minimize security risks.
Using Java Secure Socket Flow Chart:
- Load the key library to initialize the key manager factory and the trust manager factory
- Creating an SSL context using secret key manager and trust manager and secure random numbers
- Server Secure Socket Factory, Secure Socket Factory, and SSL Session Context are available from the SSL context
- Server Secure Sockets Factory allows you to create server Secure Sockets
- Secure socket factory allows you to create secure sockets
Subject structure:
As we can see from the figure above, it is obvious that JSSE adopts factory design pattern:
What is the factory design pattern?
Factory pattern is one of the most commonly used design patterns in Java. This type of design pattern belongs to the creation pattern, which provides the best way to create objects.
In factory mode, we do not expose the creation logic to the client when creating objects, and point to newly created objects by using a common interface.
It is easy to understand that the creation of objects is unified to a method to achieve, this method can be called: factory method, factory method is divided into: static factory method and non-static factory method, when we need objects directly call factory method can be obtained.
//Creating objects in normal mode KeyManager keyManager = new KeyManager();
//Factory pattern creation object public class KeyManagerFactory{ /** *Creating KeyManager Object Factory Method */ public static KeyManager getKeyManager(){ KeyManager keyManager = new KeyManager(); return keyManager ; } }
Key Manager Factory
Trust Manager Factory
Server Secure Socket Factory-SSLServer Socket Factory-SSLServer Socket
Secure Socket Factory-SSLSocket Factory-SSLSocket
KeyManager Key Manager
The key manager manages the key, which is used to verify the local key to the peer. If no key is available, the socket will not be able to provide authentication credentials. A key manager is created by using a key manager factory or by implementing one of the key manager subclasses.
TrustManager Trust Manager
The trust manager manages the trust certificates used in making trust decisions and decides whether to accept the credentials submitted by the peers. Trust managers are created by using a trust manager factory or by implementing one of the trust manager subclasses.
SSL Server Socket Server Secure Socket
This class extends ServerSocket and provides secure server sockets using protocols such as Secure Socket Layer (SSL) or Transport Layer Security (TLS) protocols. This type of instance is usually created using the SSL Server Socket Factory. The main function of the SSL Server Socket is to create an SSL Socket by accepting connections. The SSL Server Socket contains several state data inherited by the SSL Socket when creating a socket. These include enabled password suites and protocols, whether client authentication is required, and whether the socket created should start shaking hands in client or server mode. You can override the status inherited by the created SLSocket by calling the appropriate method.
SSLSocket Server Secure Socket
This class extends sockets and provides secure sockets using protocols such as Secure Sockets Layer (SSL) or IETF Transport Layer Security (TLS).
These sockets are common stream sockets, but they add a layer of security protection to the underlying network transmission protocols, such as TCP. These protective measures include:
Integrity protection. SSL prevents active eavesdroppers from modifying messages.
Authentication. In most modes, SSL provides peer-to-peer authentication. The server is usually authenticated, and the client can authenticate according to the request of the server.
Confidentiality (privacy protection). In most modes, SSL encrypts data sent between client and server. This protects the confidentiality of data, so passive eavesdroppers do not see sensitive data, such as financial information or personal information.
SSLContext
Examples of this class represent the implementation of a secure socket protocol, which acts as a factory for secure sockets or for SSL Engines. This class is initialized using an optional set of keys and trust managers as well as secure random byte sources.
Maven Project
Project code: Java HTTPS Server Actual Warfare (ssl-server)
Project structure:
Bootstrap.java
package com.tldiw.server.bootstrap; import com.tldiw.server.core.Server; import com.tldiw.server.factory.ServerFactory; import javax.net.ssl.SSLSocket; /** * Server bootstrapper */ public class Bootstrap { public static void main(String[] args) { //Accessing an SSL server through a server factory Server<SSLSocket> server = ServerFactory.httpsServer(); //Start the server server.startup(); } }
HttpsServer.java
package com.tldiw.server.core; import javax.net.ssl.SSLSocket; import java.io.InputStream; import java.io.OutputStream; /** * Https The server * Certificates are placed in the resources directory */ public class HttpsServer extends SSLServer { @Override protected String certificatePassword() { //Certificate Password return "ooWKM33W"; } @Override protected InputStream certificateStream() { //Loading Certificate return getClass().getClassLoader().getResourceAsStream("2611049_www.tldiw.com.pfx"); } @Override public void handler(SSLSocket socket) throws Exception { byte[] bytes = new byte[1024]; //Get the input stream InputStream inputStream = socket.getInputStream(); //Read 1024 bytes int read = inputStream.read(bytes); //End method if no data is read if (read == -1) return; //Print HTTP request header System.out.println(new String(bytes, 0, read)); //Get the output stream OutputStream outputStream = socket.getOutputStream(); //Input corresponding information outputStream.write(responseContext().response()); //Close the input stream inputStream.close(); //Close the output stream outputStream.close(); //Close closeSocket(socket); } }
Server.java
package com.tldiw.server.core; import com.tldiw.server.response.ResponseContext; import java.io.IOException; import java.net.Socket; /** * The server abstract class handles client requests and responses * * @param <S> Generics can be either ordinary sockets or secure sockets */ public abstract class Server<S extends Socket> { /** * Server port default 80 * * @return port */ public int port() { return 80; } /** * Enter listening for the next socket connection * * @return Default value:true */ public boolean nextConnection() { return true; } /** * Close * * @param socket socket */ protected void closeSocket(Socket socket) { if (socket == null) return; try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } /** * Start the server * * @return Start successfully returns true, fail to return false */ public abstract boolean startup(); /** * Processing socket abstraction socket is equal to flow channel < br/>. * Get input stream through socket to read data sent by client to server < br/>. * Get output stream write data through socket to client < br/>. * Close the input/output stream and socket to end this request and response service * * @param socket socket * @throws Exception Throw out any errors that may occur during the processing of sockets, such as: a sudden socket interrupt that renders the input and output streams unavailable */ public abstract void handler(S socket) throws Exception; /** * Response context, usually encapsulating response content * * @return ResponseContext */ public abstract ResponseContext responseContext(); }
SSLServer.java
package com.tldiw.server.core; import com.tldiw.server.factory.ResponseContextFactory; import com.tldiw.server.response.ResponseContext; import javax.net.ssl.*; import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; /** * SSL The server abstract class inherits from the abstract server, which handles client requests and responses <br/>. * Define an abstract method for obtaining certificate cryptographic certificate stream */ public abstract class SSLServer extends Server<SSLSocket> { /** * Get the certificate password * * @return Certificate Password */ protected abstract String certificatePassword(); /** * Acquire certificate flow <br/>. * You can read files or networks, etc. * * @return certificate */ protected abstract InputStream certificateStream(); /** * SSL Server Port * * @return 443 * 1 */ @Override public int port() { return 443; } /** * Get an instance of the SSL context <br/> * There are approximately three processes for constructing an instance of an SSL context: <br/> * 1.Load the secret key library < br/>. * 2.Initialize the Key Manager Factory <br/> * 3.Initialize the SSL context <br/>. * * @return SSLContext * @throws Exception Throws errors that may occur during the construction of an SSL context, such as certificate password errors that do not exist, etc. */ protected SSLContext sslContext() throws Exception { //Get an example of PKCS12 secret key library, PKCS12 document: https://baike.baidu.com/item/PKCS/1042350?fr=aladdin KeyStore keyStore = KeyStore.getInstance("PKCS12"); //Certificate Password char[] certificatePassword = certificatePassword().toCharArray(); //Loading certificates to cryptographic libraries using passwords keyStore.load(certificateStream(), certificatePassword); //Get an instance of a key manager factory KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); //Initialization of Key Manager Factory with Key Library and Certificate Password keyManagerFactory.init(keyStore, certificatePassword); //Get an instance of the SSL context SSLContext sslContext = SSLContext.getInstance("SSL"); //Initialize the SSL context sslContext.init(keyManagerFactory.getKeyManagers(), null, null); return sslContext; } /** * Get the server security socket instance <br/> * There are about three processes to construct server secure sockets: <br/>. * 1.Construct the SSL context <br/>. * 2.Get the server secure socket factory <br/> * 3.Create a binding server socket <br/> * * @return SSLServerSocket * @throws Exception Throws errors that may occur during the construction of server security sockets, such as errors in the construction of SSL context objects or the occupation of binding ports, etc. */ protected SSLServerSocket sslServerSocket() throws Exception { //SSL context SSLContext sslContext = sslContext(); //Get the server secure socket factory SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory(); //Create a server security socket on the specified port return (SSLServerSocket) serverSocketFactory.createServerSocket(port()); } @Override public boolean startup() { final SSLServerSocket sslServerSocket; try { //Server-side socket instance sslServerSocket = sslServerSocket(); //Setting up individual authentication sslServerSocket.setNeedClientAuth(false); } catch (Exception e) { e.printStackTrace(); return false; } //Receiving services return acceptService(sslServerSocket); } @Override public ResponseContext responseContext() { return ResponseContextFactory.responseContext(); } /** * Acceptance of services * * @param sslServerSocket Server Secure Socket * @return Whether the receiving socket connection is started properly, true starts the thread properly, false makes an error */ private boolean acceptService(final SSLServerSocket sslServerSocket) { //Start the thread to start listening for socket connections new Thread(() -> { //Determine whether to enter the next socket connection while (nextConnection()) { final SSLSocket sslSocket; try { sslSocket = (SSLSocket) sslServerSocket.accept(); } catch (IOException e) { e.printStackTrace(); return; } try { //Processing socket connection requests handler(sslSocket); } catch (Exception e) { e.printStackTrace(); closeSocket(sslSocket); } } }).start(); return true; } }
ResponseContextFactory.java
package com.tldiw.server.factory; import com.tldiw.server.response.ResponseContext; import com.tldiw.server.response.ResponseContextHandler; /** * Response Context Factory */ public final class ResponseContextFactory { /** * Create default response context * * @return ResponseContext */ public static ResponseContext responseContext() { return new ResponseContextHandler(); } }
ServerFactory.java
package com.tldiw.server.factory; import com.tldiw.server.core.Server; import com.tldiw.server.core.HttpsServer; import javax.net.ssl.SSLSocket; import java.net.Socket; /** * Server Factory <br/> * Provide HTTP servers and HTTPS servers <br/>. * Default implementation of HTTPS server <br/> */ public final class ServerFactory { /** * Create Https Server * * @return Server < SSLSocket > */ public static Server<SSLSocket> httpsServer() { return new HttpsServer(); } /** * Create Http server, not implemented by default * * @return Server < Socket > */ public static Server<Socket> httpServer() { return null; } }
ResponseContext.java
package com.tldiw.server.response; /** * Response context, divided into: response head, response body, complete response */ public interface ResponseContext { /** * Response Head * * @return Response header byte array */ byte[] head(); /** * Response volume * * @return Response volume byte array */ byte[] body(); /** * Complete response * * @return Full response byte array */ byte[] response(); }
ResponseContextHandler.java
package com.tldiw.server.response; import java.nio.charset.StandardCharsets; /** * Default Response Processor to Implement ResponseContext Interface */ public class ResponseContextHandler implements ResponseContext { private byte[] head; private byte[] body; private byte[] response; @Override public byte[] head() { if (head != null) return head; String headString = "HTTP/1.1 200\n" + "Server: nginx/1.17.0\n" + "Date: Tue, 06 Aug 2019 03:33:50 GMT\n" + "Content-Type: text/html;charset=UTF-8\n" + "Content-Length: " + body().length + "\n" + "Connection: keep-alive\n" + "Content-Language: zh-CN\n\n"; head = headString.getBytes(); return head; } @Override public byte[] body() { if (body != null) return body; String bodyString = "<!DOCTYPE html>\n" + "<html lang=\"en\">\n" + "<head>\n" + "\n" + " <meta charset=\"UTF-8\">\n" + " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n" + " <title>VIP Console</title>\n" + "\n" + "\n" + " <link href=\"https://image.yoootoo.com/css/admin.login.min.css\" rel=\"stylesheet\">\n" + " <link rel=\"stylesheet\" href=\"https://image.yoootoo.com/css/theme.min.css\" id=\"stylesheetLight\">\n" + "\n" + "\n" + "</head>\n" + "\n" + "<body class=\"text-center\" style=\"background: white;\">\n" + "\n" + "<form class=\"form-signin\" action=\"/portal/main/adminLogin\" method=\"post\">\n" + "\n" + " <img class=\"mb-4\" src=\"https://image.yoootoo.com/image/logo.png\" alt=\"\" width=\"72\"\n" + " height=\"72\">\n" + " <h1 class=\"header-title\">\n" + " VIP Console\n" + " </h1>\n" + " <br/>\n" + " <input type=\"text\" id=\"admin\" name=\"admin\" class=\"form-control\" placeholder=\"administrator account\" required autofocus>\n" + " <input type=\"password\" id=\"password\" name=\"password\" class=\"form-control\" placeholder=\"Administrator password\" required>\n" + "\n" + " <span class=\"text-danger\">\n" + " Your session has expired, please log in again!\n" + " </span>\n" + "\n" + " <br/>\n" + " <div class=\"col-auto my-1\">\n" + " <div class=\"custom-control custom-checkbox mr-sm-2\">\n" + " <input type=\"checkbox\" class=\"custom-control-input\" id=\"customControlAutosizing\">\n" + " <label class=\"custom-control-label\" for=\"customControlAutosizing\"> Keep passwords in mind </label>\n" + " </div>\n" + " </div>\n" + " <br/>\n" + "\n" + " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Log in immediately</button>\n" + "\n" + " <p class=\"mt-5 mb-3 text-muted\">© 2018-2019</p>\n" + "\n" + "</form>\n" + "\n" + "\n" + "<script src=\"https://image.yoootoo.com/js/jquery.min.js?v=1.0.0\"></script>\n" + "<script src=\"https://image.yoootoo.com/js/bootstrap.bundle.min.js?v=1.0.0\"></script>\n" + "</body>\n" + "</html>\n"; body = bodyString.getBytes(StandardCharsets.UTF_8); return body; } @Override public byte[] response() { if (response != null) return response; //Create an array of response bytes response = new byte[head().length + body().length]; //Copy the response header into the full response System.arraycopy(head(), 0, response, 0, head().length); //Copy the responder to the full response System.arraycopy(body(), 0, response, head().length, body().length); return response; } }
test
- Browser address bar input: https://www.tldiw.com/login.html
- After refreshing the browser pages many times, a simple HTTP service has been completed, you may find that we did not add the port number: 443 can access our service, in fact, the default port of Https protocol is 443, just like the default port of Http 80, when we change the port, remember to add the port number. Otherwise, requests will be made on port 443.
- When we change the domain name to localhost access, try:
The console throws an exception:
In fact, the reason is very simple. This CA application certificate can only use domain name: www.tldiw.com Access, otherwise it will be considered unsafe connection, we can try IP access instead, the result is the same.
Local IP access:
LAN IP access: