未经授权的 webapi 调用返回登录页面而不是 401



如何配置我的 mvc/webapi 项目,以便从 Razor 视图调用的 webapi 方法在未经授权时不会返回登录页面?

它是一个MVC5应用程序,它还具有WebApi控制器,用于通过javascript进行调用。

以下两种方法

[Route("api/home/LatestProblems")]      
[HttpGet()]
public List<vmLatestProblems> LatestProblems()
{
    // Something here
}
[Route("api/home/myLatestProblems")]
[HttpGet()]
[Authorize(Roles = "Member")]
public List<vmLatestProblems> mylatestproblems()
{
   // Something there
}

通过以下角度代码调用:

angular.module('appWorship').controller('latest', 
    ['$scope', '$http', function ($scope,$http) {         
        var urlBase = baseurl + '/api/home/LatestProblems';
        $http.get(urlBase).success(function (data) {
            $scope.data = data;
        }).error(function (data) {
            console.log(data);
        });
        $http.get(baseurl + '/api/home/mylatestproblems')
          .success(function (data) {
            $scope.data2 = data;
        }).error(function (data) {
            console.log(data);
        });  
    }]
);

所以我没有登录,第一种方法成功返回数据。 第二种方法返回(在成功函数中(包含等效登录页面的数据。 即,如果您请求了一个标有 [授权] 且您没有登录的控制器操作,您将在 MVC 中获得什么。

我希望它返回未经授权的 401,以便我可以根据用户是否登录为用户显示不同的数据。 理想情况下,如果用户已登录,我希望能够访问控制器的 User 属性,以便我可以返回特定于该成员的数据。

更新:由于下面的建议似乎都不再有效(对身份或WebAPI的更改(,我在github上创建了一个原始示例,该示例应该说明问题。

Brock Allen 有一篇很好的博客文章,介绍如何在使用 Cookie 身份验证和 OWIN 时为 ajax 调用返回 401。http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/

将其放在 Startup.Auth.cs 文件中的 ConfigureAuth 方法中:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
  AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
  LoginPath = new PathString("/Account/Login"),
  Provider = new CookieAuthenticationProvider
  {
    OnApplyRedirect = ctx =>
    {
      if (!IsAjaxRequest(ctx.Request))
      {
        ctx.Response.Redirect(ctx.RedirectUri);
      }
    }
  }
});
private static bool IsAjaxRequest(IOwinRequest request)
{
  IReadableStringCollection query = request.Query;
  if ((query != null) && (query["X-Requested-With"] == "XMLHttpRequest"))
  {
     return true;
  }
  IHeaderDictionary headers = request.Headers;
  return ((headers != null) && (headers["X-Requested-With"] == "XMLHttpRequest"));
}
如果要在

MVC 网站中添加 asp.net WebApi asp.net 您可能希望对某些请求进行未经授权的响应。但是 ASP.NET 基础设施开始发挥作用,当您尝试将响应状态代码设置为HttpStatusCode.Defined时,您将获得302重定向到登录页面。

如果您在此处使用 asp.net 身份和基于owin的身份验证,则代码可以帮助解决此问题:

public void ConfigureAuth(IAppBuilder app)
{
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/Account/Login"),
        Provider = new CookieAuthenticationProvider()
        {
            OnApplyRedirect = ctx =>
            {
                if (!IsApiRequest(ctx.Request))
                {
                    ctx.Response.Redirect(ctx.RedirectUri);
                }
            }
        }
    });
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}

private static bool IsApiRequest(IOwinRequest request)
{
    string apiPath = VirtualPathUtility.ToAbsolute("~/api/");
    return request.Uri.LocalPath.StartsWith(apiPath);
}

有两个 AuthorizeAttribute 实现,您需要确保为 Web API 引用正确的实现。 有用于Web API的System.Web.Http.AuthorizeAttribute,以及用于具有视图的控制器的System.Web.Mvc.AuthorizeAttribute 如果授权失败,Http.AuthorizeAttribute 将返回 401 错误,并且 Mvc.AuthorizeAttribute 将重定向到登录页面。

更新 11/26/2013

因此,正如Brock Allen在他的文章中指出的那样,MVC 5似乎发生了巨大变化。 我想 OWIN 管道接管并引入了一些新行为。 现在,当用户未获得授权时,将返回状态 200,并在 HTTP 标头中显示以下信息。

X-Responded-JSON: {"status":401,"headers":{"location":"http://localhost:59540/Account/Login?ReturnUrl=%2Fapi%2FTestBasic"}}

您可以在客户端更改逻辑以检查标头中的此信息以确定如何处理此问题,而不是在错误分支上查找 401 状态。

我尝试通过在 OnAuthorization 和 HandleUnauthorizedRequest 方法中的响应中设置状态来覆盖自定义 AuthorizeAttribute 中的此行为。

actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);

但这没有用。新管道必须稍后获取此响应,并将其修改为我之前收到的相同响应。 抛出 HttpException 也不起作用,因为它只是更改为 500 错误状态。

我测试了Brock Allen的解决方案,当我使用jQuery ajax调用时,它确实有效。如果它不适合您,我的猜测是因为您正在使用角度。 使用 Fiddler 运行测试,看看标头中是否包含以下内容。

X-Requested-With: XMLHttpRequest

如果不是,那就是问题所在。我不熟悉角度,但如果它允许您插入自己的标头值,然后将其添加到您的 ajax 请求中,它可能会开始工作。

当OWIN总是将401响应重定向到WebApi的登录页面时,我遇到了同样的情况.我们的Web API不仅支持来自Angular的ajax调用,还支持Mobile,Win Form调用。因此,检查请求是否为 ajax 请求的解决方案并没有真正针对我们的情况进行排序。

我选择了另一种方法是注入新的标头响应:Suppress-Redirect响应是否来自webApi。实现在处理程序上:

public class SuppressRedirectHandler : DelegatingHandler
{
    /// <summary>
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken).ContinueWith(task =>
        {
            var response = task.Result;
            response.Headers.Add("Suppress-Redirect", "True");
            return response;
        }, cancellationToken);
    }
}

并在 WebAPI 的全局级别注册此处理程序:

config.MessageHandlers.Add(new SuppressRedirectHandler());

因此,在 OWIN 启动时,您可以检查响应标头是否具有Suppress-Redirect

public void Configuration(IAppBuilder app)
{
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationMode = AuthenticationMode.Active,
        AuthenticationType = DefaultApplicationTypes.ApplicationCookie,
        ExpireTimeSpan = TimeSpan.FromMinutes(48),
        LoginPath = new PathString("/NewAccount/LogOn"),
        Provider = new CookieAuthenticationProvider()
        {
            OnApplyRedirect = ctx =>
            {
                var response = ctx.Response;
                if (!IsApiResponse(ctx.Response))
                {
                    response.Redirect(ctx.RedirectUri);
                }
            }
        }
    });
}
private static bool IsApiResponse(IOwinResponse response)
{
    var responseHeader = response.Headers;
    if (responseHeader == null) 
        return false;
    if (!responseHeader.ContainsKey("Suppress-Redirect"))
        return false;
    if (!bool.TryParse(responseHeader["Suppress-Redirect"], out bool suppressRedirect))
        return false;
    return suppressRedirect;
}

在以前的 ASP.NET 版本中,您必须做一大堆事情才能使其正常工作。

好消息是,由于您使用的是 4.5 ASP.NET,因此您可以使用新的 HttpResponse.SuppressFormsAuthenticationRedirect 属性禁用窗体身份验证重定向。

Global.asax

protected void Application_EndRequest(Object sender, EventArgs e)
{
        HttpApplication context = (HttpApplication)sender;
        context.Response.SuppressFormsAuthenticationRedirect = true;
}

编辑:您可能还想看看Sergey Zwezdin的这篇文章,它以更精细的方式完成您要做的事情。

下面粘贴了相关代码片段和作者旁白。代码和旁白的原作者——谢尔盖·兹韦兹丁。

首先,让我们确定当前的HTTP请求是否是AJAX请求。如果是,我们应该禁用将 HTTP 401 替换为 HTTP 302:

public class ApplicationAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        var httpContext = filterContext.HttpContext;
        var request = httpContext.Request;
        var response = httpContext.Response;
        if (request.IsAjaxRequest())
            response.SuppressFormsAuthenticationRedirect = true;
        base.HandleUnauthorizedRequest(filterContext);
    }
}

其次,让我们添加一个条件::如果用户经过身份验证,那么我们将发送HTTP 403;否则发送HTTP 401。

public class ApplicationAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        var httpContext = filterContext.HttpContext;
        var request = httpContext.Request;
        var response = httpContext.Response;
        var user = httpContext.User;
        if (request.IsAjaxRequest())
        {
            if (user.Identity.IsAuthenticated == false)
                response.StatusCode = (int)HttpStatusCode.Unauthorized;
            else
                response.StatusCode = (int)HttpStatusCode.Forbidden;
            response.SuppressFormsAuthenticationRedirect = true;
            response.End();
        }
        base.HandleUnauthorizedRequest(filterContext);
    }
}

干的好。现在我们应该用这个新过滤器替换标准 AuthorizeAttribute 的所有用法。它可能不适用于作为代码美学家的 sime 家伙。但我不知道任何其他方法。如果你有,让我们去评论,请。

最后,我们应该做的是 - 在客户端添加HTTP 401/403处理。我们可以在jQuery上使用ajaxError来避免代码重复:

$(document).ajaxError(function (e, xhr) {
    if (xhr.status == 401)
        window.location = "/Account/Login";
    else if (xhr.status == 403)
        alert("You have no enough permissions to request this resource.");
});

结果——

  • 如果用户未通过身份验证,则他将被重定向到登录名页面在任何 AJAX 调用之后。
  • 如果用户已通过身份验证,但没有足够的权限,则他将看到用户友好的 erorr 消息。
  • 如果用户经过身份验证并具有足够的权限,则没有任何错误,HTTP请求将照常进行。

如果要从MVC项目中运行Web API,则需要创建自定义AuthorizeAttribute以应用于API方法。在IsAuthorized override中,您需要获取当前HttpContext以防止重定向,如下所示:

    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        if (string.IsNullOrWhiteSpace(Thread.CurrentPrincipal.Identity.Name))
        {
            var response = HttpContext.Current.Response;
            response.SuppressFormsAuthenticationRedirect = true;
            response.StatusCode = (int)System.Net.HttpStatusCode.Forbidden;
            response.End();
        }
        return base.IsAuthorized(actionContext);
    }

我自己使用 Azure Active Directory 集成,使用 CookieAuthentication 中间件的方法对我不起作用。我必须执行以下操作:

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        ...
        Notifications = new OpenIdConnectAuthenticationNotifications
        {   
            ...         
            RedirectToIdentityProvider = async context =>
            {
                if (!context.Request.Accept.Contains("html"))
                {
                    context.HandleResponse();
                }
            },
            ...
        }
    });

如果请求来自浏览器本身(例如,不是 AJAX 调用(,则 Accept 标头将包含html的字符串。只有当客户端接受HTML时,我才会认为重定向是有用的。

我的客户端应用程序可以处理 401,通知用户该应用程序不再具有访问权限,需要重新加载才能再次登录。

我还有一个带有WebApi(使用OWIN(的MVC5应用程序(System.Web(,只是想防止WebApi的401响应更改为302响应。

对我有用的是创建一个自定义版本的WebApi AuthorizeAttribute,如下所示:

public class MyAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        base.HandleUnauthorizedRequest(actionContext);
        HttpContext.Current.Response.SuppressFormsAuthenticationRedirect = true;
    }
}

并使用它来代替标准的WebApi AuthorizeAttribute。 我使用标准的MVC AuthorizeAttribute来保持MVC行为不变。

只需安装以下 NeGet 包

Install-Package Microsoft.AspNet.WebApi.Owin

在 WebApiConfig 文件中编写以下代码。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //Web API configuration and services
        //Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
    }
}

混合MVC和WebAPI,如果请求未经授权,那么即使在WebAPI请求中,它也会重定向到登录页面。为此,我们可以添加以下代码来向移动应用程序发送响应

protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
    var httpContext = HttpContext.Current;
    if (httpContext == null)
    {
        base.HandleUnauthorizedRequest(actionContext);
        return;
    }
    actionContext.Response = httpContext.User.Identity.IsAuthenticated == false ?
        actionContext.Request.CreateErrorResponse(
      System.Net.HttpStatusCode.Unauthorized, "Unauthorized") :
       actionContext.Request.CreateErrorResponse(
      System.Net.HttpStatusCode.Forbidden, "Forbidden");
    httpContext.Response.SuppressFormsAuthenticationRedirect = true;
    httpContext.Response.End();
}
如果你想

捕获内容类型==应用程序/json,你可以使用该代码:

private static bool IsAjaxRequest(IOwinRequest request)
    {
        IReadableStringCollection queryXML = request.Query;
        if ((queryXML != null) && (queryXML["X-Requested-With"] == "XMLHttpRequest"))
        {
            return true;
        }
        IReadableStringCollection queryJSON = request.Query;
        if ((queryJSON != null) && (queryJSON["Content-Type"] == "application/json"))
        {
            return true;
        }
        IHeaderDictionary headersXML = request.Headers;
        var isAjax = ((headersXML != null) && (headersXML["X-Requested-With"] == "XMLHttpRequest"));
        IHeaderDictionary headers = request.Headers;
        var isJson = ((headers != null) && (headers["Content-Type"] == "application/json"));
        return isAjax || isJson;
    }

问候!!

我很难在OnAuthorization/HandleUnauthorizedRequest方法中同时获得状态代码和文本响应。 事实证明,这对我来说是最好的解决方案:

    actionContext.Response = new HttpResponseMessage()
    {
        StatusCode = HttpStatusCode.Forbidden,
        Content = new StringContent(unauthorizedMessage)
    };

谢谢大家!

就我而言,我结合了cuongle和Shiva的答案,得到了这样的结果:

在控制器的 API 异常的 OnException(( 处理程序中:

filterContext.ExceptionHandled = true;
//...
var response = filterContext.HttpContext.Response;
response.Headers.Add("Suppress-Redirect", "true");
response.SuppressFormsAuthenticationRedirect = true;

在应用中启动配置代码:

app.UseCookieAuthentication(new CookieAuthenticationOptions {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/Account/Login"),
        Provider = new CookieAuthenticationProvider {
            OnValidateIdentity = ctx => {
                return validateFn.Invoke(ctx);
            },
            OnApplyRedirect = ctx =>
            {
                bool enableRedir = true;
                if (ctx.Response != null)
                {
                    string respType = ctx.Response.ContentType;
                    string suppress = ctx.Response.Headers["Suppress-Redirect"];
                    if (respType != null)
                    {
                        Regex rx = new Regex("^application\/json(;(.*))?$",
                            RegexOptions.IgnoreCase);
                        if (rx.IsMatch(respType))
                        {
                            enableRedir = false;
                        }  
                    }
                    if ((!String.IsNullOrEmpty(suppress)) && (Boolean.Parse(suppress)))
                    {
                        enableRedir = false;
                    }
                }
                if (enableRedir)
                {
                    ctx.Response.Redirect(ctx.RedirectUri);
                }
            }
        }
    });

在尝试避免重定向到登录页面之后,我意识到这实际上非常适合授权属性。它说去获得授权。相反,对于未经授权的 API 调用,我只是不想向黑客透露任何信息。通过添加从 Authorization 派生的新属性,将内容隐藏为 404 错误,更容易直接实现此目标:

public class HideFromAnonymousUsersAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
         actionContext.Response = ActionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "Access Restricted");
    }
}

在带有 Dot Net Framework 4.5.2 的 MVC 5 中,我们得到了"应用程序/JSON,普通文本.." 在"接受"标题下像下面这样使用会很好:

isJson = headers["Content-Type"] == "application/json" || headers["Accept"].IndexOf("application/json", System.StringComparison.CurrentCultureIgnoreCase) >= 0;

相关内容

  • 没有找到相关文章

最新更新