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

微软停用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服务器通信。

操作步骤:

  1. 部署代理服务,配置Azure AD应用凭据(需注册应用、获取客户端ID/密钥、添加SMTP.Send权限)
  2. 修改Oracle代码中的l_smtp_hostname和l_smtp_port为代理的地址和端口
  3. 按代理要求配置验证方式(如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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 12:57:15