You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用OAuth2 client_credentials模式获取令牌后连接邮件服务器认证失败

问题

我尝试用以下Java代码通过OAuth2认证获取令牌,用来连接邮件服务器:

public static String getAuthToken(String tenantId, String clientId, String client_secret) throws ClientProtocolException , IOException {
    CloseableHttpClient client = HttpClients.createDefault();
    HttpPost loginPost = new HttpPost("https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/token");
    //String scopes = "https://outlook.office365.com/IMAP.AccessAsUser.All";
    String scopes = "https://outlook.office365.com/.default"; // we need this scope when we have the grand_type as client credidentials

    //String encodedBody = "client_id=" + clientId + "&scope=" + scopes + "&client_secret=" + client_secret+ "&username="+mailAddress+"&password=" + EMAIL_PASSWORD + "&grant_type=password";
    String encodedBody = "client_id=" + clientId + "&scope=" + scopes + "&client_secret=" + client_secret+ "&username="+mailAddress+ "&grant_type=client_credentials";
    loginPost.setEntity(new StringEntity(encodedBody, ContentType.APPLICATION_FORM_URLENCODED));
    loginPost.addHeader(new BasicHeader("cache-control", "no-cache"));
    CloseableHttpResponse loginResponse = client.execute(loginPost);
    InputStream inputStream = loginResponse.getEntity().getContent();
    byte[] response = readAllBytes(inputStream);
    ObjectMapper objectMapper = new ObjectMapper();
    JavaType type = objectMapper.constructType(
        objectMapper.getTypeFactory().constructParametricType(Map.class, String.class, String.class));
    Map<String, String> parsed = new ObjectMapper().readValue(response, type);
    return parsed.get("access_token");
}

使用password授权类型并携带密码时一切正常,但改用client_credentials授权类型、不携带密码时,获取的令牌无法连接服务器,抛出认证失败错误:

Exception in thread "main" javax.mail.AuthenticationFailedException: AUTHENTICATE failed. at com.sun.mail.imap.IMAPStore.protocolConnect(IMAPStore.java:732) at javax.mail.Service.connect(Service.java:366) at yk.Auth.main(Auth.java:79)

我试过调整请求体(修改/移除scope等)但没用,请问如何在不发送密码的情况下获取可用于连接的有效令牌?

解决方案
  • 明确client_credentials模式的定位:这种授权是服务到服务的身份验证,令牌代表的是应用本身,而非具体用户。因此不能直接用它以用户身份访问IMAP,必须完成以下配置:
  • 配置应用级权限:
    1. 在Azure AD后台找到你的应用注册,添加应用权限(区别于委托权限),选择IMAP.AccessAsApp权限,完成管理员同意操作。
    2. 保持请求范围为https://outlook.office365.com/.default,这个范围会自动包含所有已授予的应用权限。
  • 调整IMAP连接逻辑:
    使用client_credentials令牌时,JavaMail的连接配置要做对应修改:
    • 用户名不能填普通邮箱地址,需使用{租户ID}\{客户端ID}格式的标识符。
    • 确保JavaMail版本在1.6.5及以上,配置OAuth2相关属性:
      Properties props = new Properties();
      props.setProperty("mail.imap.ssl.enable", "true");
      props.setProperty("mail.imap.auth.mechanisms", "XOAUTH2");
      props.setProperty("mail.imap.auth.login.disable", "true");
      props.setProperty("mail.imap.auth.plain.disable", "true");
      
      Session session = Session.getInstance(props);
      IMAPStore store = (IMAPStore) session.getStore("imap");
      store.connect("outlook.office365.com", 993, "{tenantId}\\{clientId}", accessToken);
      
  • 验证令牌权限:
    用离线令牌解析工具检查令牌的roles字段,确认是否包含IMAP.AccessAsApp权限。如果没有,说明权限配置或管理员同意步骤未完成。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 01:21:06