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也返回401,说明问题出在环境或Key本身;如果curl成功,说明应用代码或配置在Azure环境中有加载异常。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"}] }'
内容的提问来源于stack exchange,提问作者Nafsika
相关产品推荐
相关产品推荐

