如何从c#asp.net中的私有提供商实现OpenID Connect

问题描述

我有一个ASP.NET MVC应用程序,该应用程序需要从私有OpenID Connect(OIDC)提供程序集成OpenID Connect身份验证,并且该流程具有以下步骤:

  1. 用户点击登录

  2. 它将使用以下HTTP GET请求将用户重定向到专用OIDC站点进行身份验证:

    enter image description here

  3. 成功登录私有OIDC网站后,它将重定向回我的网站并获得带有code结果的uri,如下所示:

    enter image description here

  4. 然后我将需要使用上面的code并向私有ODIC令牌端点进行HTTP POST调用,以获得该用户的访问令牌。

因此,我的questions #1是:如何在c#asp.net应用程序中实现此功能

此外,我在邮递员“获取新访问令牌”中尝试了此操作,然后我获得了令牌。

在提供所有参数并单击“请求令牌”后可以看到,它弹出登录窗口,

enter image description here

成功登录后,它会显示令牌

enter image description here

我的questions #2是:与问题#1类似,是否可以在c#asp.net应用程序中实现它? 就像在asp.net mvc应用程序中一样,在第一个图像中添加带有url的链接按钮,当用户单击它时,它将使用code将其重定向回myapp,然后使用此代码来制作 stpe3 中的HTTP POST调用

解决方法

您可以找到一个open source example of this on GitHub。许可证非常宽松,并且有据可查。我已经在各种研讨会和培训中使用了它,因此大多数错误已得到解决。我建议您深入研究。为了完整起见,我将在此处描述一般过程,并以此为基础进行解释。

任何实现OpenID Connect代码流的Web应用程序都将包括两个部分:

  1. 流程的开始和
  2. 回调的处理

执行这两项操作的应用程序称为“客户端”或“依赖方”。此客户端使用OpenID Connect协议与之通信的事物称为OpenID Connect提供程序(OP),通常也称为身份提供程序(IdP)。

客户端实现的第一部分将显示一个包含按钮的视图。此按钮将是典型的“登录”或“登录”按钮。请注意,这是可选的,如果应用程序检测到用户没有会话,则可以立即将用户重定向到OP。但是,鉴于您上面的问题,情况并非如此,客户端将首先呈现一个显示此类按钮的视图。该视图可能看起来像这样:

<div>
    @if(Session.Count == 0) {
        <p>
            This is a demo application to demonstrate the use for OAuth2 
            and OpenID Connect. 
        </p>

        <p>
            Pressing Sign In will redirect you to @ViewData["server_name"] 
            and authorize the application to access your profile info. The 
            data will only be used to demonstrate the possibilities of the 
            OpenID Connect protocol and will not be stored. Be sure to 
            revoke access when you are satisfied.
        </p>
        <div>
            <a href="/login">Sign In</a>
        </div>
    } else {
      // ...
    }
</div>

此视图将由一个非常基本的控制器呈现,该控制器连接到Global.asax.cs中建立的路由配置中。单击登录按钮后,OpenID Connect部件将启动。处理该请求的控制器将仅重定向到OP的授权端点。在最基本的情况下,可能看起来像这样:

public class LoginController : Controller
{
    private static string start_oauth_endpoint = Helpers.Client.Instance.GetAuthnReqUrl();

    public ActionResult Index()
    {
        return Redirect(start_oauth_endpoint);
    }
}

有趣的部分是如何获得授权端点。这可以是硬编码的,可以在Web.config中定义,也可以从OP的元数据中获得。在我上面引用的示例中,它在应用启动时获取OP的元数据。这是在Web应用程序的AppConfig目录中的App_Start中完成的。这将对带有Web.config的发行者ID(位于/.well-known/openid-configuration中)执行HTTP GET请求。之所以要在应用启动时获取此元数据而不是将其全部放入配置中,是为了减少OP和客户端之间的耦合。

在上面的片段中执行的重定向将具有一些重要的查询字符串参数。其中一些将在设计时已知,并将进行硬编码。其他将在Web.config中进行配置。有些将在运行时动态计算。这些在下面列出:

client_id
此MVC Web应用程序的客户端ID。
response_type
OP应该使用的响应类型。在您的情况下,它将始终为code
scope
客户端请求的访问范围。这将至少包括openid
redirect_uri
OP对用户进行身份验证和授权后,应将用户发送到的重定向URI。

其他请求参数也可以发送。为了帮助您确定要发送的内容以及它们对流程的影响,请结帐oauth.tools。这就像“用于OAuth和OpenID Connect的邮递员”。这是梦幻般的;你会爱上它。在那里,您可以使用各种参数来形成各种OAuth和OpenID Connect流。

此重定向到OP后,用户将进行身份验证。用户可能还必须同意客户端访问其受保护资源的权限。无论如何,此后,OP会将用户重定向到回调。这是实施的第二部分。

在这里,我们将有一个CallbackController(或类似的内容)。看起来像这样(以最简单的形式):

public class CallbackController : Controller
{
    public ActionResult Index()
    {
        try
        {
            string responseString = Helpers.Client.Instance
                .GetToken(Request.QueryString["code"]);

            SaveDataToSession(responseString);
        }
        catch (Exception e)
        {
            Session["error"] = e.Message;
        }

        return Redirect("/");
    }
}

此代码段的重要部分是从查询字符串中获取code,并向OP的令牌端点(也通过解析OP的元数据来定位)发出HTTP POST请求。如果成功,它将响应保存在会话中以供以后使用。 GetToken方法将如下所示:

public String GetToken(String code)
{
    var values = new Dictionary<string,string>
    {
        { "grant_type","authorization_code" },{ "client_id",client_id},{ "client_secret",client_secret },{ "code",code },{ "redirect_uri",redirect_uri}
    };


    HttpClient tokenClient = new HttpClient();
    var content = new FormUrlEncodedContent(values);
    var response = tokenClient.PostAsync(token_endpoint,content).Result;

    if (response.IsSuccessStatusCode)
    {
        var responseContent = response.Content;

        return responseContent.ReadAsStringAsync().Result;
    }

    throw new OAuthClientException("Token request failed with status code: " + response.StatusCode);
}

这会将代码发送给OP,并获得访问令牌,ID令牌,以及也许还可以作为刷新令牌的交换令牌。该代码的重要部分是:

  • 内容采用URL编码的 not JSON格式。这是一个常见的错误。
  • 再次包含以前发送的相同重定向URI。这是为了匹配OP上的两个请求。
  • grant_Type总是 authorization_code
  • 客户端以某种方式进行身份验证。在这种情况下,通过在请求中包含与先前发送的client_id表单元素中的机密相同的client_secret
  • 使用的HTTP方法(如上所述)是POST,而不是GET。这也是一个常见错误。

在上面的示例中,我重定向回默认的HomeController。现在,执行if语句的else条件。在此,它可以找到令牌:

<div>
    @if(Session.Count == 0) {
        // ...
    } else {
        @if(Session["id_token"] != null) {
            <div>
                ID Token:<br>
                <pre>@Session["id_token"]</pre>
            </div>
        }

        @if(Session["access_token"] != null) {            
            <div>
                Access Token:<br>            
                <pre>@Session["access_token"]</pre>                
            </div>
        }

        @if(Session["refresh_token"] != null) {
            <div>
                Refresh Token:<br>                
                <pre>@Session["refresh_token"]</pre>
            </div>
        }
    }
</div>

该示例比此示例更为详尽,但希望可以给您一个想法。仔细阅读该文件,检查自述文件,并有乐趣地学习有关OpenID Connect的更多信息!

,

您需要在其他位置添加一些配置。我会尽力展示您需要的所有拼图碎片。
在我的示例中,我将使用IdentityServer4的公共演示版本进行OIDC,以便您可以与工作版本进行比较。

API
在任何控制器(或方法)中,添加[Authorize]属性,因此这将需要有效的身份验证。
如果想更详细地说明用户可以执行的操作,还可以添加策略。像这样:

[Authorize(Policy = "Read")]
[ApiController]
[Route("[controller]")]
public class HelloWorldsController : ControllerBase
{
    [HttpGet]
    public string Get()
    {
        return "Hello,World!";
    }
}

ConfigureServices的{​​{1}}方法中,您需要添加类似的配置,如下所示:

Startup.cs

要编译上述配置,应添加NuGet软件包services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme,options => { options.Authority = "https://demo.identityserver.io"; options.Audience = "api"; }); // In case you want to work with policies services.AddAuthorization(options => { options.AddPolicy("Read",policy => policy.RequireClaim("scope","api")); });

Microsoft.AspNetCore.Authentication.JwtBearer的{​​{1}}方法中,您需要在Configure之前添加Startup.cs

MVC
在任何控制器(或方法)中,添加app.UseAuthentication();属性。只要您的MVC应用程序的用户点击具有此属性的方法,登录过程就会自动触发。
为了说明这一点,我将这个属性添加到方法中:

app.UseAuthorization();

[Authorize]的{​​{1}}方法中,您需要添加类似的配置,

[Authorize]
public async Task<IActionResult> Privacy()
{
    var httpClient = _httpClientFactory.CreateClient("ApiClient");
    var apiResult = await httpClient.SendAsync(
        new HttpRequestMessage(HttpMethod.Get,"/helloworlds"),HttpCompletionOption.ResponseHeadersRead);
    if (apiResult.IsSuccessStatusCode)
    {
        var content = await apiResult.Content.ReadAsStringAsync();
        ViewData.Add("apiResult",content); // Just to demonstrate
    }

    return View();
}

要编译上述配置,应添加NuGet软件包ConfigureServicesStartup.cs

services.AddHttpContextAccessor(); services.AddTransient<BearerTokenHandler>(); services .AddHttpClient("ApiClient",client => { client.BaseAddress = new Uri("https://localhost:5001"); }) .AddHttpMessageHandler<BearerTokenHandler>(); services.AddHttpClient("IDPClient",client => { client.BaseAddress = new Uri("https://demo.identityserver.io"); }); services .AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme,options => { options.Authority = "https://demo.identityserver.io"; options.ClientId = "interactive.confidential"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.SaveTokens = true; options.Scope.Add("api"); }); 的{​​{1}}方法中,您需要在Microsoft.AspNetCore.Authentication.Cookies之前添加Microsoft.AspNetCore.Authentication.OpenIdConnect

由于Configure很大,因此您可以从a GitHub repository复制它。您需要Startup.cs的NuGet包参考。
该存储库还包含您要求的设置的完整工作示例。


最后,您可能希望给用户注销的可能性。
您可以通过在视图中添加链接来做到这一点:

app.UseAuthentication();

与此匹配的控制器方法:

app.UseAuthorization();

应该的。希望您能够跟随所有拼图游戏。
让我知道是否不清楚。

,

设置IdentifyServer4:IdentityServer4是用于ASP.NET的OpenID Connect和OAuth 2.0框架

您可以在此处找到有关如何使用IdentifyServer4的文档: https://identityserver4.readthedocs.io/en/latest/ https://identityserver4.readthedocs.io/en/latest/quickstarts/3_aspnetcore_and_apis.html

IdentityServer4提供的某些功能是:

身份验证即服务

所有应用程序(Web,本机,移动,服务)的集中登录逻辑和工作流。 IdentityServer是OpenID Connect的官方认证实施。

单次登录/退出

在多种应用程序类型上单次登录(注销)。

API的访问控制 为各种类型的客户端发布API的访问令牌,例如服务器到服务器,Web应用程序,SPA和本机/移动应用程序。

联合网关

支持外部身份提供程序,例如Azure Active Directory,Google,Facebook等。这使您的应用程序免受如何连接到这些外部提供程序的详细信息的影响。

关注定制

最重要的部分-IdentityServer的许多方面都可以自定义以满足您的需求。由于IdentityServer是框架,而不是盒装产品或SaaS,因此您可以编写代码以使系统适合您的情况,以适应系统情况。

成熟的开源

IdentityServer使用许可的Apache 2许可证,该许可证允许在其之上构建商业产品。它也是.NET Foundation的一部分,.NET Foundation提供治理和法律支持。 免费和商业支持

,

没有足够的声誉来为 IdentityServer4 的答案添加评论,所以我只在这里提到它。

IS4 将不再免费用于商业用途: The Future of IdentityServer

当前版本 (IdentityServer4 v4.x) 将是我们作为免费开源工作的最后一个版本。我们将继续支持 IdentityServer4,直到 .NET Core 3.1 于 2022 年 11 月结束。

为了继续我们的工作,我们成立了一家新公司 Duende Software,IdentityServer4 将更名为 Duende IdentityServer。 Duende IdentityServer 将包含所有新功能工作,并将面向 .NET Core 3.1 和 .NET 5(以及以后的所有版本)。

此新产品将保持开源,但将提供双重许可(RPL 和商业)。如果您也在做免费的开源工作,RPL(互惠公共许可证)可以让 Duende IdentityServer 免费。如果您在商业场景中使用 Duende IdentityServer,则需要商业许可证。我们提供多种许可 Duende IdentityServer 的方式,以适应不同的公司规模和使用模式。包含 RPL 许可对我们很重要,因为它使我们能够认可并向开源社区和我们的贡献者表示感谢。