c# - अनधिकृत वेबपी कॉल 401 के बजाय लॉगिन पेज लौटा रहा है





asp.net-web-api asp.net-mvc-5 asp.net-identity owin (12)


लॉगिन पृष्ठ पर पुनर्निर्देशन से बचने की कोशिश करने के बाद, मुझे एहसास हुआ कि यह वास्तव में प्राधिकृत विशेषता के लिए काफी उपयुक्त है। यह कह रहा है और प्राधिकरण प्राप्त करें। इसके बजाय एपीआई कॉल जो अधिकृत नहीं हैं, मैं सिर्फ हैकर्स होने के लिए कोई जानकारी प्रकट नहीं करना चाहता था। प्राधिकरण से व्युत्पन्न एक नई विशेषता जोड़कर सीधे यह लक्ष्य प्राप्त करना आसान था, जो सामग्री को 404 त्रुटि के रूप में छुपाता है:

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

मैं अपने एमवीसी / वेबपीआई प्रोजेक्ट को कैसे कॉन्फ़िगर कर सकता हूं ताकि एक रेजर व्यू से बुलाया गया वेबपैआई विधि अनधिकृत होने पर लॉगिन पेज वापस न करे?

इसका एक एमवीसी 5 एप्लीकेशन है जिसमें जावास्क्रिप्ट के माध्यम से कॉल के लिए वेबएपीआई नियंत्रक भी हैं।

नीचे दो विधियां

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

इसलिए मैं लॉग इन नहीं हूं और पहली विधि सफलतापूर्वक डेटा लौटाती है। दूसरी विधि रिटर्न (सफलता समारोह में) डेटा जिसमें लॉगिन पृष्ठ के बराबर होता है। यानी एमवीसी में आपको क्या मिलेगा यदि आपने नियंत्रक कार्रवाई का अनुरोध किया था जो [अधिकृत] के साथ मुद्रित था और आप लॉग इन नहीं थे।

मैं चाहता हूं कि यह 401 अनधिकृत लौटाए, ताकि मैं उपयोगकर्ताओं के लिए अलग-अलग डेटा प्रदर्शित कर सकूं, अगर वे लॉग इन हैं या नहीं। आदर्श रूप में यदि उपयोगकर्ता लॉग इन है तो मैं नियंत्रक की उपयोगकर्ता संपत्ति तक पहुंचने में सक्षम होना चाहता हूं, इसलिए मैं उस सदस्य को विशिष्ट डेटा वापस कर सकता हूं।

अद्यतन: चूंकि नीचे दिए गए कोई भी सुझाव अब काम नहीं कर रहे हैं (पहचान या वेबएपीआई में परिवर्तन) आईव ने जिथब पर एक कच्चा उदाहरण बनाया है जो समस्या को स्पष्ट करना चाहिए।




मुझे ऑन-प्रमाणीकरण / हैंडल यूनाधिकृत रिक्वेस्ट विधियों में काम कर रहे स्टेटस कोड और टेक्स्ट प्रतिक्रिया दोनों को मुश्किल समय मिल रहा था। यह मेरे लिए सबसे अच्छा समाधान साबित हुआ:

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



बस निम्नलिखित नेगेट पैकेज स्थापित करें

इंस्टॉल-पैकेज 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"));
    }
}



यदि आप एएसपीनेट एमवीसी वेब साइट के अंदर एएसपीनेट वेबएपी जोड़ रहे हैं तो आप शायद कुछ अनुरोधों के लिए अनधिकृत प्रतिक्रिया देना चाहते हैं। लेकिन फिर एएसपी.NET आधारभूत संरचना खेलती है और जब आप HttpStatusCode को प्रतिक्रिया स्थिति कोड सेट करने का प्रयास करते हैं। अनधिकृत आपको लॉगिन पृष्ठ पर 302 रीडायरेक्ट मिलेगा।

यदि आप एएसपीनेट पहचान और ओविन आधारित प्रमाणीकरण का उपयोग कर रहे हैं तो एक कोड जो उस समस्या को हल करने में मदद कर सकता है:

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



एएसपी.नेट के पिछले संस्करणों में, आपको यह काम करने के लिए सामानों का एक पूरा समूह करना था।

अच्छी खबर यह है कि, क्योंकि आप एएसपी.NET 4.5 का उपयोग कर रहे हैं। आप नए HttpResponse.SuppressFormsAuthenticationRedirect प्रॉपर्टी का उपयोग कर फ़ॉर्म प्रमाणीकरण रीडायरेक्ट को अक्षम कर सकते हैं।

Global.asax :

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

संपादित करें : आप सर्गेई ज़्वेज़द्दीन द्वारा इस आलेख को भी देखना चाहते हैं , जिसमें आप जो करने की कोशिश कर रहे हैं उसे पूरा करने का एक अधिक परिष्कृत तरीका है।

प्रासंगिक कोड स्निपेट और लेखक वर्णन नीचे चिपकाया। कोड और विवरण के मूल लेखक - सर्गेई ज़्वेज़द्दीन

सबसे पहले - निर्धारित करें कि वर्तमान HTTP अनुरोध AJAX- अनुरोध है या नहीं। यदि हां, तो हमें HTTP 302 को 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 के सभी उपयोगों को प्रतिस्थापित करना चाहिए। यह सिम लड़कों के लिए लागू नहीं हो सकता है, जो कोड का अनुमान है। लेकिन मुझे किसी अन्य तरीके से नहीं पता। यदि आपके पास है, तो कृपया टिप्पणियों पर जाएं।

अंतिम, हमें क्या करना चाहिए - क्लाइंट-साइड पर 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-अनुरोध सामान्य रूप से आगे बढ़ेगा।



कुकी प्रमाणीकरण और ओविन का उपयोग करते समय एजेक्स कॉल के लिए 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"));
}



Azure Active Directory एकीकरण का उपयोग करके, CookieAuthentication मिडलवेयर का उपयोग करने का दृष्टिकोण मेरे लिए काम नहीं करता था। मुझे निम्नलिखित करना था:

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

यदि अनुरोध ब्राउज़र से आता है (और उदाहरण के लिए, AJAX कॉल नहीं) तो स्वीकार करने वाले शीर्षलेख में स्ट्रिंग html कहीं भी होगा। केवल जब ग्राहक HTML स्वीकार करता है तो मैं कुछ रीडायरेक्ट को कुछ उपयोगी मानता हूं।

मेरा क्लाइंट एप्लिकेशन 401 को उपयोगकर्ता को सूचित कर सकता है कि ऐप के पास और अधिक पहुंच नहीं है और फिर से लॉगिन करने के लिए पुनः लोड करने की आवश्यकता है।




डॉट नेट फ्रेमवर्क 4.5.2 के साथ एमवीसी 5 में हमें "स्वीकृति" हेडर के तहत "एप्लिकेशन / जेसन, पेंट टेक्स्ट .." मिल रहा है। निम्नलिखित का उपयोग करना अच्छा होगा:

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



यदि आप सामग्री-प्रकार == एप्लिकेशन / जेसन को पकड़ना चाहते हैं तो आप उस कोड का उपयोग कर सकते हैं:

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;

    }

सादर!!




मुझे वही स्थिति मिली जब ओविन हमेशा WebApi से लॉगिन पेज पर 401 प्रतिक्रिया को रीडायरेक्ट करता है। हमारा वेब एपीआई न केवल कोणीय से एजेक्स कॉल का समर्थन करता है बल्कि मोबाइल, विन फॉर्म कॉल भी करता है। इसलिए, यह जांचने का समाधान है कि अनुरोध AJAX अनुरोध है या नहीं, वास्तव में हमारे मामले के लिए हल नहीं किया गया है।

मैंने नया हेडर प्रतिक्रिया इंजेक्ट करने के लिए एक और तरीका चुना है: अगर प्रतिक्रियाएं वेबएपी से आती हैं तो दबाएं 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);
    }
}

और इस हैंडलर को वेबएपी के वैश्विक स्तर पर पंजीकृत करें:

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

तो, ओविन स्टार्टअप पर आप यह जांचने में सक्षम हैं कि प्रतिक्रिया हेडर में 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 चला रहे हैं, तो आपको अपने API तरीकों पर लागू करने के लिए एक कस्टम AuthorizeAttribute बनाना होगा। IsAuthorized override भीतर आपको रीडायरेक्शन को रोकने के क्रम में वर्तमान HttpContext को पकड़ने की आवश्यकता है, जैसे:

    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        if (Thread.CurrentPrincipal.Identity.Name.Length == 0)
        {
            // If using Basic Authentication, do that here
        }

        if (Thread.CurrentPrincipal.Identity.Name.Length == 0)
        {
            var response = HttpContext.Current.Response;
            response.SuppressFormsAuthenticationRedirect = true;
            response.StatusCode = (int)System.Net.HttpStatusCode.Forbidden;
            response.End();
        }

        return base.IsAuthorized(actionContext);
    }



मुझे अभी तक कोई जवाब नहीं दिख रहा है जो प्रदर्शन का उल्लेख करता है। Where() या Count() में Func<> s गुजर रहा है खराब है। असली बुरा यदि आप एक Func<> उपयोग करते हैं तो यह IQueryable बजाय IEnumerable LINQ सामान को कॉल करता है, जिसका अर्थ है कि पूरी तालिकाएं खींची जाती हैं और फिर फ़िल्टर की जाती हैं। Expression<Func<>> काफी तेज़ है, खासकर यदि आप ऐसे डेटाबेस से पूछ रहे हैं जो एक और सर्वर रहता है।





c# asp.net-web-api asp.net-mvc-5 asp.net-identity owin