Blazor WebAssembly实现经nginx反向代理的IdentityServer4的身份认证

By | 2023年3月18日

在微服务架构的部署拓扑中,比较常见的做法是将身份认证中心(Identity Authority)与其它的微服务一起,置于API Gateway之后,由API Gateway提供统一的端点供客户端程序访问。本文介绍如何将IdentityServer4配置在nginx之后,通过nginx为Blazor WebAssembly提供认证服务。部署拓扑大致如下:

在上图中:

  1. 身份认证中心IdentityServer4配置在/auth的路由下,所有的资源和API端点均通过localhost/auth路径访问
  2. 微服务配置在/service的路由下,并通过内部网络访问IdentityServer4以验证token,本文不涉及微服务配置的相关内容,故上图中标为灰色
  3. nginx Web容器提供Blazor WebAssembly的静态资源
  4. 浏览器通过nginx反向代理,从nginx Web容器下载Blazor WebAssembly资源,运行Web Assembly应用,并使用部署在/auth路径下的IdentityServer4服务实现身份认证与授权(授权暂不介绍)

IdentityServer4的配置

如果兼顾在Visual Studio中对项目进行调试,以及直接在docker中运行整个软件系统两种场景的话,则需要在IdentityServer4中实现两套OIDC Client模型,然后在Blazor WebAssembly中,根据当前环境是开发环境还是生产环境来决定选用哪个Client。比如,下面的两个Client模型,除了ClientId和跳转的URL不同之外,其它的配置都是相同的:

new Client
{
    ClientId = "webApp",
    AllowedGrantTypes = GrantTypes.Code,
    RequirePkce = true,
    RequireClientSecret = false,
    AllowedCorsOrigins = { "https://localhost:9200" },
    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "management.read",
        "management.create"
    },
    RedirectUris = { "https://localhost:9200/authentication/login-callback" },
    PostLogoutRedirectUris = { "https://localhost:9200/authentication/logout-callback" }
},

new Client
{
    ClientId = "webAppProd",
    AllowedGrantTypes = GrantTypes.Code,
    RequirePkce = true,
    RequireClientSecret = false,
    AllowedCorsOrigins = { "https://localhost" },
    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "management.read",
        "management.create"
    },
    RedirectUris = { "https://localhost/authentication/login-callback" },
    PostLogoutRedirectUris = { "https://localhost/authentication/logout-callback" }
}

其次,由于IdentityServer4会被配置在/auth子路由下,因此,我们需要在IdentityServer4项目自动生成的HostingExtensions类的ConfigurePipeline方法中,处理/auth的路由映射:

var fordwardedHeaderOptions = new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
    ForwardLimit = 2
};
fordwardedHeaderOptions.KnownNetworks.Clear();
fordwardedHeaderOptions.KnownProxies.Clear();
app.UseForwardedHeaders(fordwardedHeaderOptions);
app.Map("/auth", app =>
{
    app.UseSerilogRequestLogging();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseIdentityServer();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
        endpoints.MapRazorPages().RequireAuthorization();
    });
});

当然,这部分代码也是在生产环境中,当IdentityServer4认证服务运行于容器中时,才需要做这样的处理,所以,可以使用WebApplication.Environment.IsProduction()方法来判断是否处于生产环境。更进一步,我们可以将/auth子路由的名字作为环境变量进行设置,以便在部署中可以根据需要选择这个子路由的名称(当然,这是需要跟nginx的配置进行搭配的)。

Blazor WebAssembly的配置

在Blazor WebAssembly的wwwroot目录下,有一个appsettings.json文件,它其实与ASP.NET Core Web API类似,可以根据不同的环境对相同的环境变量设置不同的值。同样,当Blazor WebAssembly编译后在容器中运行时,其所在的环境也是生产环境,因此,我们在appsettings.Production.json文件中,对oidc进行配置:

"oidc": {
  "Authority": "https://localhost/auth",
  "ClientId": "webAppProd",
  "ResponseType": "code",
  "DefaultScopes": [
    "openid",
    "profile"
  ],
  "PostLogoutRedirectUri": "authentication/logout-callback",
  "RedirectUri": "authentication/login-callback"
}

可以看到,这里的ClientId需要使用IdentityServer4中对应的ClientId,以便使用正确的跳转路径。当然,可以在appsettings.Development.json文件中,针对调试环境进行配置,使其在Visual Studio调试时也能正常工作。除此之外,WebAssembly不需要做额外的配置,认证与授权这一部分就按照微软官方文档做就可以了。

nginx Web容器的配置

在Web容器中,Nginx的配置如下:

events {
  worker_connections 4096;
}

http {
  server {
    listen                  80;
    listen                  [::]:80;
    server_name             localhost;
    include                 /etc/nginx/mime.types;

    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html    =404;
    }
  }
}

在Blazor WebAssembly的Dockerfile中,会将WebAssembly的编译结果输出到/usr/share/nginx/html目录中:

FROM nginx:1.23.3-alpine AS base
WORKDIR /usr/share/nginx/html
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["Booking.WebApp/Booking.WebApp.csproj", "Booking.WebApp/"]
RUN dotnet restore "Booking.WebApp/Booking.WebApp.csproj"
COPY . .
WORKDIR "/src/Booking.WebApp"
RUN dotnet build "Booking.WebApp.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Booking.WebApp.csproj" -c Release -o /app/publish

FROM base AS final
COPY Booking.WebApp/nginx.conf /etc/nginx/nginx.conf
WORKDIR /usr/share/nginx/html
COPY --from=publish /app/publish/wwwroot .
CMD ["nginx", "-g", "daemon off;"]

nginx反向代理的配置

在nginx反向代理中,需要配置所有对/auth及其子路径的访问,都被路由到IdentityServer4的服务上:

location ~ ^/auth/(.*)$ {
  proxy_http_version 1.1;
  rewrite ^ $request_uri;
  rewrite ^/auth/(.*)$ $1 break;
  proxy_pass http://identity-server/auth/$1;
  
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection keep-alive;
  proxy_set_header Host $http_host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_cache_bypass $http_upgrade;
}
# ...
upstream identity-server {
  server booking-auth-service:9001;
}

当然,当用户访问根路径时,nginx反向代理需要将请求转发到nginx Web容器上:

location / {
  proxy_pass http://booking-web;
}
# ...
upstream booking-web {
  server booking-web:80;
}

在开发一个软件系统的时候,当开发人员需要调试前端代码的时候,往往都会直接将后端服务运行起来,然后使用调试工具对前端代码进行调试,也就是说,在这种场景下,nginx反向代理以及部署于其后的IdentityServer4会直接运行起来,然后前端的Blazor WebAssembly则在调试环境中运行,于是,nginx反向代理和WebAssembly将会运行在不同的端口上(比如nginx反向代理侦听80端口,而在Visual Studio中调试的WebAssembly则运行在9002端口上),其造成的结果是,两者并不同源。IdentityServer4的认证流程是需要Cookie支持的,而在新版本的浏览器中,非同源的Cookie访问是需要HTTPS支持的(也就是需要启用SSL)。下面简要介绍一下如何在nginx上配置并启用SSL。

生成自签证书

在实际生产环境中,应使用认证机构颁发的证书,但此处为了测试,可以使用自签证书来完成系统配置。生成自签名证书过程如下:

  1. 使用文本编辑器,新建一个名为csr.txt的文件,内容类似如下:
    [req]
    default_bits = 2048
    prompt = no
    default_md = sha256
    req_extensions = v3_req
    distinguished_name = dn
    x509_extensions = v3_ca
    
    [ dn ]
    C=CN
    ST=Shanghai
    L=Shanghai
    O=localhost
    OU=localhost
    emailAddress=abc@localhost.com
    CN = localhost
    
    [ v3_req ]
    basicConstraints = CA:FALSE
    keyUsage = digitalSignature, nonRepudiation, keyEncipherment
    
    [ v3_ca ]
    subjectKeyIdentifier   = hash
    authorityKeyIdentifier = keyid:always,issuer:always
    subjectAltName         = @alt_names
    issuerAltName          = issuer:copy
    
    [ alt_names ]
    DNS.1 = localhost
    DNS.2 = localhost.com
    DNS.3 = booking.daxnet.com
  2. 使用openssl命令创建私钥:
    openssl genrsa -des3 -out localhost.key 2048
    openssl rsa -in localhost.key -out localhost.key
  3. 创建证书签名请求:
    openssl req -new -out localhost.csr -key localhost.key -config csr.txt -extensions v3_ca -extensions v3_req
  4. 创建证书:
    openssl x509 -req -in localhost.csr -signkey localhost.key -out localhost.crt -days 3650 -extfile csr.txt -extensions v3_req -extensions v3_ca
  5. 可以使用下面的命令查看证书:
    openssl x509 -in localhost.crt -noout -text

至此,会产生一个名为localhost.key的私钥文件,以及一个名为localhost.crt的证书文件。

在nginx上配置SSL

可以使用下面的设置,在nginx上配置SSL:

http {
  server {
    listen 443 ssl;
    server_name         localhost;
    ssl_certificate     localhost.crt;
    ssl_certificate_key localhost.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    # ...
  }
}

有关nginx中配置SSL的详细信息,请参考官方文档:http://nginx.org/en/docs/http/configuring_https_servers.html

将自签证书安装到系统中

自签证书需要安装到系统中,才能被浏览器所使用。不同的操作系统下,证书安装方式不同,在Windows上,双击证书文件即可将生成的自签证书导入系统中,直接在图形界面上一步步操作即可。在Ubuntu Linux下,可以使用下面的命令完成证书安装:

sudo cp localhost.crt /usr/share/ca-certificates && sudo update-ca-certificates

演示与源代码

参考下面的演示视频:

源代码如下:

https://github.com/daxnet/booking/tree/nginx-proxied

 

(总访问量:270;当日访问量:1)

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据