You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

端口587连接smtp.gmail.com时SSL握手失败,端口465正常求助

解决Gmail SMTP端口587连接时的SSLException异常

这个问题的核心是你混淆了SMTP两种加密端口的工作方式

  • 端口465是SMTPS协议,连接建立时就直接启动SSL/TLS加密,所以你的SSLSocket代码用它没问题
  • 端口587是标准SMTP端口,要求先建立明文连接,再通过STARTTLS命令主动切换到加密模式,直接用SSLSocket连它就会因为对方不识别SSL握手报文而报错

下面是修正后的代码,我标注了关键步骤:

package smtpClient;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.net.Socket;

class TLS_Mime_G {
    static final int PORT = 587;
    static String REMOTEHOST = "smtp.gmail.com";
    public static void main(String[] args) throws IOException, NoSuchAlgorithmException, KeyManagementException {
        // 第一步:先建立普通明文Socket连接到587端口
        Socket plainSocket = new Socket(REMOTEHOST, PORT);
        BufferedReader in = new BufferedReader(new InputStreamReader(plainSocket.getInputStream()));
        OutputStreamWriter out = new OutputStreamWriter(plainSocket.getOutputStream());

        // 读取服务器初始响应
        System.out.println("Server response: " + in.readLine());

        // 第二步:发送EHLO命令(SMTP握手必备)
        out.write("EHLO localhost\r\n");
        out.flush();
        // 读取服务器EHLO响应(简化处理,实际可循环读取直到结束)
        String line;
        while ((line = in.readLine()) != null && !line.startsWith("250 ")) {
            System.out.println("Server EHLO response: " + line);
        }
        System.out.println("Final EHLO response: " + line);

        // 第三步:发送STARTTLS命令,请求切换到加密连接
        out.write("STARTTLS\r\n");
        out.flush();
        System.out.println("STARTTLS response: " + in.readLine());

        // 第四步:将普通Socket包装成SSLSocket,启动SSL握手
        SSLSocketFactory ssf = (SSLSocketFactory) SSLSocketFactory.getDefault();
        SSLSocket sslSocket = (SSLSocket) ssf.createSocket(
                plainSocket, REMOTEHOST, PORT, true); // 用已有的明文Socket包装
        sslSocket.setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"}); // 指定安全的协议版本
        sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites());
        sslSocket.addHandshakeCompletedListener(new MyTLSHandshakeListener());
        
        sslSocket.startHandshake(); // 现在执行握手就不会报错了
        System.out.println("Connected to " + sslSocket.getRemoteSocketAddress());

        // 后续可继续发送SMTP命令(比如AUTH登录等)
        // ...

        // 关闭资源
        sslSocket.close();
        plainSocket.close();
    }
}

class MyTLSHandshakeListener implements HandshakeCompletedListener {
    public void handshakeCompleted(HandshakeCompletedEvent e) {
        System.out.println("Handshake successful!");
        System.out.println("Cipher suite used: " + e.getCipherSuite());
    }
}

另外补充几个实用注意点:

  • 建议指定更安全的TLS版本(比如TLSv1.2及以上),避免老旧的SSLv3等不安全协议
  • 连接Gmail SMTP需要开启低安全性应用权限(如果开启了两步验证,需要使用App密码)
  • 实际生产环境中,更推荐使用JavaMail API来处理SMTP通信,它已经封装了这些复杂的握手和加密逻辑,不用手动处理Socket

内容的提问来源于stack exchange,提问作者Shashank

火山引擎 最新活动