如何实现支持Google OAuth2自动令牌刷新的Spring Boot JavaMailSender?
Google OAuth2 场景下自动处理令牌刷新的 JavaMailSender 实现方案
确实有成熟的实现方式,不用每次手动构建JavaMailSenderImpl,可以结合Spring Boot自动配置完成令牌自动刷新的流程,核心是通过扩展JavaMail的OAuth2认证逻辑,配合自定义的令牌提供者来实现。
核心实现步骤
- 自定义OAuth2令牌提供者:封装Google令牌的获取与刷新逻辑,作为Spring Bean管理,确保每次获取的都是有效令牌。
- 扩展SMTP OAuth2认证器:重写JavaMail的
SMTPOAuth2Authenticator,在认证时调用令牌提供者获取最新access_token。 - 整合Spring Boot自动配置:复用
MailProperties的基础配置,定制JavaMailSenderImpl的会话初始化逻辑,绑定自定义认证器。
代码示例
1. 令牌提供者实现
@Component public class GoogleOAuth2TokenProvider { private final String refreshToken; private final String clientId; private final String clientSecret; private String accessToken; private long expirationTime; // 从配置文件注入参数 public GoogleOAuth2TokenProvider(@Value("${google.oauth2.refresh-token}") String refreshToken, @Value("${google.oauth2.client-id}") String clientId, @Value("${google.oauth2.client-secret}") String clientSecret) { this.refreshToken = refreshToken; this.clientId = clientId; this.clientSecret = clientSecret; // 初始化时先获取一次令牌 refreshAccessToken(); } // 线程安全的令牌获取方法,提前1分钟刷新 public synchronized String getValidAccessToken() { if (System.currentTimeMillis() >= expirationTime - 60000) { refreshAccessToken(); } return accessToken; } private void refreshAccessToken() { MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add("client_id", clientId); params.add("client_secret", clientSecret); params.add("refresh_token", refreshToken); params.add("grant_type", "refresh_token"); RestTemplate restTemplate = new RestTemplate(); ResponseEntity<Map> response = restTemplate.postForEntity("https://oauth2.googleapis.com/token", params, Map.class); if (response.getStatusCode().is2xxSuccessful()) { Map<String, Object> body = response.getBody(); this.accessToken = (String) body.get("access_token"); long expiresIn = (Long) body.get("expires_in"); this.expirationTime = System.currentTimeMillis() + expiresIn * 1000; } else { throw new RuntimeException("刷新Google OAuth2令牌失败"); } } }
2. 定制JavaMailSender配置
@Configuration public class GoogleMailConfig { @Autowired private GoogleOAuth2TokenProvider tokenProvider; @Bean public JavaMailSender javaMailSender(MailProperties mailProperties) { JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); // 复用Spring Boot自动配置的邮件基础参数 mailSender.setHost(mailProperties.getHost()); mailSender.setPort(mailProperties.getPort()); mailSender.setUsername(mailProperties.getUsername()); Properties props = mailSender.getJavaMailProperties(); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.starttls.enable", "true"); props.put("mail.smtp.sasl.enable", "true"); props.put("mail.smtp.sasl.mechanisms", "XOAUTH2"); props.put("mail.smtp.auth.mechanisms", "XOAUTH2"); // 绑定自定义OAuth2认证器 mailSender.setSessionInitializer(session -> { session.getProperties().put("mail.smtp.sasl.authorizationid", mailProperties.getUsername()); try { SMTPTransport transport = (SMTPTransport) session.getTransport("smtp"); transport.setOAuth2Authenticator(new SMTPOAuth2Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { String validToken = tokenProvider.getValidAccessToken(); return new PasswordAuthentication(mailProperties.getUsername(), validToken); } }); // 可选:添加传输监听处理发送失败场景 transport.addTransportListener(new TransportListener() { @Override public void messageDelivered(TransportEvent e) {} @Override public void messageNotDelivered(TransportEvent e) { // 令牌过期导致失败时可在此重试 } @Override public void messagePartiallyDelivered(TransportEvent e) {} }); } catch (NoSuchProviderException e) { throw new RuntimeException("初始化SMTP传输失败", e); } }); return mailSender; } }
关键说明
- 完全复用Spring Boot的
MailProperties配置(比如spring.mail.host=smtp.gmail.com等),无需手动重复配置。 - 令牌刷新逻辑自动触发,每次发送邮件时都会获取有效令牌,无需业务代码干预。
- 线程安全的令牌获取机制,避免多线程下重复刷新的问题。
内容的提问来源于stack exchange,提问作者Kevin Day
相关产品推荐
相关产品推荐

