Skip to content

IMAP 认证机制 ​

IMAP 协议支持多种认证方式,其中最常见的是:

  1. 两步验证(LOGIN):客户端先请求用户名,再请求密码。
  2. 一步验证(PLAIN):客户端一次性提交用户名和密码。

下面分别介绍这两种方式的报文流程及在服务端的实现。


一、两步验证(LOGIN) ​

报文流程 ​

客户端发起 AUTHENTICATE LOGIN,服务端依次发送用户名和密码的 Base64 提示:

text
> A002 AUTHENTICATE LOGIN
< + VXNlcm5hbWU6         (Base64 编码的 "Username:")
> amlldGlAdGlvLmNvbQ==  (Base64 编码的 "jieti@tio.com")
< + UGFzc3dvcmQ6         (Base64 编码的 "Password:")
> MDAwMDAwMDA=          (Base64 编码的 "00000000")
< A002 OK AUTHENTICATE completed.

二、一步验证(PLAIN) ​

报文流程 ​

在 CAPABILITY 响应中添加 AUTH=PLAIN,客户端就会使用 PLAIN 机制,一次性发送用户名和密码:

text
41 authenticate PLAIN

+                       (服务端发起空挑战)
AGJvYgAwMDAwMDAwMA==   (Base64 编码的 "\0bob\00000000")

三、服务端实现 ​

在 ImapServerAioHandler 中,对两种机制统一在 AUTHENTICATE 命令下处理,然后根据机制分别进入不同的状态。

java
// ImapServerAioHandler 中的部分处理逻辑
if (session.getState() == ImapSessionContext.State.AUTH_WAIT_USERNAME
    || session.getState() == ImapSessionContext.State.AUTH_WAIT_PASSWORD) {
  String reply = imapService.handleAuthData(session, line);
  if (reply != null) {
    Tio.bSend(ctx, new ImapPacket(reply));
  }
  return;
}

case "AUTHENTICATE":
  reply = imapService.handleAuthenticate(session, tag, args);

1. 处理 AUTHENTICATE 命令 ​

java
public String handleAuthenticate(ImapSessionContext session, String tag, String mech) {
  StringBuilder sb = new StringBuilder();
  if (!"LOGIN".equalsIgnoreCase(mech) && !"PLAIN".equalsIgnoreCase(mech)) {
    sb.append(tag).append(" BAD Unsupported authentication mechanism").append("\r\n");
    return sb.toString();
  }

  session.setCurrentCommandTag(tag);
  if ("LOGIN".equalsIgnoreCase(mech)) {
    // 两步验证:先请求用户名
    session.setState(ImapSessionContext.State.AUTH_WAIT_USERNAME);
    String chal = Base64Utils.encodeToString("Username:".getBytes(StandardCharsets.UTF_8));
    sb.append("+ ").append(chal).append("\r\n");
  } else {
    // 一步验证:直接进入密码处理状态
    session.setState(ImapSessionContext.State.AUTH_WAIT_PASSWORD);
    sb.append("+ ").append("\r\n");
  }
  return sb.toString();
}

2. 处理客户端返回的认证数据 ​

java
public String handleAuthData(ImapSessionContext session, String data) {
  String tag = session.getCurrentCommandTag();
  StringBuilder sb = new StringBuilder();
  try {
    String decoded = Base64Utils.decodeToString(data);

    if (session.getState() == ImapSessionContext.State.AUTH_WAIT_USERNAME) {
      // 两步验证——收到用户名,继续请求密码
      session.setUsername(decoded);
      session.setState(ImapSessionContext.State.AUTH_WAIT_PASSWORD);
      String chal = Base64Utils.encodeToString("Password:".getBytes(StandardCharsets.UTF_8));
      sb.append("+ ").append(chal).append("\r\n");

    } else if (session.getState() == ImapSessionContext.State.AUTH_WAIT_PASSWORD) {
      // 收到密码后,解析用户名和密码
      String user, pass;
      if (decoded.contains("\0")) {
        // 一步验证:数据格式为 "\0user\0pass"
        String[] parts = decoded.split("\0");
        user = parts.length > 1 ? parts[1] : "";
        pass = parts.length > 2 ? parts[2] : "";
      } else {
        // 两步验证:用户名已保存在 session 中
        user = session.getUsername();
        pass = decoded;
      }

      // 调用 userService 进行认证(内部实现无关)
      Long userId = userService.authenticate(user, pass);
      if (userId != null) {
        session.setUsername(user);
        session.setUserId(userId);
        session.setState(ImapSessionContext.State.AUTHENTICATED);
        sb.append(tag).append(" OK AUTHENTICATE completed.").append("\r\n");
      } else {
        session.setState(ImapSessionContext.State.NON_AUTHENTICATED);
        sb.append(tag).append(" NO AUTHENTICATE failed: Authentication failed").append("\r\n");
      }
      session.setCurrentCommandTag(null);
    }

  } catch (IllegalArgumentException e) {
    // Base64 解码失败
    session.setState(ImapSessionContext.State.NON_AUTHENTICATED);
    sb.append(tag).append(" BAD Invalid base64 data").append("\r\n");
    session.setCurrentCommandTag(null);
  }
  return sb.toString();
}

四、小结 ​

  • 两步验证(LOGIN) 依次请求用户名和密码,更直观但多一次交互。
  • 一步验证(PLAIN) 将用户名和密码合并在一次 Base64 数据中传输,需在 CAPABILITY 中声明 AUTH=PLAIN。
  • 示例代码展示了如何在不同状态下解析客户端返回的数据,并调用 userService.authenticate 完成用户验证。

以上即为在 IMAP 服务端实现 LOGIN 与 PLAIN 两种认证方式的完整示例。

MIT Licensed