使用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,必须完成以下配置:
- 配置应用级权限:
- 在Azure AD后台找到你的应用注册,添加应用权限(区别于委托权限),选择
IMAP.AccessAsApp权限,完成管理员同意操作。 - 保持请求范围为
https://outlook.office365.com/.default,这个范围会自动包含所有已授予的应用权限。
- 在Azure AD后台找到你的应用注册,添加应用权限(区别于委托权限),选择
- 调整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
相关产品推荐
相关产品推荐

