服务器端 Blazor 中的 OIDC 身份验证

moh*_*chi 7 asp.net-authentication asp.net-core blazor blazor-server-side

如何在服务器端 Blazor 中使用 OIDC 身份验证?

我使用了这个方法,但不知何故它不正确,因为@attribute [AllowAnonymous]实际上不起作用。所以我使用该[Authorized]属性而不是[AllowAnonymous]然后删除RequireAuthenticatedUser,但是OIDC不会将客户端重定向到服务器登录页面。

我查看了Steve Sanderson 的关于 Blazor 中的身份验证和授权的 GitHub 文章,但他没有谈论 OIDC。

这是我的启动课程:

services.AddAuthentication(config =>
{
    config.DefaultScheme = "Cookie";
    config.DefaultChallengeScheme = "oidc";
})
    .AddCookie("Cookie")
    .AddOpenIdConnect("oidc", config =>
    {
        config.Authority = "https://localhost:44313/";
        config.ClientId = "client";
        config.ClientSecret = "secret";
        config.SaveTokens = true;
        config.ResponseType = "code";
        config.SignedOutCallbackPath = "/";
        config.Scope.Add("openid");
        config.Scope.Add("api1");
        config.Scope.Add("offline_access");
    });

services.AddMvcCore(options =>
{
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser() // site-wide auth
        .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
});
Run Code Online (Sandbox Code Playgroud)

Isa*_*aac 22

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

首先,您需要提供一个身份验证质询请求机制,以允许重定向到身份验证代理(例如 IdentityServer)。这只能通过 HttpContext 实现,而 SignalR(Blazor 服务器应用程序)中不提供该功能。为了解决这个问题,我们将添加几个可用 HttpContext 的 Razor 页面。更多内容在答案中...

创建 Blazor 服务器应用程序。

安装包 Microsoft.AspNetCore.Authentication.OpenIdConnect -版本 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>
Run Code Online (Sandbox Code Playgroud)

将 LoginDisplay 组件添加到 MainLayout 组件中,位于 About 锚元素上方,如下所示

<div class="top-row px-4">
    <LoginDisplay />
    <a href="https://learn.microsoft.com/aspnet/" target="_blank">About</a>
</div>
Run Code Online (Sandbox Code Playgroud)

注意:为了将登录和注销请求重定向到 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 } );
    }  
}
Run Code Online (Sandbox Code Playgroud)

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

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

using Microsoft.AspNetCore.Authentication;

public class LogoutModel : PageModel
{
    public async Task<IActionResult> OnGetAsync()
    {
        await HttpContext.SignOutAsync();
        return Redirect("/");
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码将您注销,并将您重定向到 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>
Run Code Online (Sandbox Code Playgroud)

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

using Microsoft.AspNetCore.Authentication.OpenIdConnect; 
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

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");
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

<NotAuthorized>
    @{
        var returnUrl = 
        NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
        NavigationManager.NavigateTo($"login?redirectUri= 
                              {returnUrl}", forceLoad: true);
     }
</NotAuthorized>
Run Code Online (Sandbox Code Playgroud)

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

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