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

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

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

下面的两种方法

[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);
});
}]
);

所以我没有登录,第一个方法成功地返回了数据。第二个方法返回(在成功函数中)包含相当于登录页面的数据。也就是说,如果您请求了一个带有[ Authorize ]标记的控制器操作,而您没有登录,那么您将在 mvc 中获得什么。

我希望它返回一个401未经授权,这样我可以显示不同的数据为用户,如果他们登录或没有。理想情况下,如果用户登录,我希望能够访问控制器的用户属性,这样我就可以返回数据特定的成员。

更新: 由于下面的建议似乎都不起作用了(对 Identity 或 WebAPI 的更改) ,我在 Github上创建了一个原始示例,应该可以说明这个问题。

103045 次浏览

在以前的 ASP.NET 版本中,必须使用 做很多事情才能使其工作。

好消息是,因为您使用的是 ASP.NET 4.5。可以使用新的 SuppressFormsAuthenticationRedirect属性禁用 Forms 身份验证重定向。

Global.asax:

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

编辑 : 你可能还想看看谢尔盖 · 茨韦兹丁(Sergey Zwezdin)的 在这篇文章里,它有一个更精细的方法来完成你正在尝试做的事情。

相关代码片段和作者叙述粘贴如下。代码和叙述的原作者—— Sergey Zwezdin

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

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);
}
}

其次-让我们添加一个条件: : 如果用户通过了身份验证,那么我们将发送 HTTP403; 否则发送 HTTP401。

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 的使用。它可能不适用于一些人,谁是代码的审美家。但我不知道还有什么别的办法。如果有,请点击评论。

最后,我们应该做什么——在客户端添加 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 调用后的页面。
  • 如果用户已经通过身份验证,但是没有足够的权限,那么他将看到用户友好的错误消息。
  • 如果用户已经通过身份验证并且拥有足够的权限,则不会出现任何错误,HTTP 请求将照常进行。

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"));
}

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

2013年11月26日更新

因此,正如布罗克 · 艾伦指出的 在他的文章里,MVC 5似乎发生了翻天覆地的变化。我想 OWIN 管道将接管并引入一些新的行为。现在,当用户未被授权时,将返回状态200,并在 HTTP 头中包含以下信息。

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

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

通过在 授权书处理未经授权的请求方法中设置响应中的状态,我试图在自定义 AuthorizeAttribute 授权属性中覆盖此行为。

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

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

我测试了 Brock Allen 的解决方案,当我使用 jQuery ajax 调用时,它确实起作用了。如果它不为你工作,我的猜测是,这是因为你使用有角。使用 Fiddler 运行测试,并查看头部是否包含以下内容。

X-Requested-With: XMLHttpRequest

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

如果你想捕捉 Content-Type = = application/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;


}

问候! !

在努力避免重定向到登录页面之后,我意识到这实际上非常适合 Authorise 属性。上面写着去拿授权书。对于未经授权的 Api 通话,我只是不想透露任何信息给黑客。 通过添加一个从 Authorize 派生出来的新属性(它将内容隐藏为404错误) ,可以更容易地直接实现这个目标:

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

如果你在 asp.net MVC 网站中添加 asp.net WebApi,你可能想要对一些未经授权的请求做出响应。但是 ASP.NET 基础结构会发挥作用,当您尝试将响应状态代码设置为 HttpStatusCode 时。未经授权,您将获得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);
}

在 MVC 5和点网框架4.5.2中,我们得到了 “ application/json,plaint text. .”在“ Accept”头下 这将是很好的使用像下面这样:

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

当 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;
}

如果在 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通知用户应用程序没有更多的访问权限,需要重新加载再次登录。

我还有一个 MVC5应用程序(系统。(使用 OWIN) ,只是为了防止 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 行为不变。

我在使用 OnAuthorization/HandleUnauthorizedRequest 方法获取状态代码和文本响应时遇到了困难。事实证明,这对我来说是最好的解决办法:

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

按照 NeGet 包安装即可

安装软件包 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"));
}
}

谢谢你们!

在我的例子中,我将 Cuongle湿婆的答案结合起来,得到了这样的结果:

在 Controller 针对 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);
}
}
}
});

混合使用 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();
}