设置
-
Identity Provider server with Duende server。6、注册客户端使用授权类型代码
new Client { ClientId = "test_client", RequireClientSecret = false, AllowOfflineAccess = true, ClientName = "Scope", AllowedGrantTypes = GrantTypes.Code, AllowedScopes = new List<string> { "openid", }, AllowedCorsOrigins = new List<string> { "https://localhost:5001", "https://localhost:5011", }, RedirectUris = new List<string> { "https://localhost:5011/signin-oidc" } }
-
API与以下配置
services.Configure<CookiePolicyOptions>(options => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.Strict; }) .AddAuthentication(sharedOptions => { sharedOptions.DefaultScheme = "smart"; sharedOptions.DefaultChallengeScheme = "smart"; }) .AddPolicyScheme("smart", "Authorization Bearer or OIDC", options => { options.ForwardDefaultSelector = context => { var authHeader = context.Request.Headers["Authorization"].FirstOrDefault(); if (authHeader?.StartsWith("Bearer ") == true) { return JwtBearerDefaults.AuthenticationScheme; } return "oidc"; }; }) .AddJwtBearer(jwtOptions => { jwtOptions.Authority = configuration["Authentication:Authority"]; jwtOptions.Audience = configuration["Authentication:Audience"]; jwtOptions.SaveToken = true; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = configuration["Authentication:Authority"]; options.ClientId = configuration["Authentication:ClientId"]; options.ResponseType = "code"; options.Prompt = "login"; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("openid"); options.SaveTokens = true; }); services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .AddAuthenticationSchemes("smart").Build(); });
-
和一个BFF Webclient配置
services .AddBff() .AddRemoteApis(); services.AddAuthentication(options => { options.DefaultScheme = "cookie"; options.DefaultChallengeScheme = "oidc"; options.DefaultSignOutScheme = "oidc"; }) .AddCookie("cookie", options => { options.Cookie.Name = "__Host-blazor"; options.Cookie.SameSite = SameSiteMode.Strict; }) .AddOpenIdConnect("oidc", options => { options.Authority = configuration["Authentication:Authority"]; // confidential client using code flow + PKCE options.ClientId = configuration["Authentication:ClientId"]; options.ResponseType = "code"; options.ResponseMode = "query"; options.MapInboundClaims = false; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; // request scopes + refresh tokens options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("offline_access"); }); //services.AddAccessTokenManagement(); services.AddClientAccessTokenHttpClient(AuthorizedClient, configureClient: client => { //This is the address of the Mono API client.BaseAddress = new Uri(configuration["ApiConfig:BaseAddress"]); });
每当我们手动调用API时,我们都会使用一个命名的http客户端,该客户端由httpfactory创建,并附带访问令牌。访问令牌是从HttpContext中收集的。用户在使用端点之前需要登录,所以访问令牌在HttpContext中是有效的。
public BaseHttpClient(IHttpClientFactory httpClientFactory, IHttpContextAccessor httpContextAccessor, ILogger logger)
{
this.httpClient = httpClientFactory.CreateClient(AuthorizedClient);
this.httpContextAccessor = httpContextAccessor;
this.logger = logger;
}
protected async Task<HttpResponseMessage> SendAuthenticatedAsync(HttpRequestMessage request)
{
try
{
var token = await this.httpContextAccessor.HttpContext.GetUserAccessTokenAsync();
this.httpClient.SetBearerToken(token);
var responseMessage = await this.httpClient.SendAsync(request);
return responseMessage;
}
catch (Exception e)
{
this.logger?.Error(e, "Exception at sending the authenticated client");
return new HttpResponseMessage
{
StatusCode = HttpStatusCode.BadRequest
};
}
}
从BFF到API的手动调用成功通过,但是当我们检查日志时,有些事情是不对的。
Duende BFF logs:
[19:05:39 DBG] AuthenticationScheme: cookie was successfully authenticated.
[19:05:39 DBG] AuthenticationScheme: cookie was successfully authenticated.
[19:05:39 INF] Start processing HTTP request POST https://localhost:5001/api/v1/property
[19:05:39 INF] Start processing HTTP request POST https://localhost:5001/api/v1/property
[19:05:39 DBG] Cache miss for access token for client: default
[19:05:39 DBG] Cache miss for access token for client: default
[19:05:39 DBG] Requesting client access token for client: default
[19:05:39 DBG] Requesting client access token for client: default
[19:05:39 DBG] Constructing token client configuration from OpenID Connect handler.
[19:05:39 DBG] Constructing token client configuration from OpenID Connect handler.
[19:05:39 DBG] Returning token client configuration for client: default
[19:05:39 DBG] Returning token client configuration for client: default
[19:05:39 INF] Start processing HTTP request POST https://localhost:5443/connect/token
[19:05:39 INF] Start processing HTTP request POST https://localhost:5443/connect/token
[19:05:39 INF] Sending HTTP request POST https://localhost:5443/connect/token
[19:05:39 INF] Sending HTTP request POST https://localhost:5443/connect/token
[19:05:39 INF] Received HTTP response headers after 160.5943ms - 400
[19:05:39 INF] Received HTTP response headers after 160.5943ms - 400
[19:05:39 INF] End processing HTTP request after 168.1572ms - 400
[19:05:39 INF] End processing HTTP request after 168.1572ms - 400
**HERE**
[19:05:39 ERR] Error requesting access token for client default. Error = unauthorized_client. Error description = null
[19:05:39 ERR] Error requesting access token for client default. Error = unauthorized_client. Error description = null
[19:05:39 INF] Sending HTTP request POST https://localhost:5001/api/v1/property
[19:05:39 INF] Sending HTTP request POST https://localhost:5001/api/v1/property
[19:05:39 INF] Received HTTP response headers after 110.934ms - 400
[19:05:39 INF] Received HTTP response headers after 110.934ms - 400
[19:05:39 INF] End processing HTTP request after 295.8003ms - 400
[19:05:39 INF] End processing HTTP request after 295.8003ms - 400
[19:05:39 INF] Executing StatusCodeResult, setting HTTP status code 200
[19:05:39 INF] Executing StatusCodeResult, setting HTTP status code 200
未授权客户端出现错误
Identity Server日志如下:
[19:05:39 VRB] Calling into client configuration validator: Duende.IdentityServer.Validation.DefaultClientConfigurationValidator
[19:05:39 DBG] client configuration validation for client test_client succeeded.
[19:05:39 DBG] Public Client - skipping secret validation success
[19:05:39 DBG] Client validation success
[19:05:39 INF] {"ClientId": "test_client", "AuthenticationMethod": "NoSecret", "Category": "Authentication", "Name": "Client Authentication Success", "EventType": "Success", "Id": 1010, "Message": null, "ActivityId": "0HMI8JMB87769:00000004", "TimeStamp": "2022-06-07T16:05:39.0000000Z", "ProcessId": 21368, "LocalIpAddress": "::1:5443", "RemoteIpAddress": "::1", "$type": "ClientAuthenticationSuccessEvent"}
[19:05:39 VRB] Calling into token request validator: Duende.IdentityServer.Validation.TokenRequestValidator
[19:05:39 DBG] Start token request validation
[19:05:39 DBG] Start client credentials token request validation
**HERE**
[19:05:39 ERR] Client not authorized for client credentials flow, check the AllowedGrantTypes setting{"clientId": "test_client"}, details: {"ClientId": "test_client", "ClientName": "Scope", "GrantType": "client_credentials", "Scopes": null, "AuthorizationCode": "********", "RefreshToken": "********", "UserName": null, "AuthenticationContextReferenceClasses": null, "Tenant": null, "IdP": null, "Raw": {"grant_type": "client_credentials", "client_id": "test_client"}, "$type": "TokenRequestValidationLog"}
[19:05:39 INF] {"ClientId": "test_client", "ClientName": "Scope", "RedirectUri": null, "Endpoint": "Token", "SubjectId": null, "Scopes": null, "GrantType": "client_credentials", "Error": "unauthorized_client", "ErrorDescription": null, "Category": "Token", "Name": "Token Issued Failure", "EventType": "Failure", "Id": 2001, "Message": null, "ActivityId": "0HMI8JMB87769:00000004", "TimeStamp": "2022-06-07T16:05:39.0000000Z", "ProcessId": 21368, "LocalIpAddress": "::1:5443", "RemoteIpAddress": "::1", "$type": "TokenIssuedFailureEvent"}
[19:05:39 VRB] Invoking result: Duende.IdentityServer.Endpoints.Results.TokenErrorResult
[19:05:39 DBG] Connection id "0HMI8JMB87769" completed keep alive response.
[19:05:39 DBG] 'ConfigurationDbContext' disposed.
我们看到一个请求是用授权类型客户端凭证向身份服务器发出的,但是Duende BFF是用授权类型代码注册的。
输入的客户端发出的http请求通过,因为附加的访问令牌是有效的,但是BFF和IDP的日志记录行为是奇怪的。
有什么想法或线索可能导致最好的朋友给IDP打这样的电话吗?
找到问题。我将命名的http客户端注册为AddClientAccessTokenHttpClient
services.AddClientAccessTokenHttpClient(AuthorizedClient, configureClient: client =>
{
//This is the address of the Mono API
client.BaseAddress = new Uri(configuration["ApiConfig:BaseAddress"]);
});
导致它发出的请求将授予类型设置为Client Credentials。
修复是注册到正确的http客户端- AddUserAccessTokenHttpClient
services.AddUserAccessTokenHttpClient(AuthorizedClient, configureClient: client =>
{
//This is the address of the Mono API
client.BaseAddress = new Uri($"{configuration["ApiConfig:BaseAddress"]}");
});
现在请求的授权类型设置为code。