zoukankan      html  css  js  c++  java
  • blazor wasm访问非本地的restful service

    准备工作

    blazor wasm正式版发布了!在尝试使用的过程中,发现几个小坑,跟大家分享一下,希望有所帮助。

    我是通过keycloak来保护blazor和service的,如何保护service请参考我之前的随笔,此处不再重复说明。

    启动一个新的wasm项目,包含身份认证,但不是hosted(web前端和service在同一个网址),也就是最常见的web和service分别开发和分别部署的场景,使用如下命令新建项目:

    dotnet new blazorwasm -o {存放路径} --au Individual

    自动生成客户端代码

    由于我的service采用了openapi,因此在wasm项目中,可以借助openapi代码生成工具,自动生成客户端代码,节约开发时间。这个步骤不是必须的,但是个人建议这样做。

    首先,如果没有安装工具,先安装工具:

    dotnet tool install -g microsoft.dotnet-openapi

    然后,运行命令生成客户端代码:

    dotnet openapi add url http://localhost:5000/swagger/v1/swagger.json --output-file Weather.json

    以上工具只是将json文件注册到项目中,项目中还必须安装NSWag.CodeGeneration.CSharp,才能正常生成客户端代码,为了方便大家参考,将所有需要引用的包都写在这儿了。

    <Project Sdk="Microsoft.NET.Sdk.Web">
      <PropertyGroup>
        <TargetFramework>netstandard2.1</TargetFramework>
        <RazorLangVersion>3.0</RazorLangVersion>
      </PropertyGroup>
      <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" />
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="3.2.0" />
        <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.4" />
        <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
        <PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5" />
        <PackageReference Include="NSwag.CodeGeneration.CSharp" Version="13.5.0" />
        <PackageReference Include="System.Net.Http.Json" Version="3.2.0" />
      </ItemGroup>
      <ItemGroup>
        <OpenApiReference Include="Weather.json" SourceUrl="http://localhost:5000/swagger/v1/swagger.json" />
      </ItemGroup>
    </Project>

    为了保证代码能够顺利生成,可以运行dotnet build命令,如果成功,Obj目录会出现一个WeatherClient.cs文件,其中包含了model和service。因为他使用了Newtonsoft.Json,所以要添加这个包的引用。

    配置oidc

    模板包含了oidc的基本配置,修改配置文件中的地址和客户端名称即可连接到openid服务。

    实际操作中遇到几个坑:

    1. 无法获得access token或者json解析错误
    2. 无法获得用户名
    3. 访问service时,无法自动添加acess token到请求头部

    无法获得access token

    主要是app默认采用implicit认证流程,keycloak默认没有开启该流程,而且开启后,会无法解析json。

    解决方法,修改program.cs,在builder.Services.AddOidcAuthentication中添加如下代码 

    options.ProviderOptions.ResponseType = "code";
    修改认证流程为标准流程。

    无法获得用户名

    keycloak使用preferred_username发送用户名信息,而.net默认需要name字段,两边不匹配。

    解决方法,要么修改这边,要么修改那边。

    方法1:修改keycloak的client scopes - profile - mappers - username,修改"Token Claim Name"的内容为"name"

    方法2:在builder.Services.AddOidcAuthentication中添加如下代码 

    options.UserOptions.NameClaim = "preferred_username";

    访问service时,无法自动添加acess token到请求头部

    这是最大的一个坑。

    官方模板和文档中,总是在说如何访问自己hosted的service,但是实际使用中,app和service往往在不同的服务器上,官方文档最这种情况作了说明,但是如果不很细致的看文档,就容易理解错。

    最主要的一点就是:BaseAddressAuthorizationMessageHandler只能给本地地址的请求加token,并不能处理不同服务器的情况。

    下面是我的解决方法:

     1 var clientName = "BlazorWithIdentity.ServerAPI";
     2 var baseUrl = builder.Configuration.GetValue<string>("ApiBaseUrl");
     3 
     4 builder.Services.AddHttpClient(clientName, client => client.BaseAddress = new Uri(baseUrl))
     5                 .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
     6                     .ConfigureHandler(new [] { baseUrl }));
     7             
     8 builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>()
     9                 .CreateClient(clientName));
    10 
    11 builder.Services.AddTransient<WeatherClient>(s => 
    12                 new WeatherClient(null, s.GetRequiredService<IHttpClientFactory>().CreateClient(clientName)));

    4行:注入名称为clientName的客户端,并且设置handler,对于来自baseUrl的所有请求自动添加Auth头;

    8行:注入HttpClient,在页面上通过 @inject HttpClient Http,就可以使用自带Auth头部的HttpClient了;

    11行:注入自动生成的WeatherClient,使用4行的HttpClient

    页面调用service访问数据

    经过前面的各种配置,页面访问数据就很简单了,如下:

    @page "/fetchdata"
    @using Microsoft.AspNetCore.Authorization
    @inject WeatherClient Client
    @attribute [Authorize]
    
    <h1>Weather forecast</h1>
    
    <p>This component demonstrates fetching data from the server.</p>
    
    @if (forecasts == null)
    {
        <p><em>Loading...</em></p>
    }
    else
    {
        <table class="table">
            <thead>
                <tr>
                    <th>Date</th>
                    <th>Temp. (C)</th>
                    <th>Temp. (F)</th>
                    <th>Summary</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var forecast in forecasts)
                {
                    <tr>
                        <td>@forecast.Date.Date.ToShortDateString()</td>
                        <td>@forecast.TemperatureC</td>
                        <td>@forecast.TemperatureF</td>
                        <td>@forecast.Summary</td>
                    </tr>
                }
            </tbody>
        </table>
    }
    
    @code {
        private IEnumerable<WeatherForecast> forecasts;
    
        protected override async Task OnInitializedAsync()
        {     
            forecasts = await Client.WeatherForecastAsync();
        }
    }

    怎么样,是不是很简洁的代码?

  • 相关阅读:
    body中的font属性和color属性
    div 的position属性
    border属性
    html header标签
    关于web.config中<customErrors>节点说明
    STL 小白学习(1) 初步认识
    sin n次方 x 的降幂公式
    查找结构体数组中的人名是否匹配
    将结构体数组中内容以文件形式的导出
    python 小白学习(1)
  • 原文地址:https://www.cnblogs.com/wjsgzcn/p/12936257.html
Copyright © 2011-2022 走看看