簡體   English   中英

未經授權的webapi呼叫返回登錄頁面而不是401

[英]Unauthorised webapi call returning login page rather than 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);
        });  
    }]
);

所以我沒有登錄,第一個方法成功返回數據。 第二種方法返回(在成功函數中)包含等效登錄頁面的數據。 也就是說,如果你請求一個標有[授權]並且你沒有登錄的控制器動作,你會在mvc中獲得什么。

我希望它返回401未授權,以便我可以根據用戶是否登錄顯示不同的數據。 理想情況下,如果用戶已登錄,我希望能夠訪問Controller的用戶屬性,以便我可以返回特定於該成員的數據。

更新:由於下面的建議似乎都不再適用(對Identity或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"));
}

如果您在asp.net MVC網站中添加asp.net WebApi,您可能希望對某些請求進行未經授權的響應。 但是當ASP.NET基礎結構發揮作用時,當您嘗試將響應狀態代碼設置為HttpStatusCode.Unauthorized時,您將獲得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的正確實現。 System.Web.Http.AuthorizeAttribute用於Web API, System.Web.Mvc.AuthorizeAttribute用於帶視圖的控制器。 如果授權失敗, Http.AuthorizeAttribute將返回401錯誤,並且Mvc.AuthorizeAttribute將重定向到登錄頁面。

更新於2013年11月26日

因此,正如Brock Allen 在他的文章中指出的那樣 MVC 5似乎已經發生了巨大的變化。 我猜OWIN管道接管並引入了一些新的行為。 現在,當用戶未被授權時,將返回狀態200,並在HTTP標頭中包含以下信息。

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

您可以在客戶端更改邏輯以檢查標頭中的此信息以確定如何處理此信息,而不是在錯誤分支上查找401狀態。

我試圖通過在OnAuthorizationHandleUnauthorizedRequest方法中設置響應中的狀態來在自定義AuthorizeAttribute中覆蓋此行為。

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

但這沒效果。 新管道必須稍后獲取此響應並將其修改為我之前獲得的相同響應。 拋出HttpException也不起作用,因為它只是變為500錯誤狀態。

我測試了Brock Allen的解決方案,當我使用jQuery ajax調用時它確實有效。 如果它不適合你,我的猜測是因為你正在使用角度。 使用Fiddler運行測試,看看標題中是否包含以下內容。

X-Requested-With: XMLHttpRequest

如果不是那么那就是問題。 我不熟悉angular,但是如果它允許你插入自己的頭值,那么將它添加到你的ajax請求中它可能會開始工作。

當OWIN總是從WebApi重定向401對Login頁面的響應時,我得到了相同的情況。我們的Web API不僅支持來自Angular的ajax調用,還支持Mobile,Win Form調用。 因此,檢查請求是否是ajax請求的解決方案實際上並沒有針對我們的情況進行排序。

我選擇了另一種方法是注入新的標頭響應:如果響應來自webApi,則Suppress-Redirect 實現是在處理程序上:

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版本中,您必須完成大量工作才能實現此功能。

好消息是,因為您使用的是ASP.NET 4.5。 您可以使用新的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 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);
    }
}

第二 - 讓我們添加一個條件::如果用戶通過身份驗證,那么我們將發送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的MVC5應用程序(System.Web)(使用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行為不變。

如果你想捕獲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;

    }

問候!!

在試圖避免重定向到登錄頁面之后,我意識到這實際上非常適合Authorize屬性。 它說要去獲得授權。 相反,對於未經授權的Api呼叫,我只是想不透露任何信息是黑客。 通過添加從Authorize派生的新屬性可以直接實現此目標,而該屬性將內容隱藏為404錯誤:

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

我很難在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"));
    }
}

多謝你們!

在我的例子中,我結合了cuongleShiva的答案,得到了這樣的結論

在Controller的API例外的OnException()處理程序中:

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

在App啟動配置代碼中:

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

在使用Dot Net Framework 4.5.2的MVC 5中,我們在“Accept”標題下獲得“application / json,plaint text ..”這將是很好用的如下:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM