10. 【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--微服务基础工具与技术--Ocelot 网关--认证
在微服务架构中,通过在网关层实现身份认证、权限校验和数据加密,可以有效防范恶意攻击和非法访问,保障内部服务安全。采用JWT、OAuth等主流认证机制,使每次请求均经过严格验证,降低安全漏洞风险。同时,统一的安全管理还便于监控和日志审计,及时发现异常行为并做出响应。完善安全策略不仅增强系统稳定性,还能构建可靠访问环境,切实保护企业数据安全。
一、JWT认证详解
1.1 JWT简介与工作原理
JSON Web Token(JWT)是一种开放标准(RFC 7519),用于在网络环境中以紧凑、自包含的方式安全传递信息。它基于JSON格式,并通过数字签名保证数据完整性与真实性,广泛应用于身份验证和信息交换。JWT由三部分组成:头部(Header)、载荷(Payload)和签名(Signature),并以“.”分隔。头部包含令牌类型及签名算法,如HS256或RSA,经过Base64Url编码后形成第一部分。载荷存储标准声明(如发行者 iss
、过期时间 exp
、主题 sub
)和自定义数据,用于传递身份信息,但未加密,不应存敏感数据。签名部分使用密钥对前两部分加密,确保数据未被篡改。
JWT的主要优势是无状态,无需服务器存储会话信息,适用于分布式系统和负载均衡。其轻量特性使其易于在HTTP请求头中传输,同时基于JSON格式,具备跨平台兼容性,方便不同系统统一认证。JWT常与OAuth2、OpenID Connect结合,用于安全授权,并内置过期机制(exp)防止令牌滥用。在微服务架构中,各服务节点只需验证JWT即可判定请求合法性,提升系统扩展性。
使用JWT时需确保密钥安全,防止令牌泄露,并选择合适的签名算法(如HS256或RS256)。可结合令牌黑名单、短生命周期策略等增强安全性,防止令牌被截获后重放。
1.2 JWT在Ocelot中的应用
在Ocelot网关中,JWT实现了高效的用户身份认证与授权。客户端在向网关发起请求时,会在HTTP请求头中携带JWT(格式为“Bearer {token}”)。Ocelot作为统一入口,会拦截请求并通过认证中间件解析、验证令牌,包括检查令牌的存在性、格式、签名、过期时间等。验证成功后,Ocelot将请求转发至后端微服务,否则返回未授权响应。这种机制确保只有合法认证的请求能进入系统,同时便于集中管理和安全监控。
JWT的生成与验证通常由认证服务器(如IdentityServer)负责。当用户成功认证后,服务器使用指定算法(如HS256或RS256)生成JWT,其中包含头部(类型与算法)、载荷(用户标识、权限、过期时间等)和签名(用于数据完整性保护)。生成的JWT返回给客户端,并在后续请求中携带。Ocelot接收请求后,通过密钥或公钥验证JWT,解码并校验签名及过期时间,确保请求合法。一旦通过,网关允许请求进入微服务网络,从而实现安全、高效的身份认证管理。
二、使用IdentityServer进行认证
2.1 IdentityServer简介
IdentityServer是一个基于.NET的开源身份认证与授权框架,主要用于单点登录、API保护和安全令牌管理。它帮助开发者在微服务架构中构建统一的认证中心,简化身份验证和授权流程。
作为安全令牌服务(STS),IdentityServer生成JWT等令牌,在微服务间传递用户信息和权限。它支持单点登录(SSO),避免重复认证,并允许开发者灵活配置客户端、API资源和授权范围。其支持授权码、隐式、混合、客户端凭据、资源所有者密码等多种认证流程,并内置令牌管理、刷新、撤销等安全机制,确保认证体系安全稳定。
IdentityServer基于OAuth2和OpenID Connect协议,OAuth2负责授权,允许第三方应用安全访问资源,而OIDC扩展OAuth2,引入ID Token,实现用户身份验证。IdentityServer提供访问令牌管理,并支持用户信息查询接口,确保跨系统认证的便捷性和安全性。通过标准化流程,IdentityServer提升了客户端兼容性,简化系统集成,使企业能够构建高效、可信的身份认证体系。
2.2 IdentityServer与JWT认证的关系
IdentityServer与JWT认证关系密切,它通过JWT(JSON Web Token)作为身份验证和授权的核心机制,为分布式系统提供安全、无状态的令牌管理。IdentityServer作为OAuth2和OpenID Connect的实现,负责用户身份验证,并在授权后颁发JWT令牌,供客户端访问受保护资源时使用。
在身份认证过程中,用户通过IdentityServer验证身份后,服务器会生成包含用户信息、权限范围等数据的JWT,并返回给客户端。客户端在后续请求中,将该JWT附加到HTTP请求头中,API网关或后端服务通过验证JWT签名和有效期,确保请求合法性和安全性。
JWT的无状态特性使得服务端无需存储会话信息,提升了系统的扩展性。IdentityServer通过OAuth2支持JWT的生成、刷新和撤销,使得身份认证和授权更加标准化、安全和高效。综合而言,IdentityServer利用JWT实现安全的身份认证和授权管理,确保微服务架构下的高效通信与访问控制。
2.3 集成步骤
在Ocelot网关中集成IdentityServer主要涉及身份认证与授权配置,确保所有经过网关的请求都需要进行身份验证。
-
搭建IdentityServer
在独立的认证服务器上搭建 IdentityServer4,配置客户端、API资源、授权范围(Scopes),确保IdentityServer能够正确颁发 JWT令牌,并支持OAuth2或OpenID Connect协议。 -
配置Ocelot网关
在ocelot.json
配置文件中,配置要网关路由,相关内容已经在ocelot 路由文章中已经讲解了,这里就不在重复讲解。 -
在Ocelot中启用认证
首先在Program.cs
中,添加JWT身份验证:builder.Services.AddAuthentication("Bearer").AddJwtBearer("Bearer", options =>{options.Authority = "http://localhost:5000"; // IdentityServer地址options.RequireHttpsMetadata = false;options.Audience = "api1"; // 资源API});
然后注册Ocelot网关:
builder.Services.AddOcelot();
三、案例演示
我们根据上一小节的Ocelot网关中集成IdentityServer的步骤来实现一个简单的例子。
新建解决方案OcelotIdentityServerDemo,并在这个解决方案下新建三个Web Api类库:Weather、Authentication和Gateway。Weather是业务服务,用来获取天气,Authentication作为认证服务器使用,Gateway作为网关使用。
3.1 Authentication 认证服务
-
引入IdentityServer包
打开命令行切换到 Authentication Web Api 类库所在文件夹,执行如下命令:
dotnet add package OpenIddict.AspNetCore dotnet add package OpenIddict.EntityFrameworkCore dotnet add package OpenIddict.Validation.AspNetCore
在上面的命令中,我们在类库中添加了
OpenIddict.AspNetCore
、OpenIddict.EntityFrameworkCore
和OpenIddict.Validation.AspNetCore
。这三个包的作用如下:- OpenIddict.AspNetCore
这个包将 OpenIddict 与 ASP.NET Core 无缝集成,提供构建认证服务器所需的各类终结点(如 token 颁发端点)和中间件支持。它简化了配置授权流程、处理请求、签发和撤销令牌等工作,使开发者能快速搭建符合 OAuth 2.0 和 OpenID Connect 标准的认证服务。 - OpenIddict.EntityFrameworkCore
该包利用 Entity Framework Core 作为 OpenIddict 的数据存储解决方案。它提供了必要的实体映射和数据库上下文配置,用于持久化应用信息、授权记录、令牌数据等安全相关的数据。通过这一集成,开发者可以方便地管理和查询认证数据,同时享受 EF Core 提供的数据迁移和操作功能。 - OpenIddict.Validation.AspNetCore
这个包主要用于在 ASP.NET Core 环境中对由 OpenIddict 颁发的令牌进行验证。它将 OpenIddict 的令牌验证逻辑集成到 ASP.NET Core 的身份认证中间件中,确保每个访问受保护资源的请求都能自动校验令牌的有效性和完整性,从而保障 API 的安全访问。
- OpenIddict.AspNetCore
-
配置Authentication服务
在
Program.cs
中添加 OpenIddict 服务,代码如下:builder.Services.AddOpenIddict().AddCore(options =>{// 使用 EntityFrameworkCore作为数据源options.UseEntityFrameworkCore().UseDbContext<IdentityServerDbContext>();}).AddServer(options =>{// 设置令牌端点options.SetTokenEndpointUris("/connect/token");// 启用密码模式options.AllowPasswordFlow() // 开启密码模式.AllowClientCredentialsFlow().AllowRefreshTokenFlow();// 开启刷新令牌options.RegisterScopes("api");// 使用开发环境下的临时密钥(生产环境请使用持久化证书)options.AddDevelopmentEncryptionCertificate().AddDevelopmentSigningCertificate();options.AddSigningKey(new SymmetricSecurityKey(Convert.FromBase64String("SguGSpvaRLMwnmnxiBHRdSxRpBDSiD8+8J1qp1czuD8=")));options.AddEncryptionKey(new SymmetricSecurityKey(Convert.FromBase64String("na8LnVekSu5b3fgdUhyo+KuLTMVGYLtgHrTTpKCB5VY=")));// 集成 ASP.NET Coreoptions.UseAspNetCore().EnableAuthorizationEndpointPassthrough().EnableTokenEndpointPassthrough().DisableTransportSecurityRequirement();// 开发模式下金庸HTTPS}).AddValidation(options =>{// 使用本地服务器进行验证options.UseLocalServer();// 使用 AspNetCore 进行验证options.UseAspNetCore();});builder.Services.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
这段代码利用 OpenIddict 在 ASP.NET Core 应用中构建一个完整的 OAuth 2.0 授权和资源管理体系。它分为几个关键部分,每个部分都负责不同的功能,共同协作以实现安全可靠的身份验证和授权。
首先,
builder.Services.AddOpenIddict()
是整个配置的起点。它像一个基石,将 OpenIddict 的核心服务注册到 ASP.NET Core 的依赖注入容器中。这使得应用能够使用 OpenIddict 提供的各种功能,比如定义客户端、管理授权、颁发和验证令牌等。可以把它想象成激活了 OpenIddict 的总开关,后续的配置都是在此基础上进行的。接下来,
.AddCore(options => { ... })
用于配置 OpenIddict 的核心数据存储。这部分代码指定了使用 Entity Framework Core 作为 OpenIddict 的数据持久化方案。options.UseEntityFrameworkCore()
这行代码告诉 OpenIddict,所有关于客户端、授权、Scope 和令牌的信息都将存储在数据库中。这个设置对于生产环境至关重要,因为它可以确保数据在应用重启后仍然存在。options.UseDbContext<IdentityServerDbContext>()
则进一步指定了用于 EF Core 的 DbContext。IdentityServerDbContext
必须是一个继承自DbContext
的类,并且包含 OpenIddict 所需的实体,比如OpenIddictEntityFrameworkCoreApplication
(客户端信息)、OpenIddictEntityFrameworkCoreAuthorization
(授权信息)、OpenIddictEntityFrameworkCoreScope
(Scope 信息) 和OpenIddictEntityFrameworkCoreToken
(令牌信息)。我们需要根据数据库结构来定义这个 DbContext,并确保它与 OpenIddict 的期望相符。.AddServer(options => { ... })
是配置 OAuth 2.0 授权服务器的关键部分。它定义了授权服务器的行为和端点。options.SetTokenEndpointUris("/connect/token")
设置了令牌端点的 URI,客户端会向这个端点发送请求以获取访问令牌。可以把这个端点看作是授权服务器的核心入口,所有令牌相关的请求都会经过这里。options.AllowPasswordFlow()
、.AllowClientCredentialsFlow()
和.AllowRefreshTokenFlow()
分别启用了 OAuth 2.0 规范中定义的几种授权模式:密码模式、客户端凭据模式和刷新令牌模式。密码模式允许客户端直接使用用户的用户名和密码来获取令牌,客户端凭据模式允许客户端使用自己的身份信息(客户端 ID 和密钥)来获取令牌,而刷新令牌模式则允许客户端使用刷新令牌来获取新的访问令牌,避免用户重复登录。Tip:需要注意的是,密码模式在安全性方面有一些争议,通常不建议在生产环境中使用,除非有充分的理由并且采取了额外的安全措施。
options.RegisterScopes("api")
注册了一个名为 “api” 的 Scope,Scope 用于限制访问令牌的权限。客户端在请求令牌时可以指定所需的 Scope,授权服务器会根据这些 Scope 来生成具有相应权限的令牌。options.AddDevelopmentEncryptionCertificate().AddDevelopmentSigningCertificate()
在开发环境中配置了用于加密和签名令牌的证书。这些方法添加了临时的、自动生成的证书,方便开发和测试。然而,在生产环境中,绝对不能使用这种方式,必须使用由受信任的证书颁发机构颁发的证书,并妥善保管私钥。options.AddSigningKey(...)
和options.AddEncryptionKey(...)
使用对称密钥来签名和加密令牌。Tip:这里直接在代码中硬编码了密钥,这是一种非常不安全的做法,绝对不能在生产环境中使用。在生产环境中,应该使用更安全的密钥管理方案,比如使用 Azure Key Vault 或 HashiCorp Vault 等专门的密钥管理服务来存储和管理密钥。
options.UseAspNetCore()
将 OpenIddict 与 ASP.NET Core 集成,使得 OpenIddict 可以利用 ASP.NET Core 的各种功能,比如路由、中间件和依赖注入。.EnableAuthorizationEndpointPassthrough()
和.EnableTokenEndpointPassthrough()
启用了授权和令牌端点的直通模式,我们可以使用 ASP.NET Core 的路由机制来处理这些端点,而不是完全依赖 OpenIddict 的默认处理方式。.DisableTransportSecurityRequirement()
禁用了 HTTPS 传输安全要求。这只应该在开发环境中进行,因为在生产环境中,必须使用 HTTPS 来保护敏感数据的传输。.AddValidation(options => { ... })
配置了 OpenIddict 的令牌验证功能。这部分代码定义了 API 资源服务器如何验证客户端提供的访问令牌。options.UseLocalServer()
配置使用本地授权服务器进行令牌验证,API 资源服务器会与同一应用中的授权服务器通信来验证令牌的有效性。通常用于单体应用或微服务架构中的内部服务之间的验证。最后,
builder.Services.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)
将 OpenIddict 的验证方案添加到 ASP.NET Core 的身份验证管道中。可以使用 ASP.NET Core 的身份验证机制来保护 API 资源,只有持有有效 OpenIddict 令牌的客户端才能访问这些资源。 -
配置数据库上下文
在上一小节中, 我们指定了数据库上下文
IdentityServerDbContext
,在这一小节中我们就来创建这个数据库上下文。
首先,安装数据库提供程序Pomelo.EntityFrameworkCore.MySql
,在命令行输入如下命令:dotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Pomelo.EntityFrameworkCore.MySql
然后,创建自定义 DbContext
IdentityServerDbContext
,重写OnModelCreating
方法,其中调用builder.UseOpenIddict()
注册 OpenIddict 的实体映射,重写OnConfiguring
方法配置数据库连接。代码如下:using Microsoft.EntityFrameworkCore;namespace Authentication; /// <summary> /// IdentityServer 数据库上下文 /// </summary> public class IdentityServerDbContext : DbContext {/// <summary>/// 数据库连接配置/// </summary>IConfiguration _dbConfig;/// <summary>/// 构造函数/// </summary>/// <param name="dbConfig"></param>public IdentityServerDbContext(IConfiguration dbConfig) {_dbConfig = dbConfig;}/// <summary>/// 配置数据库模型/// </summary>/// <param name="modelBuilder"></param>protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);// 配置 OpenIddictmodelBuilder.UseOpenIddict();}/// <summary>/// 数据库连接配置/// </summary>/// <param name="optionsBuilder"></param>protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){var serverVersion = ServerVersion.AutoDetect(_dbConfig.GetConnectionString("MySQLConnection"));optionsBuilder.UseMySql(_dbConfig.GetConnectionString("MySQLConnection"), serverVersion);} }
-
配置数据库链接字符串
打开appsettings.json配置文件,添加数据库连接字符串,代码如下:
"ConnectionStrings": {"MySQLConnection": "server=14.103.224.141;port=3308;database=IdentityServerDemo;user=root;pwd=123*asdasd;" }
-
数据库迁移
所有配置完成后,使用 Entity Framework Core 的迁移命令生成数据库结构,这将创建 OpenIddict 所需的表结构。
dotnet ef migrations add InitialMigration dotnet ef database update
-
设置种子数据
在Authentication项目根目录下新建
SeedData
类,这个类继承子IHostedService
,在里面编写种子数据的代码。public class SeedData: IHostedService {private readonly IServiceProvider _serviceProvider;public SeedData(IServiceProvider serviceProvider){_serviceProvider = serviceProvider;}public async Task StartAsync(CancellationToken cancellationToken){using var scope = _serviceProvider.CreateScope();var applicationManager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>();// 检查是否存在指定的 client_idif (await applicationManager.FindByClientIdAsync("client_id1", cancellationToken) is null){var descriptor = new OpenIddictApplicationDescriptor{ClientId = "client_id1",ClientSecret = "client_secret", // 根据需求设置密钥DisplayName = "示例客户端应用",Permissions ={OpenIddictConstants.Permissions.Endpoints.Token,OpenIddictConstants.Permissions.GrantTypes.Password, // 允许密码模式OpenIddictConstants.Permissions.GrantTypes.ClientCredentials,OpenIddictConstants.Permissions.Prefixes.Scope + "api" // 允许访问的 API 作用域}};await applicationManager.CreateAsync(descriptor, cancellationToken);}}public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; }
这段代码在 ASP.NET Core 中使用 OpenIddict 配置 OpenID Connect 客户端,
SeedData
实现了IHostedService
,在应用程序启动时创建客户端配置。通过构造函数注入IServiceProvider
,在StartAsync
中创建服务范围,解析IOpenIddictApplicationManager
,并检查是否存在指定的ClientId
。如果不存在,创建
OpenIddictApplicationDescriptor
定义客户端配置:包括ClientId
(客户端 ID)、ClientSecret
(密钥)、DisplayName
(显示名称)和权限。权限包括访问 Token 端点(Endpoints.Token
)、密码模式授权(GrantTypes.Password
)、客户端凭据授权(GrantTypes.ClientCredentials
)和访问名为api
的作用域。通过applicationManager.CreateAsync()
将配置存储在 OpenIddict 的配置存储中。StopAsync
为空,不需要在程序关闭时执行清理。接着,将
SeedData
类引入到Program
类中:builder.Services.AddHostedService<SeedData>();
-
实现令牌端点
在
Program
类中设置了获取令牌的端点,在这一小节,就来实现这个端点。首先创建AuthorizationController
控制器,接着在其中编写令牌端点的代码。using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; using OpenIddict.Abstractions; using OpenIddict.Server.AspNetCore; using System.Security.Claims; using Microsoft.AspNetCore;namespace Authentication.Controllers {/// <summary>/// 授权控制器/// </summary>[Route("/connect")][ApiController]public class AuthorizationController : ControllerBase{/// <summary>/// 令牌端点/// </summary>/// <returns></returns>/// <exception cref="InvalidOperationException"></exception>[HttpPost("token")]public IActionResult Exchange(){var request = HttpContext.GetOpenIddictServerRequest();// 处理资源所有者密码模式if (request.IsPasswordGrantType()){// 示例:仅支持用户名 "alice" 和密码 "password123"if (request.Username != "alice" || request.Password != "password123"){return Forbid(authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,properties: new AuthenticationProperties(new Dictionary<string, string?>{[OpenIddictServerAspNetCoreConstants.Properties.Error] =OpenIddictConstants.Errors.InvalidGrant,[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户名或密码错误。"}));}// 创建用户身份并添加必要的声明var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);identity.AddClaim(OpenIddictConstants.Claims.Subject, request.Username);identity.AddClaim(OpenIddictConstants.Claims.Name, "Alice");identity.AddClaim(OpenIddictConstants.Claims.Audience, "api"); // 添加 aud 声明// 创建 ClaimsPrincipal,并设置请求的范围var principal = new ClaimsPrincipal(identity);principal.SetScopes(request.GetScopes());// 返回 SignIn 结果,OpenIddict 将生成访问令牌return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);}// 处理客户端凭证模式else if (request.IsClientCredentialsGrantType()){// 客户端凭证模式下,客户端身份已由 OpenIddict 验证器验证,直接使用 client_id 作为主题标识var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);identity.AddClaim(OpenIddictConstants.Claims.Subject,request.ClientId ?? throw new InvalidOperationException());identity.AddClaim(OpenIddictConstants.Claims.Audience, "api"); // 添加 aud 声明var principal = new ClaimsPrincipal(identity);principal.SetScopes(request.GetScopes());return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);}// 不支持的授权类型else{return BadRequest(new{error = OpenIddictConstants.Errors.UnsupportedGrantType,error_description = "不支持的授权类型。"});}}} }
Exchange
方法用来处理客户端通过 OpenID Connect 发起的令牌请求。首先通过HttpContext.GetOpenIddictServerRequest()
获取当前的 OpenIddict 请求对象request
。这个对象封装了 OpenID Connect 协议中的授权请求参数,例如授权类型(grant_type)、客户端 ID(client_id)、作用域(scope)、用户名(username)和密码(password)等。接下来,代码根据授权类型(grant_type)来决定如何处理授权请求。如果授权类型是“资源所有者密码模式”(Password Grant),通过request.IsPasswordGrantType()
进行判断。在密码模式下,客户端需要提供用户名和密码来直接获取访问令牌。 在这里,代码接受用户名为 “alice” 且密码为 “password123” 的请求。
如果用户名或密码不正确,返回
Forbid()
结果。Forbid()
方法表示认证失败,使用OpenIddictServerAspNetCoreDefaults.AuthenticationScheme
作为认证方案,同时设置 OpenIddict 定义的错误代码和错误描述。AuthenticationProperties
被用来传递错误信息,其中OpenIddictServerAspNetCoreConstants.Properties.Error
被设置为"invalid_grant"
,表示授权失败,OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription
设置为 “用户名或密码错误。”。如果用户名和密码正确,创建一个
ClaimsIdentity
对象,使用OpenIddictServerAspNetCoreDefaults.AuthenticationScheme
作为身份验证方案。然后通过identity.AddClaim()
方法为身份对象添加声明(Claim):
-OpenIddictConstants.Claims.Subject
:设置sub
(主题)声明,表示身份的唯一标识,这里设置为用户名alice
。
-OpenIddictConstants.Claims.Name
:设置name
声明,表示用户的显示名称,设置为"Alice"
。
-OpenIddictConstants.Claims.Audience
:设置aud
(受众)声明,表示访问的资源,设置为"api"
。然后,创建
ClaimsPrincipal
对象,将前面创建的ClaimsIdentity
作为参数传入。通过principal.SetScopes(request.GetScopes())
方法设置请求的作用域(scope),OpenIddict 将根据这些作用域生成访问令牌中包含的权限范围。最后,调用
SignIn()
方法,传入principal
和OpenIddictServerAspNetCoreDefaults.AuthenticationScheme
作为参数。SignIn()
方法表示认证成功,OpenIddict 将根据传入的声明信息生成访问令牌并返回给客户端。如果授权类型是“客户端凭证模式”(Client Credentials Grant),则通过
request.IsClientCredentialsGrantType()
进行判断。在客户端凭证模式下,客户端不涉及用户身份,直接通过client_id
和client_secret
进行认证。由于 OpenIddict 的验证器已经在此之前完成了对client_id
和client_secret
的验证,因此代码直接创建一个新的ClaimsIdentity
对象,并通过identity.AddClaim()
方法添加声明:
-OpenIddictConstants.Claims.Subject
:设置sub
(主题)声明,表示客户端身份,设置为request.ClientId
。如果request.ClientId
为空,抛出InvalidOperationException
。
-OpenIddictConstants.Claims.Audience
:设置aud
(受众)声明,表示访问的资源,设置为"api"
。最后,创建
ClaimsPrincipal
,并设置请求的作用域。调用SignIn()
方法,OpenIddict 将生成访问令牌返回给客户端。这段代码,密码模式主要用于涉及用户身份的场景,如通过用户名和密码登录,获取用户授权的访问令牌。而客户端凭证模式用于服务间通信,不涉及用户身份,仅通过客户端凭据授权。
3.2 Gateway 服务
-
网关配置
命令行切换到 Gateway Web Api 类库所在文件夹,将网关Ocelot引入进来:
dotnet add package Ocelot
在项目中
Program.cs
中,配置 JWT Bearer 认证,将 Authority 指向 OpenIddict 身份认证服务器地址,并注册 Ocelot:// 配置 JWT 认证(验证来自认证服务器的令牌) builder.Services.AddAuthentication("Bearer").AddJwtBearer("Bearer", options =>{options.Authority = "https://localhost:5002"; // 认证服务器地址options.RequireHttpsMetadata = false; // 开发环境下允许 HTTPoptions.Audience = "api"; // 与业务服务器要求的受众一致options.TokenValidationParameters = new TokenValidationParameters{ValidateIssuer = false,ValidateAudience = false,ValidateLifetime = false,ValidateIssuerSigningKey = false, // 忽略 kid 检查ValidAudience = "api", ValidIssuer = "http://localhost:5002",IssuerSigningKey =new SymmetricSecurityKey(Convert.FromBase64String("SguGSpvaRLMwnmnxiBHRdSxRpBDSiD8+8J1qp1czuD8=")),TokenDecryptionKey =new SymmetricSecurityKey(Convert.FromBase64String("na8LnVekSu5b3fgdUhyo+KuLTMVGYLtgHrTTpKCB5VY=")),ValidAlgorithms = new[]{SecurityAlgorithms.HmacSha256,SecurityAlgorithms.Aes256KW,SecurityAlgorithms.Aes256CbcHmacSha512}};});// 注册 Ocelot 服务 builder.Services.AddOcelot();
这段代码配置了JWT认证,并注册了Ocelot服务。首先,
builder.Services.AddAuthentication("Bearer").AddJwtBearer("Bearer", options => { ... });
这部分用于添加Bearer类型的JWT认证。options.Authority = "https://localhost:5002";
设置了认证服务器的地址,表明token是从这个地址签发的。options.RequireHttpsMetadata = false;
在开发环境下允许使用HTTP,正式环境应该设置为true以保证安全性。options.Audience = "api";
指定了业务服务器期望的受众,确保只有目标为 “api” 的token才被接受。options.TokenValidationParameters
定义了token验证的各种参数,其中ValidateIssuer = false
,ValidateAudience = false
,ValidateLifetime = false
,ValidateIssuerSigningKey = false
这些参数都被设置为false,这意味着发行者、受众、生命周期和签名密钥的验证都被禁用了,这通常不建议在生产环境中使用。ValidAudience = "api"
,ValidIssuer = "http://localhost:5002"
指定了合法的受众和发行者,但由于前面的验证参数被禁用,这些设置实际上没有生效。IssuerSigningKey
和TokenDecryptionKey
使用了对称密钥,这些密钥从Base64字符串转换而来,用于签名验证和token解密。ValidAlgorithms
指定了允许的加密算法,包括HmacSha256
,Aes256KW
和Aes256CbcHmacSha512
。最后,builder.Services.AddOcelot();
注册了Ocelot服务,Ocelot是一个API网关,用于路由和管理API请求。总的来说,这段代码配置了一个JWT认证方案,但由于禁用了许多关键验证步骤,可能存在安全风险,并注册了Ocelot API网关服务。 -
配置路由
接着,进行Ocelot 网关配置,在项目的
ocelot.json
文件中,配置路由和认证信息。假设所有请求通过 Ocelot 转发至下游 API,并要求 JWT 验证:{"Routes": [{"DownstreamPathTemplate": "/connect/token","DownstreamScheme": "http","DownstreamHostAndPorts": [{"Host": "localhost","Port": 5002}],"UpstreamPathTemplate": "/connect/token","UpstreamHttpMethod": [ "POST" ]},{"DownstreamPathTemplate": "/api/{everything}","DownstreamScheme": "http","DownstreamHostAndPorts": [{"Host": "localhost","Port": 5001}],"UpstreamPathTemplate": "/api/{everything}","UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ],"AuthenticationOptions": {"AuthenticationProviderKey": "Bearer","AllowedScopes": [ "api" ]},"AddHeadersToRequest": {"Authorization": "Authorization"}}],"GlobalConfiguration": {"BaseUrl": "http://localhost:5000"} }
AddHeadersToRequest
和AuthenticationOptions
这两个配置项在Ocelot的路由规则中起着至关重要的作用,它们分别负责请求头的处理和认证授权的管理。AddHeadersToRequest
配置项允许你将上游请求(客户端发送到Ocelot网关的请求)的头部信息添加到转发到下游服务(实际处理请求的API服务)的请求中。在这个例子中,"Authorization": "Authorization"
表示将上游请求中的Authorization
头部原封不动地传递给下游服务。这通常用于传递认证信息,例如JWT token。当下游服务需要知道用户的身份验证信息时,这个配置就非常有用。通过传递Authorization
头部,下游服务可以根据token进行身份验证和授权。AuthenticationOptions
配置项则定义了Ocelot如何对上游请求进行认证和授权。"AuthenticationProviderKey": "Bearer"
指定了用于认证的方案是"Bearer",这通常与JWT认证一起使用。这意味着Ocelot会尝试使用"Bearer"认证方案来验证上游请求的身份。"AllowedScopes": ["api"]
定义了允许的OAuth 2.0 scopes。只有当上游请求携带的token包含"api"这个scope时,Ocelot才会允许该请求通过这个路由。Scope是一种权限声明,用于限制客户端可以访问的资源。通过配置AllowedScopes
,可以确保只有具有适当权限的客户端才能访问特定的API端点。如果请求的token不包含"api"这个scope,Ocelot会拒绝该请求,返回未经授权的错误。
3.3 业务服务开发
Weather项目使用的是带有示例的模板创建的,因此就不用编写控制器了,只用在控制器上加上[Authorize]
即可。
打开Program类,在该类中新增身份验证服务的代码,需要注意的是这里的IssuerSigningKey
需要和Gateway
中的配置一样:
builder.Services.AddAuthentication("Bearer").AddJwtBearer("Bearer", options =>{options.TokenValidationParameters = new TokenValidationParameters{ValidateIssuerSigningKey = true,IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String("SguGSpvaRLMwnmnxiBHRdSxRpBDSiD8+8J1qp1czuD8=")),TokenDecryptionKey =new SymmetricSecurityKey(Convert.FromBase64String("na8LnVekSu5b3fgdUhyo+KuLTMVGYLtgHrTTpKCB5VY=")),ValidateIssuer = false,ValidIssuer = "http://localhost:5002",ValidateAudience = false};});
这段代码和Gateway
类似,就不再讲解。看到这里大家一定很奇怪,为什么网关服务已经配置了身份验证,业务服务还要进行一次验证呢?其实要分情况的,如果项目是内网项目,无需访问外网就可使用的话就不需要给业务负责增加身份验证的代码,如果是外网项目就必须增加身份验证服务。
四、总结
本文详细介绍了微服务架构中如何通过JWT认证和IdentityServer实现统一的身份认证与授权,以提升系统安全性。首先,阐述了JWT的工作原理及其在Ocelot网关中的应用,强调了JWT的无状态、轻量和跨平台兼容性,并强调了密钥安全的重要性。其次,介绍了IdentityServer作为.NET的开源身份认证与授权框架,如何简化认证流程,支持多种认证流程,并与JWT结合实现安全令牌管理。最后,通过一个集成Ocelot、IdentityServer和JWT认证的案例演示,详细讲解了认证服务器、网关和业务服务的配置步骤,包括OpenIddict的配置、数据库迁移、种子数据设置以及令牌端点的实现。该方案通过在网关层实现身份认证、权限校验和数据加密,有效防范恶意攻击,构建可靠的访问环境,切实保护企业数据安全。
相关文章:
10. 【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--微服务基础工具与技术--Ocelot 网关--认证
在微服务架构中,通过在网关层实现身份认证、权限校验和数据加密,可以有效防范恶意攻击和非法访问,保障内部服务安全。采用JWT、OAuth等主流认证机制,使每次请求均经过严格验证,降低安全漏洞风险。同时,统一…...

DeepSeek 3FS:端到端无缓存的存储新范式
在 2025 年 2 月 28 日,DeepSeek 正式开源了其高性能分布式文件系统 3FS【1】,作为其开源周的压轴项目,3FS 一经发布便引发了技术圈的热烈讨论。它不仅继承了分布式存储的经典设计,还通过极简却高效的架构,展现了存储技…...
vue3组合式API怎么获取全局变量globalProperties
设置全局变量 main.ts app.config.globalProperties.$category { index: 0 } 获取全局变量 const { appContext } getCurrentInstance() as ComponentInternalInstance console.log(appContext.config.globalProperties.$category) 或是 const { proxy } getCurrentInstance…...
【YOLOv12改进trick】多尺度大核注意力机制MLKA模块引入YOLOv12,实现多尺度目标检测涨点,含创新点Python代码,方便发论文
🍋改进模块🍋:多尺度大核注意力机制(MLKA) 🍋解决问题🍋:MLKA模块结合多尺度、门控机制和空间注意力,显著增强卷积网络的模型表示能力。 🍋改进优势🍋:超分辨的MLKA模块对小目标和模糊目标涨点很明显 🍋适用场景🍋:小目标检测、模糊目标检测等 🍋思路…...

网络安全之端口扫描(一)
前置介绍 什么是DVWA? DVWA(Damn Vulnerable Web Application)是一个专门设计用于测试和提高Web应用程序安全技能的开源PHP/MySQL Web应用程序。它是一个具有多个安全漏洞的故意不安全的应用程序,供安全专业人员、渗透测试人员、…...
HCIE云计算学什么?怎么学?未来职业发展如何?
随着云计算成为IT行业发展的主流方向,HCIE云计算(华为认证云计算专家)作为华为认证体系中的高端认证之一,逐渐成为了许多网络工程师和IT从业者提升职业竞争力的重要途径。 那么,HCIE云计算究竟学什么内容,如…...

upload-labs文件上传
第一关 上传一个1.jpg的文件,在里面写好一句webshell 保留一个数据包,将其中截获的1.jpg改为1.php后重新发送 可以看到,已经成功上传 第二关 写一个webshell如图,为2.php 第二关在过滤tpye的属性,在上传2.php后使用b…...

操作系统控制台-健康守护我们的系统
引言基本准备体验功能健康守护系统诊断 收获提升结语 引言 阿里云操作系统控制平台作为新一代云端服务器中枢平台,通过创新交互模式重构主机管理体验。操作系统控制台提供了一系列管理功能,包括运维监控、智能助手、扩展插件管理以及订阅服务等。用户可以…...

财务会计域——合并报表系统设计
摘要 本文主要介绍了合并报表系统的设计,包括其背景、业务流程和系统架构设计。合并报表系统可自动化生成数据,减少人为错误,确保报表合规。其业务流程涵盖数据收集、标准化、合并调整、报表生成、审核及披露等环节。系统架构设计包括数据接…...

教务考试管理系统-Sprintboot vue
一、前言 1.1 实践目的和要求 本次实践的目的是为了帮助学生强化对实践涉及专业技术知识的理解,掌握专业领域中软件知识的应用方法,并了解软件工程在具体行业领域的发展趋势。通过培养学生利用软件工程方法分析、设计并完成具体行业软件开发的能力&…...

vue实现一个pdf在线预览,pdf选择文本并提取复制文字触发弹窗效果
[TOC] 一、文件预览 1、安装依赖包 这里安装了disjs-dist2.16版本,安装过程中报错缺少worker-loader npm i pdfjs-dist2.16.105 worker-loader3.0.8 2、模板部分 <template><div id"pdf-view"><canvas v-for"page in pdfPages&qu…...
【CSS 】Class Variance Authority CSS 类名管理工具库
1.背景、什么是 CVA? Class Variance Authority (CVA) 是一个用于管理 CSS 类名 的工具库,特别适合在 React 或 Vue 等前端框架中使用。它可以帮助你更轻松地处理组件的 样式变体(Variants),比如按钮的不同状态&#…...
自然语言处理:文本分类
介绍 大家好,我这个热衷于分享知识的博主又来啦!之前我们一起深入探讨了自然语言处理领域中非常重要的两个方法:朴素贝叶斯和逻辑斯谛回归。在探索的过程中,我们剖析了朴素贝叶斯如何基于概率原理和特征条件独立假设,…...
刷题记录 HOT100 贪心-2:45. 跳跃游戏 II
题目:45. 跳跃游戏 II 难度:中等 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i j] 处: 0 &l…...
7.2 奇异值分解的基与矩阵
一、奇异值分解 奇异值分解(SVD)是线性代数的高光时刻。 A A A 是一个 m n m\times n mn 的矩阵,可以是方阵或者长方形矩阵,秩为 r r r。我们要对角化 A A A,但并不是把它化成 X − 1 A X X^{-1}A X X−1AX 的形…...
PDFMathTranslate安装使用
PDF全文翻译!!!! PDFMathTranslate安装使用 它是个啥 PDFMathTranslate 可能是一个用于 PDF 文件的数学公式翻译 工具。它可能包含以下功能: 提取 PDF 内的数学公式 将数学公式转换成 LaTeX 代码 翻译数学公式的内…...

STL之list的使用(超详解)
目录 一、list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 iterator的使用 1.2.3capacity(容量相关) 1.2.4 element access(元素访问) 1.2.5 modifiers(链表修改)…...

动态 SQL 的使用
目录 1、< if> 标签2、< trim> 标签3、< where> 标签4、< set> 标签5、< foreach> 标签 1、< if> 标签 < if test“条件语句”> xxxx < /if> 只有当条件语句满足条件,才会拼接 < if> 标签内容,因…...
【如何删除在 Linux 系统中的删除乱码文件】
如何删除在 Linux 系统中的删除乱码文件 1. 列出文件并找到乱码文件:2. 使用通配符(谨慎使用):3. 转义特殊字符:4. 使用 find 命令:5. 使用 inode 号删除文件:6. 图形界面文件管理器:…...

防火墙IPSec (无固定IP地址---一对多)
目录 前言 一、场景: 二、实现 1.拓扑图 2.配置思路 ①基础通信配置 ②PPPoE配置 ③总部的模版IPSec配置 ④分部的IPSec配置 ⑤NAT配置 3.具体配置 ①基础配置 ②详细配置和顺序 效果测试: ③PPPOE ①配置PPPoE ②策略放行 ③IPSec与NA…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...