• Blazor WebAssembly项目访问Identity Server 4


    Blazor WebAssembly项目访问Identity Server 4

    Identity Server系列目录

    1. Blazor Server访问Identity Server 4单点登录 - SunnyTrudeau - 博客园 (cnblogs.com)
    2. Blazor Server访问Identity Server 4单点登录2-集成Asp.Net角色 - SunnyTrudeau - 博客园 (cnblogs.com)
    3. Blazor Server访问Identity Server 4-手机验证码登录 - SunnyTrudeau - 博客园 (cnblogs.com)
    4. Blazor MAUI客户端访问Identity Server登录 - SunnyTrudeau - 博客园 (cnblogs.com)
    5. Identity Server 4项目集成Blazor组件 - SunnyTrudeau - 博客园 (cnblogs.com)
    6. Identity Server 4退出登录自动跳转返回 - SunnyTrudeau - 博客园 (cnblogs.com)
    7. Identity Server通过ProfileService返回用户角色 - SunnyTrudeau - 博客园 (cnblogs.com)
    8. Identity Server 4返回自定义用户Claim - SunnyTrudeau - 博客园 (cnblogs.com)
    9. Blazor Server获取Token访问外部Web Api - SunnyTrudeau - 博客园 (cnblogs.com)
    10. Blazor Server通过RefreshToken更新AccessToken - SunnyTrudeau - 博客园 (cnblogs.com)

    Blazor WebAssembly项目提供了丰富的认证和授权支持,参考微软官网两篇文章,编写一个Blazor WebAssembly项目访问之前已经建好的Identity Server 4服务器。

    https://docs.microsoft.com/zh-cn/aspnet/core/blazor/security/webassembly/standalone-with-authentication-library?view=aspnetcore-6.0&tabs=visual-studio

    https://docs.microsoft.com/zh-cn/aspnet/core/blazor/security/webassembly/hosted-with-identity-server?view=aspnetcore-6.0&tabs=visual-studio

    创建Blazor WebAssembly项目

    新建Blazor WebAssembly项目WebAsmOidc,身份验证类型=个人账户,无托管主机。框架自动引用认证相关的NuGet类库,自动生成认证相关的文件,改一下就能用。

    appsettings.Development.json改为访问已有的Identity Server 4服务器 

      "Local": {
        "Authority": "https://localhost:5001/",
        "ClientId": "WebAssemblyOidc",
        "DefaultScopes": [
          "scope1"
        ],
        "PostLogoutRedirectUri": "/",
        "ResponseType": "code"
      }

    launchSettings.json改一下项目的端口

          "applicationUrl": "https://localhost:5801;http://localhost:5800",

    AspNetId4Web项目增加Blazor WebAssembly项目的客户端配置,因为WebAssembly代码在浏览器里边可以看到,没有必要用秘钥了 

    // Blazor WebAssembly客户端
                    new Client
                    {
                        ClientId = "WebAssemblyOidc",
                        ClientName = "WebAssemblyOidc",
                        RequireClientSecret = false,
    
                        AllowedGrantTypes = GrantTypes.Code,
    
                        AllowedScopes ={ "openid", "profile", "scope1", },
                        
                        //网页客户端运行时的URL
                        AllowedCorsOrigins = {
                            "https://localhost:5801",
                        },
    
                        //登录成功之后将要跳转的网页客户端的URL
                        RedirectUris = {
                            "https://localhost:5801/authentication/login-callback",
                        },
    
                        //退出登录之后将要跳转的网页客户端的URL
                        PostLogoutRedirectUris = {
                            "https://localhost:5801",
                        },
                    },

    同时运行AspNetId4Web项目、WebAsmOidc项目,在WebAsmOidc项目登录,可以跳转到Identity Server 4登录页面,并成功返回。

    重新映射用户角色

    参考微软官网的例子,把角色数组拆分为单个角色。

    /// <summary>
        /// 自定义用户工厂
        /// 在 Client 应用中,创建自定义用户工厂。 Identity 服务器在一个 role 声明中发送多个角色作为 JSON 数组。 单个角色在该声明中作为单个字符串值进行发送。 
        /// 工厂为每个用户的角色创建单个 role 声明。
        /// https://docs.microsoft.com/zh-cn/aspnet/core/blazor/security/webassembly/hosted-with-identity-server?view=aspnetcore-6.0&tabs=visual-studio#name-and-role-claim-with-api-authorization
        /// </summary>
        public class CustomUserFactory : AccountClaimsPrincipalFactory<RemoteUserAccount>
        {
            public CustomUserFactory(IAccessTokenProviderAccessor accessor)
                : base(accessor)
            {
            }
    
            public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
                RemoteUserAccount account,
                RemoteAuthenticationUserOptions options)
            {
                var user = await base.CreateUserAsync(account, options);
    
                if (user.Identity.IsAuthenticated)
                {
                    var identity = (ClaimsIdentity)user.Identity;
                    var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();
    
                    if (roleClaims.Any())
                    {
                        foreach (var existingClaim in roleClaims)
                        {
                            identity.RemoveClaim(existingClaim);
                        }
    
                        var rolesElem = account.AdditionalProperties[identity.RoleClaimType];
    
                        if (rolesElem is JsonElement roles)
                        {
                            if (roles.ValueKind == JsonValueKind.Array)
                            {
                                foreach (var role in roles.EnumerateArray())
                                {
                                    identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
                                }
                            }
                            else
                            {
                                identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
                            }
                        }
                    }
                }
    
                return user;
            }

    Program.cs注册工厂,注意角色的名称也要转换 

    builder.Services.AddOidcAuthentication(options =>
    {
        // Configure your authentication provider options here.
        // For more information, see https://aka.ms/blazor-standalone-auth
        builder.Configuration.Bind("Local", options.ProviderOptions);
    
        //这里是个ClaimType的转换,Identity Server的ClaimType和Blazor中间件使用的名称有区别,需要统一。
        options.UserOptions.NameClaim = "name";
        options.UserOptions.RoleClaim = "role";
    })
        .AddAccountClaimsPrincipalFactory<CustomUserFactory>();

    FetchData.razor页面增加认证要求 

    @using Microsoft.AspNetCore.Authorization
    @attribute [Authorize(Roles = "Admin")]

    再次运行2个项目,测试aliceAdmin权限,可以访问FetchData.razor页面,bob不行。

    获取Access Token访问资源Web Api

    参考微软官网定义,在Program.cs访问资源服务器的HttpClient参数,框架会自动获取Access TokenHttpClientHeader

    https://docs.microsoft.com/zh-cn/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-6.0#configure-the-httpclient-handler

    AuthorizationMessageHandler 是一个 DelegatingHandler,用于将访问令牌附加到传出 HttpResponseMessage 实例。 令牌是使用由框架注册的 IAccessTokenProvider 服务获取的。

    可以使用 ConfigureHandler 方法将 AuthorizationMessageHandler 配置为授权的 URL、作用域和返回 URLConfigureHandler 配置此处理程序,以使用访问令牌授权出站 HTTP 请求。 仅当至少有一个授权 URL 是请求 URI (HttpRequestMessage.RequestUri) 的基 URI 时,才附加访问令牌。  

    builder.Services.AddHttpClient("MyWebApi",
            client => client.BaseAddress = new Uri("https://localhost:5601"))
        .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
        .ConfigureHandler(
            authorizedUrls: new[] { "https://localhost:5601" },
            scopes: new[] { "scope1" }));
    
    builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
        .CreateClient("MyWebApi"));

    FetchData.razor页面改为访问MyWebApi项目获取数据 

    protected override async Task OnInitializedAsync()
        {
            //forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
            try
            {
                forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
            }
            catch (AccessTokenNotAvailableException exception)
            {
                exception.Redirect();
            }
        }

    资源Web Api配置跨域共享

    同时运行AspNetId4Web项目、MyWebAPi项目、WebAsmOidc项目,用管理员alice登录,访问FetchData.razor页面,提示跨域访问错误。

    blazor.webassembly.js:1 info: System.Net.Http.HttpClient.MyWebApi.ClientHandler[100]

          Sending HTTP request GET https://localhost:5601/WeatherForecast

    fetchdata:1

            

           Access to fetch at 'https://localhost:5601/WeatherForecast' from origin 'https://localhost:5801' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

    :5601/WeatherForecast:1

    参考微软官网给MyWebApi项目增加跨域共享配置

    https://docs.microsoft.com/zh-cn/aspnet/core/blazor/call-web-api?view=aspnetcore-6.0&pivots=webassembly#call-web-api-example 

    app.UseCors(policy =>
        policy.WithOrigins("https://localhost:5801")
        .AllowAnyHeader()
        .AllowAnyMethod()
        .AllowCredentials());

    Fiddler抓包看一下,WebAsmOidc项目访问了2MyWebAPi项目。

    第一次是OPTIONS方法,获取MyWebAPi项目支持的功能。 

    OPTIONS https://localhost:5601/WeatherForecast HTTP/1.1
    Host: localhost:5601
    Connection: keep-alive
    Accept: */*
    Access-Control-Request-Method: GET
    Access-Control-Request-Headers: authorization
    Origin: https://localhost:5801
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39
    Sec-Fetch-Mode: cors
    Sec-Fetch-Site: same-site
    Sec-Fetch-Dest: empty
    Referer: https://localhost:5801/
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
    
    HTTP/1.1 204 No Content
    Date: Wed, 16 Mar 2022 12:13:16 GMT
    Server: Kestrel
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Headers: authorization
    Access-Control-Allow-Methods: GET
    Access-Control-Allow-Origin: https://localhost:5801

    第二次才是查询数据。

    GET https://localhost:5601/WeatherForecast HTTP/1.1
    Host: localhost:5601
    Connection: keep-alive
    sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Microsoft Edge";v="99"
    authorization: Bearer eyJ……ihg
    sec-ch-ua-mobile: ?0
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39
    sec-ch-ua-platform: "Windows"
    Accept: */*
    Origin: https://localhost:5801
    Sec-Fetch-Site: same-site
    Sec-Fetch-Mode: cors
    Sec-Fetch-Dest: empty
    Referer: https://localhost:5801/
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
    
    HTTP/1.1 200 OK
    Content-Type: application/json; charset=utf-8
    Date: Wed, 16 Mar 2022 12:13:17 GMT
    Server: Kestrel
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Origin: https://localhost:5801
    Transfer-Encoding: chunked
    
    1ee
    [{"date":"2022-03-17T20:13:18.0963201+08:00","temperatureC":13,"temperatureF":55,"summary":"Cool"},{"date":"2022-03-18T20:13:18.0966368+08:00","temperatureC":24,"temperatureF":75,"summary":"Balmy"},{"date":"2022-03-19T20:13:18.0966403+08:00","temperatureC":-17,"temperatureF":2,"summary":"Mild"},{"date":"2022-03-20T20:13:18.0966405+08:00","temperatureC":15,"temperatureF":58,"summary":"Chilly"},{"date":"2022-03-21T20:13:18.0966406+08:00","temperatureC":10,"temperatureF":49,"summary":"Mild"}]
    0

    问题

    Blazor WebAssembly项目访问跨域的资源Web Api配置比较麻烦,这是由浏览器安全机制规定的,简单的Blazor WebAssembly项目最好还是配合托管主机一起使用,网页客户端只访问配套的托管主机服务端,对于第三方资源Web Api也通过托管主机中转,托管主机起到类似网关的作用。托管主机是后台服务器,不受浏览器跨域访问的约束。这样网页客户端的HttpClient配置比较简单,资源Web Api也不用配置跨域共享,当然这个会牺牲性能,有利有弊。

    访问托管主机的简单配置:

    builder.Services.AddHttpClient("MyWebApi",
            client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
        .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
    
    builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
        .CreateClient("MyWebApi"));

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

  • 相关阅读:
    CodeForces 620D Professor GukiZ and Two Arrays 双指针
    模板汇总 —— 最大团
    CodeForces 1105E Helping Hiasat 最大独立集
    CodeForces 925 C Big Secret
    CodeForces 979 D Kuro and GCD and XOR and SUM
    CodeForces 665E Beautiful Subarrays 字典树
    CodeForces 723F st-Spanning Tree
    CodeForces 103D Time to Raid Cowavans 询问分块
    博客园添加访问次数统计
    oracle转mysql总结
  • 原文地址:https://www.cnblogs.com/sunnytrudeau/p/16014314.html
Copyright © 2020-2023  润新知