使用JavaMail API连接到GMail,而不启用安全性较低的访问



是否有任何方法可以使用JavaMail API连接到GMail而不启用"不太安全的访问"?

您必须使用OAuth2进行身份验证,以避免启用"不太安全的访问"。

根据很难找到的文件

  • https://developers.google.com/gmail/imap/xoauth2-protocol
  • https://javaee.github.io/javamail/OAuth2

示例代码:

Properties props = (Properties) System.getProperties().clone();
props.put("mail.imaps.ssl.enable", "true");
props.put("mail.imaps.auth.mechanisms", "XOAUTH2");
// props.put("mail.debug.auth", "true");
Session session = Session.getDefaultInstance(props);
// session.setDebug(true);
store = session.getStore("imaps");
String accessToken = getAccessToken(user, clientId, clientSecret);
store.connect(hostname, user, accessToken);

下一个技巧是获取访问令牌。

  • https://developers.google.com/identity/protocols/oauth2/native-app#step-2:sensed-a-request-to-googles-oauth-2.0-server

这里有一个你可以在本地使用的:

您首先需要在https://console.developers.google.com/apis/credentials并获取客户端id和机密JSON

您可以在任何端口上启动本地web服务器,并在任何端口使用http://localhost重定向URL(需要在前面提到的屏幕上添加(。

private static String getAccessToken(String emailAddress, String clientId, String clientSecret) throws Exception {
NetHttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(jsonFactory, new StringReader("{n" +
"  "installed": {n" +
"    "client_id": "" + clientId + "",n" +
"    "client_secret": "" + clientSecret + ""n" +
"  }n" +
"}"));
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(transport, jsonFactory, clientSecrets, Collections.singleton(GMAIL_SCOPE)).setAccessType("offline").build();
AtomicReference<String> redirectUri = new AtomicReference<>();
String authorizationCode = doRedirect("code=([^&$]+)", RE.wrapConsumer(p -> {
redirectUri.set("http://localhost:" + p);
Desktop.getDesktop().browse(flow.newAuthorizationUrl().setRedirectUri(redirectUri.get()).toURI());
}));
// second redirect URL needs to be set and match the one on the newAuthorization flow but isn't actually used
GoogleTokenResponse execute = flow.newTokenRequest(authorizationCode).setRedirectUri(redirectUri.get()).execute();
String refreshToken = execute.getRefreshToken();
String accessToken = execute.getAccessToken();
return accessToken;
}
private static String doRedirect(String pattern, Consumer<Integer> portConsumer) {
try {
ServerSocket socket = new ServerSocket(0);
portConsumer.accept(socket.getLocalPort());
Socket connection = socket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
OutputStream out = new BufferedOutputStream(connection.getOutputStream());
PrintStream pout = new PrintStream(out);
try {
String request = in.readLine();
Matcher matcher = Pattern.compile(pattern).matcher(request);
String response = "<html><body>Window can be closed now.</body></html>";
pout.println("HTTP/1.1 200 OK");
pout.println("Server: MyApp");
pout.println("Content-Type: text/html");
pout.println("Content-Length: " + response.length());
pout.println();
pout.println(response);
pout.flush();
if (matcher.find())
return matcher.group(1);
else
throw new RuntimeException("Could not find match");
} finally {
in.close();
}
} catch (Exception ex) {
throw new RuntimeException("Error while listening for local redirect", ex);
}
}

获取刷新令牌并将其保存以备将来使用是最好的选择。根据Getting null refresh令牌:

GoogleAuthorizationCodeFlow flow = 
new GoogleAuthorizationCodeFlow.Builder(transport, jsonFactory, clientSecrets, Collections.singleton(GMAIL_SCOPE))
.setApprovalPrompt("force") // Needed only if you users didn't accept this earlier
.setAccessType("offline")
.build();

最新更新