自定义身份验证中间件:身份验证失败时出现 InvalidOperationException
Custom authentication middleware: InvalidOperationException on failed authentication
我想使用像 Authorization ToggledKey 10079a4c-d27e-4898-915a-968850c756ef
这样的 HTTP Header 验证对我的 ASP.NET Core 1.0 应用程序的调用
我的想法是我可以发布 API 密钥,人们可以使用它们、撤销它们等等。我只想要一个非常简单的密钥——我不想进入 OAuth、IdentityServer 或还要别的吗。
我按照 this tutorial 讨论了如何 "fake" 成功响应以进行测试,最后我得到了以下代码:
public class TestAuthenticationOptions : AuthenticationOptions
{
public virtual ClaimsIdentity Identity { get; } = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, Guid.NewGuid().ToString()),
// Other claims omitted for brevity
}, "Toggled");
public TestAuthenticationOptions()
{
this.AuthenticationScheme = "ToggledAuthenticationMiddleware";
this.AutomaticAuthenticate = true;
}
}
public class TestAuthenticationHandler : AuthenticationHandler<TestAuthenticationOptions>
{
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var authenticationTicket = new AuthenticationTicket(
new ClaimsPrincipal(Options.Identity),
new AuthenticationProperties(),
this.Options.AuthenticationScheme);
return Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
}
public class TestAuthenticationMiddleware : AuthenticationMiddleware<TestAuthenticationOptions>
{
private readonly RequestDelegate next;
public TestAuthenticationMiddleware(RequestDelegate next, IOptions<TestAuthenticationOptions> options, ILoggerFactory loggerFactory)
: base(next, options, loggerFactory, System.Text.Encodings.Web.UrlEncoder.Default)
{
this.next = next;
}
protected override AuthenticationHandler<TestAuthenticationOptions> CreateHandler()
{
return new TestAuthenticationHandler();
}
}
首先,我只是想获得 hard-coded 成功的身份验证,这将使我能够看到具有 User.Identity.Name
的用户和 hard-coded 失败的身份验证让我看到401 Unauthorized
- 只是为了了解它是如何工作的。
添加到Configure
方法中:
app.UseMiddleware<TestAuthenticationMiddleware>();
app.UseMvc();
到目前为止,很好,它可以工作 - [Authorize]
属性在控制器上,我在 User.Identity.Name
中获得了一个 GUID
问题:
当我因响应失败而更改时:
return Task.FromResult(AuthenticateResult.Fail("Auth Failed!"));
//return Task.FromResult(AuthenticateResult.Success(authenticationTicket));
我得到的不是 401,而是 500,但有以下例外:
Unknown error responding to request: InvalidOperationException:
System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()
我确实在记录的中间件中看到身份验证失败:
[Information] ToggledAppServices.TestAuthenticationMiddleware: ToggledAuthenticationMiddleware was not authenticated. Failure message: Auth failed!
所以我可以看到它确实使用了中间件,验证失败,但也看到 InvalidOperationException
:
[Information] Microsoft.AspNetCore.Mvc.ChallengeResult: Executing ChallengeResult with authentication schemes ().
在这之后我很困惑。我该怎么做才能使我的中间件成为 "only" 身份验证点,如果它失败,则返回 return 401,而不是尝试做更多事情并因 InvalidOperationException 而失败?
完整日志:
[Debug] Microsoft.AspNetCore.Hosting.Internal.WebHost: Hosting starting
[Debug] Microsoft.AspNetCore.Hosting.Internal.WebHost: Hosting started
START RequestId: 52c7358f-e50a-11e7-a5a6-b3632cca0b75 Version: $LATEST
Incoming GET requests to /api/values
[Information] Microsoft.AspNetCore.Hosting.Internal.WebHost: Request starting GET https://www.example.com/api/controller application/json
[Information] ToggledAppServices.TestAuthenticationMiddleware: ToggledAuthenticationMiddleware was not authenticated. Failure message: Auth failed!
[Debug] Microsoft.AspNetCore.Routing.Tree.TreeRouter: Request successfully matched the route with name '' and template 'api/values'.
[Debug] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker: Executing action ToggledAppServices.Controllers.ValuesController.Get (ToggledAppServices)
[Information] Microsoft.AspNetCore.Authorization.DefaultAuthorizationService: Authorization failed for user: .
[Warning] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
[Information] Microsoft.AspNetCore.Mvc.ChallengeResult: Executing ChallengeResult with authentication schemes ().
Unknown error responding to request: InvalidOperationException:
System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.ChallengeResult.<ExecuteResultAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeResultAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeAsync>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.<Invoke>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction.<ProcessRequest>d__15.MoveNext()
InvalidOperationException:
System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.ChallengeResult.<ExecuteResultAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeResultAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeAsync>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.<Invoke>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction.<ProcessRequest>d__15.MoveNext()
[Information] Microsoft.AspNetCore.Hosting.Internal.WebHost: Request finished in 7360.5673ms 0
Response Base 64 Encoded: False
END RequestId: 52c7358f-e50a-11e7-a5a6-b3632cca0b75
System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()
这表明您的身份验证处理程序的身份验证 工作正常,并且由于身份验证失败而正确创建质询结果。
所以现在,默认质询身份验证处理程序应该 运行 处理质询请求。但是,没有配置默认的质询方案,所以失败了。
为了让您的 TestAuthenticationHandler
也处理质询请求,您应该将 TestAuthenticationOptions
类型的 AutomaticChallenge
选项设置为 true
:
// handle authenticate requests by default
AutomaticAuthenticate = true;
// handle challenge requests by default
AutomaticChallenge = true;
然后,您的处理程序将 运行 质询请求,并且由于您不提供自定义实现,the default behavior 将只生成 401 HTTP 响应。
我想使用像 Authorization ToggledKey 10079a4c-d27e-4898-915a-968850c756ef
我的想法是我可以发布 API 密钥,人们可以使用它们、撤销它们等等。我只想要一个非常简单的密钥——我不想进入 OAuth、IdentityServer 或还要别的吗。
我按照 this tutorial 讨论了如何 "fake" 成功响应以进行测试,最后我得到了以下代码:
public class TestAuthenticationOptions : AuthenticationOptions
{
public virtual ClaimsIdentity Identity { get; } = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, Guid.NewGuid().ToString()),
// Other claims omitted for brevity
}, "Toggled");
public TestAuthenticationOptions()
{
this.AuthenticationScheme = "ToggledAuthenticationMiddleware";
this.AutomaticAuthenticate = true;
}
}
public class TestAuthenticationHandler : AuthenticationHandler<TestAuthenticationOptions>
{
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var authenticationTicket = new AuthenticationTicket(
new ClaimsPrincipal(Options.Identity),
new AuthenticationProperties(),
this.Options.AuthenticationScheme);
return Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
}
public class TestAuthenticationMiddleware : AuthenticationMiddleware<TestAuthenticationOptions>
{
private readonly RequestDelegate next;
public TestAuthenticationMiddleware(RequestDelegate next, IOptions<TestAuthenticationOptions> options, ILoggerFactory loggerFactory)
: base(next, options, loggerFactory, System.Text.Encodings.Web.UrlEncoder.Default)
{
this.next = next;
}
protected override AuthenticationHandler<TestAuthenticationOptions> CreateHandler()
{
return new TestAuthenticationHandler();
}
}
首先,我只是想获得 hard-coded 成功的身份验证,这将使我能够看到具有 User.Identity.Name
的用户和 hard-coded 失败的身份验证让我看到401 Unauthorized
- 只是为了了解它是如何工作的。
添加到Configure
方法中:
app.UseMiddleware<TestAuthenticationMiddleware>();
app.UseMvc();
到目前为止,很好,它可以工作 - [Authorize]
属性在控制器上,我在 User.Identity.Name
问题:
当我因响应失败而更改时:
return Task.FromResult(AuthenticateResult.Fail("Auth Failed!"));
//return Task.FromResult(AuthenticateResult.Success(authenticationTicket));
我得到的不是 401,而是 500,但有以下例外:
Unknown error responding to request: InvalidOperationException:
System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()
我确实在记录的中间件中看到身份验证失败:
[Information] ToggledAppServices.TestAuthenticationMiddleware: ToggledAuthenticationMiddleware was not authenticated. Failure message: Auth failed!
所以我可以看到它确实使用了中间件,验证失败,但也看到 InvalidOperationException
:
[Information] Microsoft.AspNetCore.Mvc.ChallengeResult: Executing ChallengeResult with authentication schemes ().
在这之后我很困惑。我该怎么做才能使我的中间件成为 "only" 身份验证点,如果它失败,则返回 return 401,而不是尝试做更多事情并因 InvalidOperationException 而失败?
完整日志:
[Debug] Microsoft.AspNetCore.Hosting.Internal.WebHost: Hosting starting
[Debug] Microsoft.AspNetCore.Hosting.Internal.WebHost: Hosting started
START RequestId: 52c7358f-e50a-11e7-a5a6-b3632cca0b75 Version: $LATEST
Incoming GET requests to /api/values
[Information] Microsoft.AspNetCore.Hosting.Internal.WebHost: Request starting GET https://www.example.com/api/controller application/json
[Information] ToggledAppServices.TestAuthenticationMiddleware: ToggledAuthenticationMiddleware was not authenticated. Failure message: Auth failed!
[Debug] Microsoft.AspNetCore.Routing.Tree.TreeRouter: Request successfully matched the route with name '' and template 'api/values'.
[Debug] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker: Executing action ToggledAppServices.Controllers.ValuesController.Get (ToggledAppServices)
[Information] Microsoft.AspNetCore.Authorization.DefaultAuthorizationService: Authorization failed for user: .
[Warning] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
[Information] Microsoft.AspNetCore.Mvc.ChallengeResult: Executing ChallengeResult with authentication schemes ().
Unknown error responding to request: InvalidOperationException:
System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.ChallengeResult.<ExecuteResultAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeResultAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeAsync>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.<Invoke>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction.<ProcessRequest>d__15.MoveNext()
InvalidOperationException:
System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.ChallengeResult.<ExecuteResultAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeResultAsync>d__32.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeAsync>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.<Invoke>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction.<ProcessRequest>d__15.MoveNext()
[Information] Microsoft.AspNetCore.Hosting.Internal.WebHost: Request finished in 7360.5673ms 0
Response Base 64 Encoded: False
END RequestId: 52c7358f-e50a-11e7-a5a6-b3632cca0b75
System.InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
at Microsoft.AspNetCore.Http.Authentication.Internal.DefaultAuthenticationManager.<ChallengeAsync>d__12.MoveNext()
这表明您的身份验证处理程序的身份验证 工作正常,并且由于身份验证失败而正确创建质询结果。
所以现在,默认质询身份验证处理程序应该 运行 处理质询请求。但是,没有配置默认的质询方案,所以失败了。
为了让您的 TestAuthenticationHandler
也处理质询请求,您应该将 TestAuthenticationOptions
类型的 AutomaticChallenge
选项设置为 true
:
// handle authenticate requests by default
AutomaticAuthenticate = true;
// handle challenge requests by default
AutomaticChallenge = true;
然后,您的处理程序将 运行 质询请求,并且由于您不提供自定义实现,the default behavior 将只生成 401 HTTP 响应。