微软停用Basic Authentication后,Oracle 21c如何用Hotmail通过UTL_SMTP发邮件?
解决Oracle UTL_SMTP连接Hotmail(微软)的身份验证问题
微软已停用个人Hotmail账号的Basic Authentication,针对Oracle 21c的UTL_SMTP工具,可通过以下几种方案实现身份验证:
方案1:使用应用密码(限开启两步验证的账号)
对开启了两步验证(MFA)的个人Hotmail账号,微软允许创建应用密码作为Basic Auth的替代凭证,这是当前个人账号唯一保留的Basic Auth例外场景。
操作步骤:
- 登录微软账户,进入「安全」→「高级安全选项」
- 开启两步验证(未开启则无法创建应用密码)
- 在「应用密码」模块生成新的应用密码
- 将PL/SQL代码中
l_smtp_password的值替换为该应用密码,其余代码无需修改
方案2:搭建本地SMTP代理服务器
部署支持微软OAuth2验证的SMTP代理(如Postfix搭配OAuth2插件、或专用邮件转发工具),Oracle通过Basic Auth连接代理,代理再通过OAuth2与微软SMTP服务器通信。
操作步骤:
- 部署代理服务,配置Azure AD应用凭据(需注册应用、获取客户端ID/密钥、添加
SMTP.Send权限) - 修改Oracle代码中的
l_smtp_hostname和l_smtp_port为代理的地址和端口 - 按代理要求配置验证方式(如Basic Auth或匿名访问)
此方案无需大幅修改Oracle代码,但需额外维护代理服务。
方案3:自定义PL/SQL实现OAuth2(XOAUTH2)验证
Oracle 21c的UTL_SMTP原生不支持XOAUTH2,需手动结合UTL_HTTP获取OAuth2令牌,再构造SMTP的AUTH命令完成验证。
修改后的代码示例
-- 使用XOAUTH2验证的测试过程 DECLARE l_smtp_hostname varchar2(1024); l_smtp_port varchar2(1024); l_wallet_path varchar2(1024); l_wallet_password varchar2(1024); l_domain varchar2(1024); l_smtp_username varchar2(1024); l_client_id varchar2(1024); -- Azure AD注册应用的客户端ID l_client_secret varchar2(1024); -- Azure AD注册应用的客户端密钥 l_oauth_token varchar2(4000); l_auth_string varchar2(4000); l_source_app varchar2(1024); l_location varchar2(1024); l_smtp_conn utl_smtp.connection; l_smtp_reply utl_smtp.reply; l_smtp_replies utl_smtp.replies; l_http_req utl_http.req; l_http_resp utl_http.resp; l_response_text clob; BEGIN l_smtp_hostname := 'outlook.office365.com'; l_smtp_port := '587'; l_domain := 'hotmail.com'; l_smtp_username := 'mYuSERNAME@hotmail.com'; l_client_id := '你的Azure AD客户端ID'; l_client_secret := '你的Azure AD客户端密钥'; l_source_app := '111'; l_wallet_path := 'My Wallet Path'; l_wallet_password := 'My Wallet Password'; -- 1. 获取OAuth2访问令牌 l_location := 'UTL_HTTP 获取OAuth令牌'; utl_http.set_wallet(l_wallet_path, l_wallet_password); l_http_req := utl_http.begin_request('https://login.microsoftonline.com/common/oauth2/v2.0/token', 'POST'); utl_http.set_header(l_http_req, 'Content-Type', 'application/x-www-form-urlencoded'); utl_http.write_text(l_http_req, 'grant_type=password' || '&client_id=' || utl_url.escape(l_client_id) || '&client_secret=' || utl_url.escape(l_client_secret) || '&username=' || utl_url.escape(l_smtp_username) || '&password=' || utl_url.escape('你的Hotmail账号密码') || '&scope=offline_access https://outlook.office365.com/SMTP.Send'); l_http_resp := utl_http.get_response(l_http_req); utl_http.read_text(l_http_resp, l_response_text); utl_http.end_response(l_http_resp); -- 解析JSON获取access_token(建议使用UTL_JSON包替代正则,更稳定) l_oauth_token := regexp_substr(l_response_text, '"access_token":"([^"]+)"', 1, 1, 'i', 1); IF l_oauth_token IS NULL THEN RAISE_APPLICATION_ERROR(-20001, '获取OAuth令牌失败: ' || l_response_text); END IF; -- 2. 建立SMTP连接 l_location := 'utl_smtp.OPEN_CONNECTION'; l_smtp_reply := utl_smtp.open_connection ( host => l_smtp_hostname , port => l_smtp_port , c => l_smtp_conn , wallet_path => l_wallet_path , wallet_password => l_wallet_password , secure_connection_before_smtp => FALSE ); If l_smtp_reply.code != 220 Then Insert into mail_error_log (err_code, err_msg, transaction_type, transaction_detail, source_app, code_location) values (l_smtp_reply.code, l_smtp_reply.text, 'utl_smtp', l_smtp_reply.code, l_source_app, l_location); commit; End If; -- 发送首次EHLO l_location := 'utl_smtp.EHLO - First'; l_smtp_replies := utl_smtp.ehlo(l_smtp_conn, l_smtp_hostname); -- 启动TLS加密 l_location := 'utl_smtp.STARTTLS'; l_smtp_reply := utl_smtp.starttls(l_smtp_conn); If l_smtp_reply.code != 220 Then Insert into mail_error_log (err_code, err_msg, transaction_type, transaction_detail, source_app, code_location) values (l_smtp_reply.code, l_smtp_reply.text, 'utl_smtp', l_smtp_reply.code, l_source_app, l_location); commit; End If; -- 发送第二次EHLO l_location := 'utl_smtp.EHLO - Second'; l_smtp_replies := utl_smtp.ehlo(l_smtp_conn, l_domain); -- 3. 手动发送AUTH XOAUTH2命令 l_location := 'utl_smtp.AUTH XOAUTH2'; -- 构造XOAUTH2验证字符串 l_auth_string := 'user=' || l_smtp_username || chr(1) || 'auth=Bearer ' || l_oauth_token || chr(1) || chr(1); -- Base64编码 l_auth_string := utl_raw.cast_to_varchar2(utl_encode.base64_encode(utl_raw.cast_to_raw(l_auth_string))); -- 发送AUTH命令 l_smtp_reply := utl_smtp.command(l_smtp_conn, 'AUTH XOAUTH2 ' || l_auth_string); If l_smtp_reply.code != 235 Then Insert into mail_error_log (err_code, err_msg, transaction_type, transaction_detail, source_app, code_location) values (l_smtp_reply.code, l_smtp_reply.text, 'utl_smtp', l_smtp_reply.code, l_source_app, l_location); commit; End If; -- 关闭连接 utl_smtp.quit(l_smtp_conn); EXCEPTION When utl_smtp.transient_error Or utl_smtp.permanent_error Or utl_http.http_error Then Begin utl_smtp.quit(l_smtp_conn); Exception When utl_smtp.transient_error Or utl_smtp.permanent_error Then Null; End; RAISE; END;
注意事项
- 解析JSON时,建议启用Oracle的
UTL_JSON包,替代正则表达式提升稳定性 - 微软的密码流(Resource Owner Password Credentials Grant)可能存在使用限制,需确认账号是否支持
- 确保Oracle服务器可访问微软令牌端点(
login.microsoftonline.com)和SMTP服务器,防火墙需开放对应端口
内容的提问来源于stack exchange,提问作者user3138025
相关产品推荐
相关产品推荐

