如何通过 IdentityServer4 将 OpenId Connect 添加到 ASP.NET Core 服务器端 Blazor Web 应用程序?

Posted

技术标签:

【中文标题】如何通过 IdentityServer4 将 OpenId Connect 添加到 ASP.NET Core 服务器端 Blazor Web 应用程序?【英文标题】:How to add OpenIdConnect via IdentityServer4 to ASP.NET Core ServerSide Blazor web app? 【发布时间】:2020-04-25 13:58:49 【问题描述】:

我做了以下(它应该工作,但它没有),没有重定向,没有错误,没有什么,它只是显示没有身份验证的页面,我做错了什么?


ASP.NET Core 3.1 Blazor

步骤 1. 安装包 Microsoft.AspNetCore.Authentication.OpenIdConnect

第 2 步。 编辑 Statup.cs

在“ConfigurationServices”下添加

        services.AddAuthentication(options =>
        
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        )
        .AddCookie()
        .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
        
           options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
           options.Authority = "http://localhost:5000";
           options.RequireHttpsMetadata = false; //false for development only
           options.ClientId = "mywebclient";
           options.ResponseType = "code";
           options.UsePkce = true;
           options.Scope.Add("profile");
           options.Scope.Add("offline_access");
           options.SaveTokens = true;
        );

在“配置”下添加

        ...
        services.AddAuthorization();

        app.UseStaticFiles();

        app.UseRouting();


        app.UseAuthentication();
        app.UseAuthorization();

        ....

第 3 步。 将属性 Authorize 添加到 blazor 页面

        @page "/item"
        @attribute [Authorize] 

【问题讨论】:

现在有很多官方文档可用:docs.microsoft.com/en-us/aspnet/core/blazor/security/… 【参考方案1】:

您的代码存在一些问题...主要问题是您的代码没有提供身份验证质询请求机制,无法重定向到身份验证代理,例如 IdentityServer。这仅适用于 HttpContext,它在 SignalR(Blazor 服务器应用程序)中不可用。为了解决这个问题,我们将添加几个 HttpContext 可用的 Razor 页面。更多答案...

以下是该问题的完整且有效的解决方案:

    创建 Blazor 服务器应用。 安装包 Microsoft.AspNetCore.Authentication.OpenIdConnect -Version 3.1.0

    创建一个名为 LoginDisplay (LoginDisplay.razor) 的组件,并将其放置在 Shared 文件夹中。该组件在MainLayout组件中使用

    <AuthorizeView> <Authorized> <a href="logout">Hello, @context.User.Identity.Name !</a> <form method="get" action="logout"> <button type="submit" class="nav-link btn btn-link">Log out</button> </form> </Authorized> <NotAuthorized> <a href="login?redirectUri=/">Log in</a> </NotAuthorized> </AuthorizeView>

    将 LoginDisplay 组件添加到 MainLayout 组件中,就在 About 上方 锚元素,像这样 <div class="top-row px-4"> <LoginDisplay /> <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a> </div>

注意:为了将登录和注销请求重定向到 IdentityServer,我们必须创建两个 Razor 页面,如下所示: 1、创建一个Login Razor页面Login.cshtml(Login.cshtml.cs),并将它们放在Pages文件夹中,如下:

Login.cshtml.cs

using Microsoft.AspNetCore.Authentication;
 using Microsoft.AspNetCore.Authentication.OpenIdConnect;
 using Microsoft.AspNetCore.Authentication.Cookies;
 using Microsoft.IdentityModel.Tokens;

public class LoginModel : PageModel

    public async Task OnGet(string redirectUri)
    
        await HttpContext.ChallengeAsync("oidc", new 
            AuthenticationProperties  RedirectUri = redirectUri  );
      

此代码启动您在 Startup 类中定义的 Open Id Connect 身份验证方案的质询。

    创建一个 Logout Razor 页面 Logout.cshtml (Logout.cshtml.cs) 并将它们也放在 Pages 文件夹中:

    注销.cshtml.cs

    using Microsoft.AspNetCore.Authentication;

    public class LogoutModel : PageModel public async Task<IActionResult> OnGetAsync() await HttpContext.SignOutAsync(); return Redirect("/");

此代码将您注销,将您重定向到 Blazor 应用的主页。

将 App.razor 中的代码替换为以下代码:

@inject NavigationManager NavigationManager

<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
            <NotAuthorized>
                @
                    var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);

                    NavigationManager.NavigateTo($"login?redirectUri=returnUrl", forceLoad: true);

                

            </NotAuthorized>
            <Authorizing>
                Wait...
            </Authorizing>
        </AuthorizeRouteView>
    </Found>
    <NotFound>

        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>

    </NotFound>

</Router>
</CascadingAuthenticationState>

将 Startup 类中的代码替换为以下内容:

using Microsoft.AspNetCore.Authentication.OpenIdConnect; 
using Microsoft.AspNetCore.Authorization; 
using Microsoft.AspNetCore.Mvc.Authorization; 
using System.Net.Http;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Logging;


public class Startup

    public Startup(IConfiguration configuration)
    
        Configuration = configuration;
    

    public IConfiguration Configuration  get; 

    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    
        services.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddAuthorizationCore();
        services.AddSingleton<WeatherForecastService>();

        services.AddAuthentication(sharedOptions =>
        
            sharedOptions.DefaultAuthenticateScheme = 
                 CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultSignInScheme = 
                CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = 
               OpenIdConnectDefaults.AuthenticationScheme;
        )
        .AddCookie()
        .AddOpenIdConnect("oidc", options =>
         
             options.Authority = "https://demo.identityserver.io/";
             options.ClientId = "interactive.confidential.short"; 
             options.ClientSecret = "secret";
             options.ResponseType = "code";
             options.SaveTokens = true;
             options.GetClaimsFromUserInfoEndpoint = true;
             options.UseTokenLifetime = false;
             options.Scope.Add("openid");
             options.Scope.Add("profile");
             options.TokenValidationParameters = new 
                    TokenValidationParameters
                    
                        NameClaimType = "name"
                    ;

             options.Events = new OpenIdConnectEvents
             
               OnAccessDenied = context =>
                        
                          context.HandleResponse();
                          context.Response.Redirect("/");
                          return Task.CompletedTask;
                       
       ;
 );




  // This method gets called by the runtime. Use this method to configure 
     the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    
        if (env.IsDevelopment())
        
            app.UseDeveloperExceptionPage();
        
        else
        
            app.UseExceptionHandler("/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();


        app.UseEndpoints(endpoints =>
        
            endpoints.MapBlazorHub();
            endpoints.MapFallbackToPage("/_Host");
        );
    

重要提示:在上面的所有代码示例中,您必须根据需要添加 using 语句。其中大部分是默认提供的。此处提供的使用是启用身份验证和授权流程所必需的。

运行您的应用程序,单击登录按钮进行身份验证。您将被重定向到允许您执行 OIDC 登录的 IdentityServer 测试服务器。您可以输入用户名:bob 和密码bob,点击确定按钮后,您将被重定向到您的主页。另请注意,您可以使用外部登录提供商 Google(尝试一下)。请注意,在您使用身份服务器登录后,LoginDisplay 组件会显示字符串“Hello,”。

注意:当你在试验你的应用时,你应该清除浏览数据,如果你想被重定向到身份服务器的登录页面,否则你的浏览器可能会使用缓存的数据。请记住,这是一种基于 cookie 的授权机制...

请注意,在这里创建登录机制不会使您的应用程序比以前更安全。任何用户都无需登录即可访问您的网络资源。为了保护您网站的某些部分,您还必须实施授权,通常,经过身份验证的用户有权访问受保护的资源,除非实施其他措施,例如角色、策略等。以下是演示如何您可以保护您的 Fetchdata 页面免受未经授权的用户的攻击(同样,经过身份验证的用户被视为有权访问 Fetchdata 页面)。

    在 Fetchdata 组件页面的顶部,为 Authorize 属性添加 @attribute 指令,如下所示:@attribute [Authorize] 当未经身份验证的用户尝试访问 Fetchdata 页面时,会执行 AuthorizeRouteView.NotAuthorized 委托属性,因此我们可以添加一些代码将用户重定向到同一身份服务器的登录页面进行身份验证。

    NotAuthorized 元素中的代码如下所示:

    <NotAuthorized> @ var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); NavigationManager.NavigateTo($"login?redirectUri= returnUrl", forceLoad: true); </NotAuthorized>

这会检索您尝试访问的最后一个页面的 url,即 Fetchdata 页面,然后导航到执行密码质询的 Login Razor 页面,即,用户被重定向到身份服务器的登录页面进行身份验证。

用户通过身份验证后,他被重定向到 Fetchdata 页面。

祝你好运……

【讨论】:

哇,感谢您的详细回复和全套说明:) 你赢了! 你将如何处理访问令牌过期? 这有点问题,因为 Signalr 没有 HttpContext 和 cookie 的概念。我会尝试找出解决方案,并让您知道。【参考方案2】:

Blazor 中尚无对 IdentityServer 和 OIDC 流的官方支持。我已经在 aspnetcore github 上 opened several issues 但他们总是在没有正确答案的情况下关闭。

目前我发现的最佳来源是以下博客:

https://mcguirev10.com/2019/12/15/blazor-authentication-with-openid-connect.html

https://wellsb.com/csharp/aspnet/blazor-httpclientfactory-and-web-api/

【讨论】:

【参考方案3】:

它只是显示没有身份验证的页面,我做错了什么

很可能您没有为您的应用添加 AuthorizeRouteView。请注意,@attribute [Authorize] 只是为该页面组件添加[AuthorizeAttribute] 的指令。要启用路由授权,您需要:

    在最顶层添加&lt;CascadingAuthenticationState&gt; 添加&lt;AuthorizeRouteView&gt; 以启用路由授权。见official docs 使用&lt;AuthorizeView&gt; 进行普通组件授权。添加&lt;NotAuthorized&gt; 以在未授权时显示组件。并在授权用户时使用Authorizing 显示组件。见official docs

例如,您 BlazorApp.razor 可能看起来像:

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" >
                <NotAuthorized>
                    You're not allowed!
                </NotAuthorized>
                <Authorizing>
                    <h1>Authentication in progress</h1>
                </Authorizing>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

【讨论】:

以上是关于如何通过 IdentityServer4 将 OpenId Connect 添加到 ASP.NET Core 服务器端 Blazor Web 应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过自省验证 IdentityServer4 访问令牌

如何将 IdentityServer4 设置为外部身份提供者

IdentityServer4 - 如何从另一个 ApiResource 调用一个 ApiResource

通过 Azure API 管理生成访问令牌并针对 IdentityServer4 进行验证

如何在 IdentityServer4 中添加自定义声明以访问令牌? [关闭]

如何在 IdentityServer4 上添加电话号码声明