获取 IIS 未授权 html 页面而不是 Web api 未授权状态的未授权响应

Getting IIS unauthorized html page instead of web api unauthorized response for unauthorized status

我有一个 Web API 应用程序托管在 IIS 10 (Windows Server 2016) 上。该应用程序有一个 API 用于登录。登录名 API 装饰有实现 IAuthenticationFilter 的自定义身份验证过滤器属性。

[RoutePrefix("api/login")]
[AllowUnAuthorized]
[AuthenticationFilter]

public class LoginController : ApiController
{

    [HttpPost]
    public IHttpActionResult Login()
    {
        //Code to return token if passed authentication in the Authentication Filter
    }
 }   

如果登录凭据(用户名和密码)无效,则身份验证筛选器属性会在 returns 状态代码 401 ("Unauthorized") 的上下文中设置一个 ErrorResult 以及响应消息。

public class AuthenticationFilter : Attribute, IAuthenticationFilter
{
    public bool AllowMultiple => false;

    public async Task AuthenticateAsync(HttpAuthenticationContext context, 
    CancellationToken cancellationToken)
    {
        HttpRequestMessage request = context.Request;
        AuthenticationHeaderValue authorization = 
         request.Headers.Authorization;

        if (authorization == null)
        {
            return;
        }
        if (authorization.Scheme != "Basic")
        {
            return;
        }


        Tuple<string, string> credentials = 
        ExtractCredentials(request.Headers);
        if (credentials == null)
        {
            context.ErrorResult = new AuthenticationFailureResult("Invalid 
            credentials", request);                
            return;
        }
        string userName = credentials.Item1;
        string password = credentials.Item2;


        IAuthenticationService 
        service=container.Resolve<IAuthenticationService>();
        var user = service.GetUser(userName, password);
        if (user ==  null)
        {
            context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);                
            return;
        }

        //Code to set principal if authentication passed
}

这是 AuthenticationFailureResult 的代码 class。

internal class AuthenticationFailureResult : IHttpActionResult
{
    public string ReasonPhrase { get; private set; }

    public HttpRequestMessage Request { get; private set; }

    public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request)
    {
        ReasonPhrase = reasonPhrase;
        Request = request;
    }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute());
    }

    private HttpResponseMessage Execute()
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
        response.RequestMessage = Request;
        response.ReasonPhrase = ReasonPhrase;
        return response;
    }
}

此代码运行良好并返回 401 状态代码以及原因短语中指定的消息。但是我不知道由于什么变化,API 突然返回一个通常由 IIS 返回的 html 页面,消息为 "401 - Unauthorized: Access is denied由于凭据无效。" 而不是身份验证过滤器中设置的错误响应。请注意,在 IISExpress

中 运行 时,相同的 API 会按预期工作

到目前为止我做了什么:

  1. 已检查 Web.config 并确认 CustomErrors 模式设置为 "On"
  2. 确保在 IIS 中站点的 "Authentication" 部分启用了 "Anonymous Authentication"
  3. 在 Web.config

    中添加了以下内容
    <system.webServer>
      <modules runAllManagedModulesForAllRequests="true"/>
    </system.webServer>
    

奇怪的是,网络 api returns 正确 JSON 响应当认证通过时

编辑 1:

这是 ChallengeAsync 方法

public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
        {
            var challenge = new AuthenticationHeaderValue("Basic");
            context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
            return Task.FromResult(0);
        }

AddChallengeOnUnauthorizedResult 的实施

public class AddChallengeOnUnauthorizedResult : IHttpActionResult
    {
        public AddChallengeOnUnauthorizedResult(AuthenticationHeaderValue challenge, IHttpActionResult innerResult)
        {
            Challenge = challenge;
            InnerResult = innerResult;
        }

        public AuthenticationHeaderValue Challenge { get; private set; }

        public IHttpActionResult InnerResult { get; private set; }

        public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = await InnerResult.ExecuteAsync(cancellationToken);

            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                // Only add one challenge per authentication scheme.
                if (!response.Headers.WwwAuthenticate.Any((h) => h.Scheme == Challenge.Scheme))
                {
                    response.Headers.WwwAuthenticate.Add(Challenge);
                }
            }

            return response;
        }
    }

解决方案是在 Web.config 中添加以下两个设置。

 <system.webServer>
            <httpErrors existingResponse="PassThrough" />
            <modules runAllManagedModulesForAllRequests="true" />
  </system.webServer>