You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现支持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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 07:53:18