У меня есть пакет Node.js, который обрабатывает список пользователей и выполняет вызовы API в серверную часть Java для каждого пользователя. Пакет Node.js и серверная часть Java отправляют письма пользователям с учетной записью G-Suite.
Нет проблем для Node.js, все письма проходят успешно. Что касается Java, после определенного количества отправленных писем следующие письма терпят неудачу со следующим сообщением:
org.springframework.mail.MailAuthenticationException: Authentication
failed; nested exception is javax.mail.AuthenticationFailedException:
334
eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoiQmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ==
; nested exception is: javax.mail.AuthenticationFailedException:
OAUTH2 asked for more at
org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:424)
at
org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:345)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:340)
Что после декодирования Base64 дает:
{"status":"400","schemes":"Bearer","scope":"https://mail.google.com/"}
Вот некоторые подробности моих реализаций.
@Service
public class MailServiceImpl implements MailService {
@Autowired
private JavaMailSender javaMailSender;
@Value("${fr.app.email.service-account}")
private String serviceAccount;
@Value("${fr.app.email.scope}")
private String scope;
private Credential creds;
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy à HH:mm");
@Async
public CompletableFuture<Void> sendSimpleMail(String email, String subject, String text) throws Exception {
if (creds == null) {
final HttpTransport TRANSPORT = new NetHttpTransport();
final JsonFactory JSON_FACTORY = new JacksonFactory();
final URL URL = Thread.currentThread().getContextClassLoader().getResource("MyApp-d8a5d9785c54.p12");
creds = new GoogleCredential.Builder()
.setTransport(TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(serviceAccount)
.setServiceAccountPrivateKeyFromP12File(new File(URL.getFile()))
.setServiceAccountScopes(Collections.singleton(scope))
.setServiceAccountUser(((JavaMailSenderImpl)javaMailSender).getUsername())
.build();
}
if (creds.getExpiresInSeconds() != null && creds.getExpiresInSeconds() < 0 || creds.getAccessToken() == null) {
creds.refreshToken();
}
try {
((JavaMailSenderImpl)javaMailSender).setDefaultEncoding("UTF-8");
((JavaMailSenderImpl)javaMailSender).setPassword(creds.getAccessToken());
Message message = new MimeMessage(((JavaMailSenderImpl)javaMailSender).getSession());
message.setFrom(new InternetAddress(((JavaMailSenderImpl)javaMailSender).getUsername()));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email));
message.setSubject(subject);
message.setContent(text, "text/html; charset=UTF-8");
javaMailSender.send((MimeMessage)message);
return CompletableFuture.completedFuture(null);
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
}
И рабочая реализация Node.js:
const { google } = require('googleapis');
// configure a JWT auth client
let jwtClient = new google.auth.JWT(
config.appServiceAccount,
config.googleKeyFile,
null,
[config.googleScope],
config.appMail
);
logger.info('Gmail client created!');
const gmail = google.gmail({ version: "v1", auth: jwtClient });
function sendMail(gmail, jwtClient, to, subject, message) {
let headers = {
"To": to,
"Subject": subject,
"Content-Type": "text/html; charset=UTF-8"
}
let email = '';
for (var header in headers)
email += header += ": " + headers[header] + "\r\n";
email += "\r\n" + message;
return gmail.users.messages.send({
auth: jwtClient,
userId: config.appMail,
requestBody: {
raw: btoa(email).replace(/\+/g, '-').replace(///g, '_')
}
});
}
У меня есть более подробная информация об ошибке благодаря ключу конфигурации mail.debug=true
: первые 80 сообщений дают следующий результат:
DEBUG SMTP: protocolConnect login, host=smtp.gmail.com,
[email protected], password=<non-null> DEBUG SMTP: Attempt to
authenticate using mechanisms: XOAUTH2 DEBUG SMTP: Using mechanism
XOAUTH2 AUTH XOAUTH2 <access_token>
235 2.7.0 Accepted
Следующие сообщения производят вывод:
DEBUG SMTP: protocolConnect login, host=smtp.gmail.com,
[email protected], password=<non-null> DEBUG SMTP: Attempt to
authenticate using mechanisms: XOAUTH2 DEBUG SMTP: Using mechanism
XOAUTH2 AUTH XOAUTH2 <exact same access_token>
334
eyJzdGF0dXMiOiI0MDAiLCJzY2hlbWVzIjoiQmVhcmVyIiwic2NvcGUiOiJodHRwczovL21haWwuZ29vZ2xlLmNvbS8ifQ==
DEBUG SMTP: AUTH XOAUTH2 failed, THROW:
javax.mail.AuthenticationFailedException: OAUTH2 asked for more
at com.sun.mail.smtp.SMTPTransport$OAuth2Authenticator.doAuth(SMTPTransport.java:1095)
at com.sun.mail.smtp.SMTPTransport$Authenticator.authenticate(SMTPTransport.java:909)
at com.sun.mail.smtp.SMTPTransport.authenticate(SMTPTransport.java:843)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:748)
Что касается реализация клиента Node.js, основное отличие состоит в том, что Node.js, похоже, не использует SMTP, а вместо этого вызывает API.
Я прочитал здесь, что существуют определенные ограничения для SMTP, но я не могу понять, какое ограничение я достиг, потому что сообщение об ошибке очень нечеткое (DDoS, сообщения за 10 минут, ...).
@BillShannon моя реализация должна обновлять токен при необходимости. Вы видите что-то не так в моем коде?
Убедитесь, что это освежает, когда вы думаете, что это нужно. У вас есть выражение, в котором используются "&&" и "||" и без скобок, действительно ли он делает то, что вы ожидаете?
@BillShannon Я буду исследовать, но если проблема связана с токеном, вы объясните, почему это происходит только при одновременной отправке большого количества писем? Есть ли какое-то «истечение срока действия bean-компонента», когда он простаивает, что объясняет такое поведение?
Срок действия токена зависит от времени. Предположительно, количество отправленных сообщений определяет количество затраченного времени. Но очевидно, что я просто догадываюсь об истечении срока действия токена, вам нужно немного отладить.
Вероятно, срок действия вашего токена доступа OAUTH2 истек, и его необходимо обновить.