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

Azure虚拟机部署Spring Boot应用调用SendGrid发件失败(401错误)

问题描述

在Azure虚拟机上部署Spring Boot应用后,无法通过SendGrid发送邮件通知,但本地运行该应用时邮件发送完全正常。已尝试使用SendGrid的受限权限和全权限API Key,调用时均返回401错误,提示授权凭证无效、过期或已撤销。

报错信息
401
{"errors":[{"message":"The provided authorization grant is invalid, expired, or revoked","field"
,"help"
}]}

{Strict-Transport-Security=max-age=600; includeSubDomains, Server=nginx, Access-Control-Allow-Origin=https://sendgrid.api-docs.io, Access-Control-Allow-Methods=POST, Connection=keep-alive, X-No-CORS-Reason=https://sendgrid.com/docs/Classroom/Basics/API/cors.html, Content-Length=116, Access-Control-Max-Age=600, Date=Wed, 19 Jun 2024 17:54:06 GMT, Access-Control-Allow-Headers=Authorization, Content-Type, On-behalf-of, x-sg-elas-acl, Content-Type=application/json}
配置文件(application.properties)
server.port=9090

spring.datasource.username=dbuser
spring.datasource.password=pass123
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/aimodosia

# auto generate sql and update db schema at startup
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update

#show and format sql
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

#dialect
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.PostgreSQLDialect


# App Properties
app.jwtSecret=123esef
app.jwtExpirationMs=99900000

#TRY OLD MAIL
sendgrid.key=abcd

#app.sendgrid.key=abc

### Mail Properties
#spring.mail.host=smtp.sendgrid.net
#spring.mail.port=587
#spring.mail.username=new-try
#spring.mail.password=${app.sendgrid.key}
#spring.mail.properties.mail.smtp.auth=true
#spring.mail.properties.mail.smtp.starttls.enable=true
#spring.mail.properties.mail.smtp.starttls.required=true

management.endpoint.health.probes.enabled=true
management.health.livenessState.enabled=true
management.health.readinessState.enabled=true
代码实现

SendGrid配置类(Sendgridconfig)

@Configuration
public class Sendgridconfig {

    @Value("${sendgrid.key}")
    private String appKey;

    @Bean
    public SendGrid sendGrid(){
        return new SendGrid(appKey);
    }
}

邮件服务类(EmailService)

@Service
public class EmailService {

    @Autowired
    SendGrid sendGrid;

    public void sendEmail(String to, String subject, String body) throws IOException {
        Email from = new Email("it2021077@hua.gr");
        Email toEmail = new Email(to);
        Content content = new Content("text/plain", body);
        Mail mail = new Mail(from, subject, toEmail, content);

        Request request = new Request();
        try {
            request.setMethod(Method.POST);
            request.setEndpoint("mail/send");
            request.setBody(mail.build());
            Response response = sendGrid.api(request);
            System.out.println(response.getStatusCode());
            System.out.println(response.getBody());
            System.out.println(response.getHeaders());
        } catch (IOException ex) {
            throw ex;
        }
    }
}

控制器调用示例

@PostMapping("/{user_id}/new")
    @Secured("ROLE_SECRETARY")
    public ResponseEntity<Map<String, String>> saveDonationRequest(@PathVariable Integer user_id, @RequestBody DonationRequest donationRequest) {
        Map<String, String> response = new HashMap<>();

        if (donationRequestRepository.findByLocationAndDate(donationRequest.getLocation(), donationRequest.getDate()).isPresent()) {
            System.out.println("Donation Request already exists.");
            response.put("error", "Error. Donation Request already exists.");
            return ResponseEntity.badRequest().body(response);
        } else if (donationRequest.getDate() == null || donationRequest.getLocation().isBlank() || donationRequest.getDate().isBefore(LocalDate.now())) {
            response.put("error", "Invalid donation request details.");
            return ResponseEntity.badRequest().body(response);
        } else {
            User user = userDetailsService.getUser(user_id);
            String email = user.getEmail();
            Secretary secretary = secretaryRepository.findByEmail(email).orElse(null);
            if (secretary != null) {
                System.out.println("GDGDHFGDFG");
                donationRequest.setSecretary(secretary);
                donationRequestService.saveDonationRequest(donationRequest);

                // Send confirmation email
                try {
                    String subject = "New Donation Request Submitted";
                    String body = "A new donation request has been created for " + donationRequest.getLocation() + " on " + donationRequest.getDate() + ".";
                    emailservice.sendEmail(email, subject, body);
                } catch (IOException e) {
                    e.printStackTrace();
                    response.put("error", "Your request has been done, but mail failed to send");
                    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
                }

                response.put("success", "Created New Donation Request and sent confirmation email.");
                return ResponseEntity.ok(response);
            } else {
                response.put("error", "Secretary not found.");
                return ResponseEntity.badRequest().body(response);
            }
        }
    }
排查建议
  • 验证Azure环境中API Key的正确性:检查虚拟机内应用的配置文件或环境变量,确认sendgrid.key的值和本地测试时完全一致,无额外空格或格式错误。建议在Azure中通过环境变量注入API Key,避免明文配置的潜在问题。
  • 检查SendGrid账户与Key状态:登录SendGrid后台,确认账户未被限制或暂停,API Key未被撤销。同时检查账户是否完成必要验证(如邮箱、域名验证),未验证的账户可能无法正常发送邮件。
  • 确认Azure网络出站规则:检查虚拟机的网络安全组(NSG)是否允许出站访问SendGrid的API端点(api.sendgrid.com,端口443)。网络限制可能导致请求无法到达SendGrid服务器,间接返回401错误。
  • 核对API Key权限:即使使用全权限Key,需确认该Key已启用Mail Send权限。在SendGrid创建API Key时,必须勾选对应的邮件发送权限选项。
  • 捕获完整请求日志:在Azure环境开启应用的详细日志,记录发送邮件时的请求头(尤其是Authorization头),确认API Key是否正确携带。排查是否存在配置加载异常,导致请求未携带有效凭证。
  • 直接测试SendGrid API连通性:在Azure虚拟机中用curl命令测试SendGrid API,示例如下:
    curl -X POST "https://api.sendgrid.com/v3/mail/send" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "personalizations": [{"to": [{"email": "recipient@example.com"}]}],
      "from": {"email": "it2021077@hua.gr"},
      "subject": "Test Email",
      "content": [{"type": "text/plain", "value": "Test content"}]
    }'
    
    如果curl也返回401,说明问题出在环境或Key本身;如果curl成功,说明应用代码或配置在Azure环境中有加载异常。

内容的提问来源于stack exchange,提问作者Nafsika

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 03:05:02