.NET Core OpenId Connect 服务器:跨多个应用程序共享相同的令牌
.NET Core OpenId Connect Server: Sharing same token across multiple applications
我有两个用 .NET Core 编写并针对 4.6.1 的 API:
- myAuthApi (http://localhost:8496):验证凭据并向客户端颁发令牌。它还有一个端点 /api/values/1(此操作具有授权属性,用于验证令牌)
- myPublicApi(http://localhost:8497):它在 /api/values/1 上从客户端接收令牌(此操作具有授权属性,也用于验证令牌)。 myPublicApi 没有任何令牌端点,旨在成为资源服务器。
我正在使用 AspNet.Security.OpenIdConnect.Server 1.0.0。
两个 API 都是 Service.Fabric 无状态服务
我可以通过以下请求格式成功获取token到http://localhost:8496/connect/token
client_id=XX&client_secret=XXX&grant_type=password&username=XXX&password=XXX
当根据 myAuthApi (http://localhost:8496/api/values/1) 验证令牌时,它有效。但是,当对 myPublicApi(http://localhost:8497/api/values/1) 使用相同的令牌时,它不会。
在 API 中,在 Startup.cs 中,我有
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Connect to Redis database.
var redis = ConnectionMultiplexer.Connect(ConnectionHelper.GetRedisConnectionString(Configuration));
services.AddDataProtection()
.PersistKeysToRedis(redis, "DataProtection-Keys")
.ProtectKeysWithCertificate(CertificateHandler.GetX509Certificate2(Configuration));
// Add framework services.
services.AddMvc().AddJsonOptions(opts =>
{
// we set the json serializer to follow camelCaseConventions when
// receiving /replying to JSON requests
opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
// we add authentication for the oAuth middleware to be registered in the DI container
services.AddAuthentication();
}
在 myPublicApi 中我有:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add a new middleware validating access tokens.
app.UseOAuthValidation(options =>
{
// Automatic authentication must be enabled
// for SignalR to receive the access token.
options.AutomaticAuthenticate = true;
options.Events = new OAuthValidationEvents
{
// Note: for SignalR connections, the default Authorization header does not work,
// because the WebSockets JS API doesn't allow setting custom parameters.
// To work around this limitation, the access token is retrieved from the query string.
OnRetrieveToken = context =>
{
// Note: when the token is missing from the query string,
// context.Token is null and the JWT bearer middleware will
// automatically try to retrieve it from the Authorization header.
context.Token = context.Request.Query["access_token"];
return Task.FromResult(0);
}
};
});
app.UseMvc();
}
在 myAuthApi 中我有:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add a new middleware validating access tokens.
app.UseOAuthValidation(options =>
{
// Automatic authentication must be enabled
// for SignalR to receive the access token.
options.AutomaticAuthenticate = true;
options.Events = new OAuthValidationEvents
{
// Note: for SignalR connections, the default Authorization header does not work,
// because the WebSockets JS API doesn't allow setting custom parameters.
// To work around this limitation, the access token is retrieved from the query string.
OnRetrieveToken = context =>
{
// Note: when the token is missing from the query string,
// context.Token is null and the JWT bearer middleware will
// automatically try to retrieve it from the Authorization header.
context.Token = context.Request.Query["access_token"];
return Task.FromResult(0);
}
};
});
// Add a new middleware issuing access tokens.
app.UseOpenIdConnectServer(options =>
{
options.Provider = new AuthenticationProvider();
// Enable the logout, token and userinfo endpoints.
options.LogoutEndpointPath = "/connect/logout";
options.TokenEndpointPath = "/connect/token";
options.UserinfoEndpointPath = "/connect/userinfo";
CertificateHandler.SetupCommonAuthServerOptions(options, Configuration);
});
app.UseMvc();
}
如您所见,我的数据保护提供商将密钥存储在 Redis 中,我使用我在 2 个应用程序之间共享的证书来保护密钥。
资源服务器没有配置任何身份验证提供程序,也没有在启动时使用 UseOpenIdConnectServer。在 asp.net Web API 2 中,过去使用共享机器密钥跨应用程序管理令牌解密。
如何使用 oAuthValidation 在所有其他应用程序中成功验证 myAuthApi 颁发的令牌?
编辑,一些日志可以在这里看到:
https://pastebin.com/ACDz1fam
编辑2:
仔细阅读日志后,我看到令牌的解除保护使用相同的数据保护提供程序,但目的不同:
"Performing unprotect operation to key {4406cfa7-a588-44ba-b73a-e25817d982d9} with purposes ('C:\SfDevCluster\Data\_App\_Node_4\TestMicroServicesType_App22\PublicApiPkg.Code.1.0.1', 'OpenIdConnectServerHandler', 'AccessTokenFormat', 'ASOS')."
"Performing unprotect operation to key {4406cfa7-a588-44ba-b73a-e25817d982d9} with purposes ('C:\SfDevCluster\Data\_App\_Node_3\TestMicroServicesType_App22\AuthApiPkg.Code.1.0.1', 'OpenIdConnectServerHandler', 'AccessTokenFormat', 'ASOS')."
要解决此问题,@PinpointTownes 建议像这样设置数据保护提供程序:
var redis = ConnectionMultiplexer.Connect(ConnectionHelper.GetRedisConnectionString(Configuration));
services.AddDataProtection()
// set the application name to a common value in all apps
// to have the same purpose and share the token across apps
.SetApplicationName("MyTestMicroServices")
.PersistKeysToRedis(redis, "DataProtection-Keys")
.ProtectKeysWithCertificate(CertificateHandler.GetX509Certificate2(Configuration));
调用 services.AddDataProtection().SetApplicationName("[your application name]")
以确保您的两个 API 使用相同的鉴别器(用于派生加密密钥)并且它应该工作。
我有两个用 .NET Core 编写并针对 4.6.1 的 API:
- myAuthApi (http://localhost:8496):验证凭据并向客户端颁发令牌。它还有一个端点 /api/values/1(此操作具有授权属性,用于验证令牌)
- myPublicApi(http://localhost:8497):它在 /api/values/1 上从客户端接收令牌(此操作具有授权属性,也用于验证令牌)。 myPublicApi 没有任何令牌端点,旨在成为资源服务器。
我正在使用 AspNet.Security.OpenIdConnect.Server 1.0.0。 两个 API 都是 Service.Fabric 无状态服务
我可以通过以下请求格式成功获取token到http://localhost:8496/connect/token
client_id=XX&client_secret=XXX&grant_type=password&username=XXX&password=XXX
当根据 myAuthApi (http://localhost:8496/api/values/1) 验证令牌时,它有效。但是,当对 myPublicApi(http://localhost:8497/api/values/1) 使用相同的令牌时,它不会。
在 API 中,在 Startup.cs 中,我有
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Connect to Redis database.
var redis = ConnectionMultiplexer.Connect(ConnectionHelper.GetRedisConnectionString(Configuration));
services.AddDataProtection()
.PersistKeysToRedis(redis, "DataProtection-Keys")
.ProtectKeysWithCertificate(CertificateHandler.GetX509Certificate2(Configuration));
// Add framework services.
services.AddMvc().AddJsonOptions(opts =>
{
// we set the json serializer to follow camelCaseConventions when
// receiving /replying to JSON requests
opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
// we add authentication for the oAuth middleware to be registered in the DI container
services.AddAuthentication();
}
在 myPublicApi 中我有:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add a new middleware validating access tokens.
app.UseOAuthValidation(options =>
{
// Automatic authentication must be enabled
// for SignalR to receive the access token.
options.AutomaticAuthenticate = true;
options.Events = new OAuthValidationEvents
{
// Note: for SignalR connections, the default Authorization header does not work,
// because the WebSockets JS API doesn't allow setting custom parameters.
// To work around this limitation, the access token is retrieved from the query string.
OnRetrieveToken = context =>
{
// Note: when the token is missing from the query string,
// context.Token is null and the JWT bearer middleware will
// automatically try to retrieve it from the Authorization header.
context.Token = context.Request.Query["access_token"];
return Task.FromResult(0);
}
};
});
app.UseMvc();
}
在 myAuthApi 中我有:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add a new middleware validating access tokens.
app.UseOAuthValidation(options =>
{
// Automatic authentication must be enabled
// for SignalR to receive the access token.
options.AutomaticAuthenticate = true;
options.Events = new OAuthValidationEvents
{
// Note: for SignalR connections, the default Authorization header does not work,
// because the WebSockets JS API doesn't allow setting custom parameters.
// To work around this limitation, the access token is retrieved from the query string.
OnRetrieveToken = context =>
{
// Note: when the token is missing from the query string,
// context.Token is null and the JWT bearer middleware will
// automatically try to retrieve it from the Authorization header.
context.Token = context.Request.Query["access_token"];
return Task.FromResult(0);
}
};
});
// Add a new middleware issuing access tokens.
app.UseOpenIdConnectServer(options =>
{
options.Provider = new AuthenticationProvider();
// Enable the logout, token and userinfo endpoints.
options.LogoutEndpointPath = "/connect/logout";
options.TokenEndpointPath = "/connect/token";
options.UserinfoEndpointPath = "/connect/userinfo";
CertificateHandler.SetupCommonAuthServerOptions(options, Configuration);
});
app.UseMvc();
}
如您所见,我的数据保护提供商将密钥存储在 Redis 中,我使用我在 2 个应用程序之间共享的证书来保护密钥。 资源服务器没有配置任何身份验证提供程序,也没有在启动时使用 UseOpenIdConnectServer。在 asp.net Web API 2 中,过去使用共享机器密钥跨应用程序管理令牌解密。
如何使用 oAuthValidation 在所有其他应用程序中成功验证 myAuthApi 颁发的令牌?
编辑,一些日志可以在这里看到: https://pastebin.com/ACDz1fam
编辑2: 仔细阅读日志后,我看到令牌的解除保护使用相同的数据保护提供程序,但目的不同:
"Performing unprotect operation to key {4406cfa7-a588-44ba-b73a-e25817d982d9} with purposes ('C:\SfDevCluster\Data\_App\_Node_4\TestMicroServicesType_App22\PublicApiPkg.Code.1.0.1', 'OpenIdConnectServerHandler', 'AccessTokenFormat', 'ASOS')."
"Performing unprotect operation to key {4406cfa7-a588-44ba-b73a-e25817d982d9} with purposes ('C:\SfDevCluster\Data\_App\_Node_3\TestMicroServicesType_App22\AuthApiPkg.Code.1.0.1', 'OpenIdConnectServerHandler', 'AccessTokenFormat', 'ASOS')."
要解决此问题,@PinpointTownes 建议像这样设置数据保护提供程序:
var redis = ConnectionMultiplexer.Connect(ConnectionHelper.GetRedisConnectionString(Configuration));
services.AddDataProtection()
// set the application name to a common value in all apps
// to have the same purpose and share the token across apps
.SetApplicationName("MyTestMicroServices")
.PersistKeysToRedis(redis, "DataProtection-Keys")
.ProtectKeysWithCertificate(CertificateHandler.GetX509Certificate2(Configuration));
调用 services.AddDataProtection().SetApplicationName("[your application name]")
以确保您的两个 API 使用相同的鉴别器(用于派生加密密钥)并且它应该工作。