我正在用 Java 构建一个简单的命令行应用程序,它可以登录我的电子邮件箱 (IMAP) 并下载所有附件。我使用了基本身份验证,但 Microsoft 正在禁用它,因此我尝试将我的应用程序转换为使用 OAuth。
在阅读了不同的 OAuth 流程后,似乎对于我的简单独立命令行应用程序来说,简单地对密码进行硬编码是没有问题的,资源所有者密码凭证大(如所述here https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc)将是最好的(或好的)选择。我进一步根据以下指示这个来源 https://javaee.github.io/javamail/OAuth2其中描述了如何使用最新版本的 Javamail 启用 OAuth。
将它们放在一起似乎有点困难,而且我不断收到 AUTHENTICATE Failed 错误。
那么,我尝试了什么?我首先按如下方式检索我的授权令牌:
public String getAuthToken() {
try {
CloseableHttpClient client = HttpClients.createDefault();
HttpPost loginPost = new HttpPost("https://login.microsoftonline.com/organizations/oauth2/v2.0/token");
String clientId = "some client UUID";
String scopes = "email openid IMAP.AccessAsUser.All offline_access";
String client_secret = "My client secret, not base64 encoded";
String username = "my emailadress";
String password = "my password, not base64 encoded";
String encodedBody = "client_id=" + clientId
+ "&scope=" + scopes
+ "&client_secret=" + client_secret
+ "&username=" + username
+ "&password=" + password
+ "&grant_type=password";
loginPost.setEntity(new StringEntity(encodedBody, ContentType.APPLICATION_FORM_URLENCODED));
loginPost.addHeader(new BasicHeader("cache-control", "no-cache"));
CloseableHttpResponse loginResponse = client.execute(loginPost);
byte[] response = loginResponse.getEntity().getContent().readAllBytes();
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");
} catch (Exception e) {
e.printStackTrace();
return null;
}
来自 oauth 服务的响应实际上是一个 json 对象,其中包含以下字段:
显然令牌更长,但这里不共享。 access_token 本身采用三个 base64 编码字符串的形式,以 .第一个,解码时包含
{
"typ": "JWT",
"nonce": "Vobb8bI7E...",
"alg": "RS256",
"x5t": "2ZQpJ3Up...",
"kid": "2ZQpJ3Up..."
}
第二部分是一个更大的对象,包含以下字段(也经过编辑):
{
"aud": "someuuid",
"iss": "https://sts.windows.net/someuuid/",
"iat": 1658397625,
"nbf": 1658397625,
"exp": 1658402597,
"acct": 0,
"acr": "1",
"aio": "ASQ....",
"amr": [
"pwd"
],
"app_displayname": "myapp",
"appid": "some uuid",
"appidacr": "1",
"family_name": "My Last Name",
"given_name": "My First Name",
"idtyp": "user",
"ipaddr": "some.ip.address.here",
"name": "My Full name",
"oid": "someuuid",
"platf": "14",
"puid": "10032...",
"rh": "0.AToA....",
"scp": "email IMAP.AccessAsUser.All openid profile",
"sub": "enaKK...",
"tenant_region_scope": "EU",
"tid": "someuuid",
"unique_name": "my email",
"upn": "my email",
"uti": "1cc...",
"ver": "1.0",
"wids": [
"some uuid",
"some uuid"
],
"xms_st": {
"sub": "02n7h..."
},
"xms_tcdt": 1571393936
}
最后一部分只是二进制数据。我目前只是将整个 access_token 传递给 JavaMail,如下所示:
String accesstoken = new OauthTokenFetcher().getAuthToken();
imapReader = new ImapMailBoxReader(
"outlook.office365.com",
"my email",
accesstoken);
LocalDate startDate = LocalDate.of(2022,4,1);
LocalDate endDate = LocalDate.of(2022,7,1);
imapReader.processOnMessages("Inbox", startDate, endDate,this::processMessage);
与 ImapMailBoxReader 如下:
public class ImapMailBoxReader {
private String host;
private String username;
private String password;
public ImapMailBoxReader(String host, String username, String password) {
this.host = host;
this.username = username;
this.password = password;
}
public void processOnMessages(String folder, LocalDate since, LocalDate until, Consumer<Message> mailconsumer) {
try {
System.out.println("Password:" + password);
Properties prop = new Properties();
MailSSLSocketFactory sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true);
prop.put("mail.debug.auth", "true");
prop.put("mail.imap.sasl.enable", "true");
prop.put("mail.imap.sasl.mechanisms", "XOAUTH2");
prop.put("mail.imap.auth.login.disable", "true");
prop.put("mail.imap.auth.plain.disable", "true");
prop.put("mail.imap.ssl.enable", "true");
// Create the session
//Connect to the server
Session session = Session.getDefaultInstance(prop, null);
session.setDebug(true);
Store store = session.getStore("imap");
store.connect(host, username, password);
//open the inbox folder
Folder inbox = store.getFolder(folder);
inbox.open(Folder.READ_ONLY);
Message[] messages;
if (since != null) {
Date startDate = Date.from(since.atStartOfDay(ZoneId.systemDefault()).toInstant());
SearchTerm newerThan = new ReceivedDateTerm(ComparisonTerm.GE, startDate);
if (until != null) {
Date endDate = Date.from(until.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
SearchTerm olderThan = new ReceivedDateTerm(ComparisonTerm.LT, endDate);
SearchTerm both = new AndTerm(olderThan, newerThan);
messages = inbox.search(both);
} else {
messages = inbox.search(newerThan);
}
} else if (until != null) {
Date endDate = Date.from(until.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
SearchTerm olderThan = new ReceivedDateTerm(ComparisonTerm.LT, endDate);
messages = inbox.search(olderThan);
} else {
messages = inbox.getMessages();
}
for (Message m: messages) {
mailconsumer.accept(m);
}
inbox.close(false);
store.close();
} catch (Exception e) {
e.printStackTrace();
}
}
上述语句在 store.connect 语句处失败,并显示 AUTHENTICATE FAILED。
我可能错误地传递了令牌?上面的 JavaMail 文档指出我不应该对令牌进行 Base64 编码,但我确实收到了它。我应该只发送一部分吗?那是哪一部分呢?
任何帮助,将不胜感激。