AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79528880
Accepted
B Reed
B Reed
Asked: 2025-03-23 19:40:18 +0800 CST2025-03-23 19:40:18 +0800 CST 2025-03-23 19:40:18 +0800 CST

具有多个身份验证的 ASP.NET Core MVC 需要 Oauth

  • 772

在构建新的内部解决方案(使用 ASP.NET Core MVC)时,我们的策略是使用我们的 Active Directory(又名 Entra)为用户进行身份验证。然后,这个新应用程序收集数据,然后使用其 API/SDK 生成 DocuSign 对象。这是我第一次需要通过 SDK 调用 API,我对这些机制有点困惑。我想遵循最佳安全实践。DocuSign 建议在用户在场时使用机密授权代码授予,并提供示例。但是,我认为修改 Program.cs 的示例(示例显示使用旧 .NET 版本的 Startup.cs)与 Entra 身份验证结合使用时不起作用。

简而言之,这个解决方案应该这样工作:

  1. 所有用户均通过 Entra ID (Azure AD) 进行身份验证。(参见黄色突出显示)
  2. 一旦通过身份验证,用户可以执行可能需要使用 OAuth 2.0 授权码流调用 DocuSign API 的操作。

我的问题(更新后,仍然想了解前两个问题):

  1. 我相信(但不是 100% 确定)Entra 不使用 cookie 身份验证。我们不应该改变这一点。

  2. 是否可以像我使用 所述那样在单个应用程序中进行多次身份验证Program.cs AddAuthentication()? 的目的是什么EnableTokenAcquisitionToCallDownstreamApi()?是使用用户的凭据来获取这些凭据允许的其他数据吗?

  3. (已更新回答 - 找到了另一种方法来实现这一点)我认为我应该在 Program.cs 中尝试AddAuthentication(),而不是创建 HttpClient 请求,在控制器中编写代码以获取访问令牌,然后以某种方式持久化它以调用其他 DocuSign API。我以为我会找到这样的例子,但我主要看到使用 JWT 令牌进行 api 到 api 调用的例子。对吗?答案:是的

更新:最终,从 Program.cs 中删除了第二个 AddAuthentication():

// Add Entra authentication
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
//pulled out all the other AddAuth, AddOauth... 

我使用我的控制器来调用 Docusign SDK。底部有一个文档,其中包含可用的类和方法:https://developers.docusign.com/platform/auth/confidential-authcode-get-token/

在我的控制器层中使用了 DocuSignClient.cs 3 种方法。 (在此处查看其类代码https://github.com/docusign/docusign-esign-csharp-client/blob/master/sdk/src/DocuSign.eSign/Client/DocuSignClient.cs )

GetAuthorizationUri(), GenerateAccessToken(),GetUserInfo()

成功了。也许 DocuSign 会提供使用这些的示例和样本,我自己没有找到,但它相当简单。它不在他们的 QuickStart 代码中。

原文:当我尝试示例代码(下面的片段)时,我成功地通过了 Entra 的身份验证,当我使用 DocuSign 身份验证导航到控制器时,系统会提示我输入 DocuSign 凭据。一切都很好。在 中设置断点后OnCreatingTicket,我完成了身份验证的这一部分。但是,在此之后,我得到了下面的异常。所有关于它的研究表明我需要在我的默认身份验证方案中启用 cookie。我不想这样做。

例外:

The authentication handler registered for scheme 'OpenIdConnect' is 'OpenIdConnectHandler' which cannot be used for SignInAsync. The registered sign-in schemes are: Cookies.

Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
        var builder = WebApplication.CreateBuilder(args);
        
        // Add Entra authentication
        builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
            .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
        
        
        // Add DocuSign authentication
        builder.Services.AddAuthentication(options =>
                    {
                        options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                        options.DefaultChallengeScheme = "DocuSign";
                    })
                    .AddCookie()
                    .AddOAuth("DocuSign", options =>
                    {
                        options.ClientId = this.Configuration["DocuSign:ClientId"];
                        options.ClientSecret = this.Configuration["DocuSign:ClientSecret"];
                        options.CallbackPath = new PathString("/ds/callback");
                        options.AuthorizationEndpoint = this.Configuration["DocuSign:AuthorizationEndpoint"];
                        options.TokenEndpoint = this.Configuration["DocuSign:TokenEndpoint"];
                        options.UserInformationEndpoint = this.Configuration["DocuSign:UserInformationEndpoint"];
        
        options.Events = new OAuthEvents
        {
            OnCreatingTicket = async context =>
            {
                var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
                request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
                var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
                response.EnsureSuccessStatusCode();
                var user = JObject.Parse(await response.Content.ReadAsStringAsync());
                user.Add("access_token", context.AccessToken);
                user.Add("refresh_token", context.RefreshToken);
                user.Add("expires_in", DateTime.Now.Add(context.ExpiresIn.Value).ToString());
                using (JsonDocument payload = JsonDocument.Parse(user.ToString()))
                {
                    context.RunClaimActions(payload.RootElement);
                }
            }
    
    //lines 151 - 244 from https://github.com/docusign/code-examples-csharp/blob/master/launcher-csharp/Startup.cs
    
    // Add controllers and Razor pages with Auth
    builder.Services.AddControllersWithViews(options =>
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticateUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    }).AddMicrosoftIdentityUI();

    —-----------------
    [Authorize(AuthenticationSchemes = "DocuSign")]
    public IActionResult Login(string authType = "CodeGrant", string returnUrl = "/")
    {
        if (authType == "CodeGrant") 
        {
            return Challenge(new AuthenticationProperties() { RedirectUri = returnUrl });
        }
    }

以下是我用作指导的 DocuSign 文档:https://developers.docusign.com/docs/esign-rest-api/sdks/csharp/auth/

  • 1 1 个回答
  • 35 Views

1 个回答

  • Voted
  1. Best Answer
    Larry K
    2025-03-24T18:47:54+08:002025-03-24T18:47:54+08:00

    如果您的用户在使用您的“内部解决方案”(您的“应用程序”)时始终在线,那么最好设置您的 Docusign 帐户以使用您的 Entra/AD 系统作为 SSO 身份验证。这将通过浏览器使用 OAuth 授权码流程。

    Cookies 用于创建/维护 Docusign 的登录会话。

    由于您的应用将存储/使用访问令牌,因此即使没有 cookie 也可能有效。试试吧。

    如果您的应用有时/总是在用户不在场的情况下使用,那么您可以使用 OAuth JWT 流程。在这种情况下,您的应用会(通过 Entra/AD)检查发起操作的用户是否经过身份验证(以及他们是谁)。然后,您的应用使用此信息通过 JWT OAuth 流程获取与用户关联的访问令牌来模拟用户。(浏览器不用于 JWT OAuth。)

    即使用户始终在场,某些开发人员也会使用模拟/JWT 模式。这样做会增加额外的维护/配置复杂性,因为您的管理员需要维护授权 Entra/AD 用户和 Docusign 中定义的匹配用户之间的映射表。

    额外

    Docusign 大约 8 年前开始使用 OAuth JWT 模拟。从那时起,您有两种 JWT 模拟选项。要模拟某人,您现在需要他们的 Docusign UserID(可以查找和缓存)。在使用 OAuth 之前,您只需提供他们的电子邮件即可“扮演”他们。

    使用“服务用户帐户”

    例如,创建一个名为“HR”的 Docusign 用户。您的应用始终会获取访问令牌来模拟 HR 用户。这意味着您的应用发送的所有信封都将由 HR 用户发送,属于该用户,等等。

    缺点是用户 1 可能能够看到用户 2 发送的信封,因为在 Docusign 级别,这两个信封都是由同一个用户(HR)发送的。

    不要向服务用户帐户授予管理员权限!如果您的应用只是发送信封,则服务用户无需成为管理员。

    冒充个人用户账户

    这通常是最好的策略,因为它允许应用程序发送的每个用户的信封都从用户的 Docusign 帐户发送。如果授权 (AD) 系统提供了用户的电子邮件地址,则应用程序可以:

    1. 从电子邮件中查找用户的 Docusign UserID。(以管理员用户身份执行此操作。使用 JWT 创建管理员的访问令牌。然后查找用户以获取用户 ID。)

    2. 通过 JWT Auth 模拟用户。

    电子邮件到用户 ID 的映射不会被改变,因此应用程序可以缓存该映射。(假设所有用户都有自己的 Docusign 用户帐户。)

    • 1

相关问题

  • 将复制活动的序列号添加到 Blob

  • Packer 动态源重复工件

  • 选择每组连续 1 的行

  • 图形 API 调用列表 subscribedSkus 状态权限不足,但已授予权限

  • 根据列值创建单独的 DF 的函数

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    重新格式化数字,在固定位置插入分隔符

    • 6 个回答
  • Marko Smith

    为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会?

    • 2 个回答
  • Marko Smith

    VScode 自动卸载扩展的问题(Material 主题)

    • 2 个回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Martin Hope
    Fantastic Mr Fox msvc std::vector 实现中仅不接受可复制类型 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant 使用 chrono 查找下一个工作日 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor 构造函数的成员初始化程序可以包含另一个成员的初始化吗? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský 为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul C++20 是否进行了更改,允许从已知绑定数组“type(&)[N]”转换为未知绑定数组“type(&)[]”? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann 为什么 {2,3,10} 和 {x,3,10} (x=2) 的顺序不同? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve