• Blazor Server访问Identity Server 4单点登录


    网上有大量Asp.Net Core访问id4单点登录的介绍,但是Blazor Server的不多,我参考网上文章练习了一下,做一个记录。

    参考文章,感谢作者:

    Blazor与IdentityServer4的集成 - towerbit - 博客园 (cnblogs.com)

    Blazor.Server以正确的方式集成Ids4_dotNET跨平台-CSDN博客

    创建Identity Server 4项目

    在控制台进入解决方案目录,安装id4项目模板。

    D:softwaregiteelzid4>dotnet new -i IdentityServer4.Templates

    新建一个测试用的id4项目,带有UI和测试用户。

    D:softwaregiteelzid4>dotnet new is4inmem -n Id4Web

    已成功创建模板“IdentityServer4 with In-Memory Stores and Test Users”。

    新增2个客户端定义

                    new Client()
                    {
                        ClientId="BlazorServer1",
                        ClientName = "BlazorServer1",
                        ClientSecrets=new []{new Secret("BlazorServer1.Secret".Sha256())},
    
                        AllowedGrantTypes = GrantTypes.Code,
                        
                        AllowedCorsOrigins = { "https://localhost:5101" },
                        RedirectUris = { "https://localhost:5101/signin-oidc" },
                        PostLogoutRedirectUris = { "https://localhost:5101/signout-callback-oidc" },
    
                        AllowedScopes = { "openid", "profile", "scope1" }
                    },
    
                    new Client()
                    {
                        ClientId="BlazorServer2",
                        ClientName = "BlazorServer2",
                        ClientSecrets=new []{new Secret("BlazorServer2.Secret".Sha256())},
    
                        AllowedGrantTypes = GrantTypes.Code,
    
                        AllowedCorsOrigins = { "https://localhost:5201" },
                        RedirectUris = { "https://localhost:5201/signin-oidc" },
                        PostLogoutRedirectUris = { "https://localhost:5201/signout-callback-oidc" },
    
                        AllowedScopes = { "openid", "profile", "scope1" }
                    },
    

      

    创建Blazor Server项目

    创建Blazor Server项目。NuGet安装

        <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.9" />

    修改App.razor实现未登录用户自动跳转登录

    @inject IJSRuntime _jsRuntime
    @inject NavigationManager _navManager
    
    <CascadingAuthenticationState>
    
        <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
            <Found Context="routeData">
                <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                    <NotAuthorized>
                        @if (!context.User.Identity.IsAuthenticated)
                        {
                            //如果用户未登录,跳转到Account控制器Login函数,发起登录
                            _jsRuntime.InvokeVoidAsync("window.location.assign", $"account/login?returnUrl={Uri.EscapeDataString(_navManager.Uri)}");
                        }
                        else
                        {
                            <h4 class="text-danger">Sorry</h4>
                            <p>You're not authorized to reach this page.</p>
                            <p>You may need to log in as a different user.</p>
                            <a href="/account/login" class="btn btn-primary">Login</a>
                        }
                    </NotAuthorized>
                </AuthorizeRouteView>
            </Found>
            <NotFound>
                <LayoutView Layout="@typeof(MainLayout)">
                    <p>Sorry, there's nothing at this address.</p>
                </LayoutView>
            </NotFound>
        </Router>
    </CascadingAuthenticationState>
    

      

    修改program默认端口

    public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder
                            .UseUrls("https://*:5101")
                            .UseStartup<Startup>();
                    });
    

      

    修改launchSettings.json默认端口

          "applicationUrl": "https://localhost:5101",

    修改startup添加oidc认证服务

                //默认采用cookie认证方案,添加oidc认证方案
                services.AddAuthentication(options =>
                    {
                        options.DefaultScheme = "cookies";
                        options.DefaultChallengeScheme = "oidc";
                    })
                    //配置cookie认证
                    .AddCookie("cookies")
                    .AddOpenIdConnect("oidc", options =>
                    {
                        //id4服务的地址
                        options.Authority = "https://localhost:5001";
    
                        //id4配置的ClientId以及ClientSecrets
                        options.ClientId = "BlazorServer1";
                        options.ClientSecret = "BlazorServer1.Secret";
    
                        //认证模式
                        options.ResponseType = "code";
    
                        //保存token到本地
                        options.SaveTokens = true;
    
                        //很重要,指定从Identity Server的UserInfo地址来取Claim
                        options.GetClaimsFromUserInfoEndpoint = true;
    
                    });
    

      

    开启认证和授权服务

                app.UseRouting();
    
                //开启认证和授权服务
                app.UseAuthentication();
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
    

      

    添加登录用的MVC控制器AccountController,这个真是Blazor Server的痛点了,非要借助MVC做一次跳转,Net 7是不是能安排解决一下?

    public class AccountController : Controller
        {
            private readonly ILogger _logger;
    
            public AccountController(ILogger<AccountController> logger)
            {
                _logger = logger;
            }
    
            /// <summary>
            /// 跳转到Identity Server 4统一登录
            /// </summary>
            /// <param name="returnUrl">登录成功后,返回之前的网页路由</param>
            /// <returns></returns>
            [HttpGet]
            public IActionResult Login(string returnUrl = "")
            {
                if (string.IsNullOrEmpty(returnUrl))
                    returnUrl = "/";
    
                var properties = new AuthenticationProperties
                {
                    //记住登录状态
                    IsPersistent = true,
    
                    RedirectUri = returnUrl
                };
    
                _logger.LogInformation($"id4跳转登录, returnUrl={returnUrl}");
    
                //跳转到Identity Server 4统一登录
                return Challenge(properties, "oidc");
            }
    
            /// <summary>
            /// 退出登录
            /// </summary>
            /// <param name="returnUrl"></param>
            /// <returns></returns>
            [HttpGet]
            public async Task<IActionResult> Logout()
            {
                var userName = HttpContext.User.Identity?.Name;
    
                _logger.LogInformation($"{userName}退出登录。");
    
                //删除登录状态cookies
                await HttpContext.SignOutAsync("cookies");
    
                var properties = new AuthenticationProperties
                {
                    RedirectUri = "/"
                };
    
                //跳转到Identity Server 4统一退出登录
                return SignOut(properties, "oidc");
            }
    

      

    还要修改startup让系统支持MVC路由。

                app.UseEndpoints(endpoints =>
                {
                    //支持MVC路由,跳转登录
                    endpoints.MapDefaultControllerRoute();
    
                    endpoints.MapBlazorHub();
                    endpoints.MapFallbackToPage("/_Host");
                });
    

      

    在Index页面显示一下登录用户信息

    <AuthorizeView>
        <Authorized>
    
            <p>您已经登录</p>
    
            <div class="card">
                <div class="card-header">
                    <h2>context.User.Claims</h2>
                </div>
                <div class="card-body">
                    <dl>
                        <dt>context.User.Identity.Name</dt>
                        <dd>@context.User.Identity.Name</dd>
                        @foreach (var claim in context.User.Claims)
                        {
                            <dt>@claim.Type</dt>
                            <dd>@claim.Value</dd>
                        }
                    </dl>
                </div>
            </div>
    
            <a class="nav-link" href="Account/Logout">退出登录</a>
        </Authorized>
    
        <NotAuthorized>
            <p>您还没有登录,请先登录</p>
            <a class="nav-link" href="Account/Login">登录</a>
        </NotAuthorized>
    
    </AuthorizeView>
    

      

    给counter页面增加认证要求,这样如果没有登录的状态下,点击counter页面就会触发自动跳转登录

    @attribute [Authorize]

    把id4项目和blazor server项目一起运行,点击BlzWeb1主页的登录,即可跳转到id4登录页面

    输入id4提供的测试账号aclie和密码alice。

    登录成功,跳转回到BlzWeb1主页,看一下用户身份信息。

    可以通过HttpContext获取更多信息。

    修改startup添加服务。

                services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    修改BlzWeb1主页

    @using Microsoft.AspNetCore.Authentication
    @using Microsoft.AspNetCore.Http
    @inject IHttpContextAccessor httpContextAccessor
    
    @if (AuthResult is not null)
            {
                <p>AuthResult.Principal.Identity.Name: <strong>@AuthResult.Principal.Identity.Name</strong></p>
    
                <div class="card">
                    <div class="card-header">
                        <h2>AuthenticateResult.Principal</h2>
                    </div>
                    <div class="card-body">
                        <dl>
                            @foreach (var claim in AuthResult.Principal.Claims)
                            {
                                <dt>@claim.Type</dt>
                                <dd>@claim.Value</dd>
                            }
                        </dl>
                    </div>
                </div>
    
                <div class="card">
                    <div class="card-header">
                        <h2>AuthenticateResult.Properties.Items</h2>
                    </div>
                    <div class="card-body">
                        <dl>
                            @foreach (var prop in AuthResult.Properties.Items)
                            {
                                <dt>@prop.Key</dt>
                                <dd>@prop.Value</dd>
                            }
                        </dl>
                    </div>
                </div>
            }
    
    @code{
        private AuthenticateResult AuthResult;
    
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                AuthResult = await httpContextAccessor.HttpContext.AuthenticateAsync();
    
                StateHasChanged();
            }
        }
    
    }
    

      

    可以看到token等信息。

    但是获取不到context.User.Identity.Name,这也是一个痛点,为什么id4就是不爽快地返回Username呢?

    修改startup可以把id4用户的name字段赋值给User.Identity.Name,然而我想要的是id4用户的Username。

                        //这里是个ClaimType的转换,Identity Server的ClaimType和Blazor中间件使用的名称有区别,需要统一。

                        //User.Identity.Name=JwtClaimTypes.Name

                        options.TokenValidationParameters.NameClaimType = "name";

                        //options.TokenValidationParameters.RoleClaimType = "role";

    有一个鸵鸟办法,就是自己定义的用户class中,让name跟Username保持同一个值。

    获取role则更麻烦,还要转换数据类型,补充添加到cliams,这些最常用的功能都没衔接好,心很累。

    接着创建第二个Blazor Server项目。

    测试验证

    注意这里有坑!

    测试方案一:

    在VS2019同时调试运行id4项目和2个Blazor Server项目,自动打开了3个Edge浏览器窗口。在BlzWeb1网页登录,然后刷新BlzWeb2网页,点击主页的登录按钮,会发现还要再次跳转到id4网页登录,根本没有实现单点登录!为什么会这样!我也不知道。

    百度查资料,没有结果。

    测试方案二:

    后来我改变了一下测试方法,在BlzWeb1浏览器新建一个页卡,然后访问BlzWeb2主页,然后再点击BlzWeb2主页的登录按钮,这次自动登录了。

    然后在BlzWeb1主页退出登录,再次刷新BlzWeb2主页地址栏,它又提示当前是未登录状态了,实现了单点登录。

    如果在测试过程中,反复在两个Edge浏览器登录,退出,很任意导致网页死机,不知道是什么问题。

    查看Edge的cookies,可以看到在同一个浏览器的2个页卡运行的BlzWeb1和BlzWeb2的登录状态相同,共享了cookies,这是单点登录的原理和基础。

    注意,如果部署BlzWeb1和BlzWeb2到云服务器测试,需要共用一个数据保护秘钥,因为Asp.Net Core采用数据保护秘钥加密cookies,要确保2个项目能够互认cookies,详情参见:

    DataProtection设置问题引起不同ASP.NET Core站点无法共享用户验证Cookie - dudu - 博客园 (cnblogs.com)

    DEMO代码地址:https://gitee.com/woodsun/blzid4

  • 相关阅读:
    Python中的生成器与yield
    【爬虫系列】(一)最简单的爬虫
    【刷题笔记】--lintcode木头加工(java)
    使用TaskManager爬取2万条代理IP实现自动投票功能
    开源任务管理平台TaskManager介绍
    数据字典生成工具之旅系列文章导航
    使用工具安装,运行,停止,卸载Window服务
    Quartz Cron表达式 在线生成器
    Oracle .NET Core Beta驱动已出,自己动手写EF Core Oracle
    .net core2.0下Ioc容器Autofac使用
  • 原文地址:https://www.cnblogs.com/sunnytrudeau/p/15145388.html
Copyright © 2020-2023  润新知