简体   繁体   English

REST调用异常:设置内部异常时,出现401 UNAUTHORIZED循环

[英]REST call exception: 401 UNAUTHORIZED loop when inner exception is set

As a curiosity experiment, I ran a test to throw a custom WebFaultException<OurException> from a REST service. 作为好奇心实验,我运行了一个测试,以从REST服务中抛出自定义WebFaultException<OurException> I've thrown custom exceptions from WCF services in the past instead of creating faux "exceptions" using DataContract and DataMember . 我过去曾从WCF服务抛出自定义异常,而不是使用DataContractDataMember创建虚假的“异常”。 Doing this doesn't make as much sense in REST, but I was curious. 在REST中这样做没有多大意义,但我很好奇。

What I didn't expect was getting stuck in a 401 UNAUTHORIZED loop when an inner exception was set. 设置内部异常时,我没想到的是被卡在401 UNAUTHORIZED循环中。 A simple exception serialized perfectly, even for our own custom exceptions. 即使对于我们自己的自定义异常,一个简单的异常也可以完美地序列化。 If the inner exception was the same type as the outer exception, no problem. 如果内部异常与外部异常的类型相同,则没有问题。 But anything I caught and wrapped got stuck in a repeat loop of making the REST call, throwing the exception, a 401 UNAUTHORIZED response to the client with a password prompt, followed by making the rest call again after entering my password - repeat. 但是我捕获和包装的所有内容都陷入了进行REST调用的重复循环,引发异常,对客户端的401 UNAUTHORIZED响应(带有密码提示),然后在输入密码后再次进行其余调用-重复。

I finally cracked open the source for the WebFaultException<T> class and found this gem: 我终于破解了WebFaultException<T>类的源代码,发现了这个瑰宝:

[Serializable]
[System.Diagnostics.CodeAnalysis.SuppressMessage
       ("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", 
        Justification = "REST Exceptions cannot contain InnerExceptions or messages")]
public class WebFaultException {...}

So why can't they contain inner exceptions? 那么为什么它们不能包含内部异常呢? Everything serializes fine independently, so it has to be something in the inner workings of WebFaultException that either isn't implemented or explicitly prevents this for some clearly-known-to-someone reason. 一切都可以独立地很好地进行序列化,因此它必须是WebFaultException的内部工作中的WebFaultException ,由于某些众所周知的原因,它要么未实现,要么被明确阻止。

What gives? 是什么赋予了?

The interface: 界面:

[OperationContract]
[FaultContract(typeof(OurException))]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Xml, 
           BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "/TestCustomException")]
string TestCustomException();

The service method: 服务方法:

public string TestCustomException() {
    throw new WebFaultException<OurException>(new OurException("Nope.", new Exception("Still Nope.")), HttpStatusCode.InternalServerError);
}

Having found no reason why this shouldn't be done, we did it. 没有发现为什么不应该这样做的原因,我们做到了。 The service was ultimately implemented as a Web API 2 service. 该服务最终实现为Web API 2服务。 We have a custom exception and an exception filter that mimics the default exception behavior but allows us to specify the HTTP status code (Microsoft makes everything a 503). 我们有一个自定义的异常和一个异常过滤器,它模仿默认的异常行为,但允许我们指定HTTP状态代码(Microsoft将所有内容都设为503)。 Inner exceptions serialize fine... one could argue that we shouldn't include them, and if the service were exposed outside of our department, we wouldn't. 内部异常可以很好地序列化...有人可能会争辩说我们不应该包含它们,而且如果服务不在我们部门之外,我们也不会这样做。 For the moment, it's the caller's responsibility to decide whether a failure to contact the domain controller is a transient issue that can be retried. 目前,确定与域控制器联系失败是否是可以重试的暂时性问题是呼叫者的责任。

RestSharp removed the Newtonsoft dependencies some while back, but unfortunately, the deserializers it provides now (v106.4 at the time of this answer) can't handle public members in a base class - it was literally only designed to deserialize simple, non-inheriting types. RestSharp不久前就删除了Newtonsoft依赖项,但不幸的是,它现在提供的反序列化器(在此回答时为v106.4)无法处理基类中的公共成员-实际上,它只是为了反序列化简单,非继承类型。 So we had to add the Newtonsoft deserializers back ourselves... with those, inner exceptions serialize just fine. 因此,我们不得不将Newtonsoft反序列化器重新添加回自己...,那些内部异常序列化就很好了。

In any case, here's the code we ended up with: 无论如何,这是我们最终得到的代码:

[Serializable]
public class OurAdApiException : OurBaseWebException {

    /// <summary>The result of the action in Active Directory </summary>
    public AdActionResults AdResult { get; set; }

    /// <summary>The result of the action in LDS </summary>
    public AdActionResults LdsResult { get; set; }

    // Other constructors snipped...

    public OurAdApiException(SerializationInfo info, StreamingContext context) : base(info, context) {

        try {
            AdResult  = (AdActionResults) info.GetValue("AdResult",  typeof(AdActionResults));
            LdsResult = (AdActionResults) info.GetValue("LdsResult", typeof(AdActionResults));
        }
        catch (ArgumentNullException) {
            //  blah
        }
        catch (InvalidCastException) {
            //  blah blah
        }
        catch (SerializationException) {
            //  yeah, yeah, yeah
        }

    }

    [SecurityPermission(SecurityAction.LinkDemand, Flags = SerializationFormatter)]
    public override void GetObjectData(SerializationInfo info, StreamingContext context) {

        base.GetObjectData(info, context);

        //  'info' is guaranteed to be non-null by the above call to GetObjectData (will throw an exception there if null)
        info.AddValue("AdResult", AdResult);
        info.AddValue("LdsResult", LdsResult);

    }

}

And: 和:

[Serializable]
public class OurBaseWebException : OurBaseException {

    /// <summary>
    /// Dictionary of properties relevant to the exception </summary>
    /// <remarks>
    /// Basically seals the property while leaving the class inheritable.  If we don't do this,
    /// we can't pass the dictionary to the constructors - we'd be making a virtual member call
    /// from the constructor.  This is because Microsoft makes the Data property virtual, but 
    /// doesn't expose a protected setter (the dictionary is instantiated the first time it is
    /// accessed if null).  #why
    /// If you try to fully override the property, you get a serialization exception because
    /// the base exception also tries to serialize its Data property </remarks>
    public new IDictionary Data => base.Data;

    /// <summary>The HttpStatusCode to return </summary>
    public HttpStatusCode HttpStatusCode { get; protected set; }

    public InformationSecurityWebException(SerializationInfo info, StreamingContext context) : base(info, context) {

        try {
            HttpStatusCode = (HttpStatusCode) info.GetValue("HttpStatusCode", typeof(HttpStatusCode));
        }
        catch (ArgumentNullException) {
            //  sure
        }
        catch (InvalidCastException) {
            //  fine
        }
        catch (SerializationException) {
            //  we do stuff here in the real code
        }

    }
    [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
    public override void GetObjectData(SerializationInfo info, StreamingContext context) {
        base.GetObjectData(info, context);            
        info.AddValue(nameof(HttpStatusCode), HttpStatusCode, typeof(HttpStatusCode));         
    }

}

Finally, our exception filter: 最后,我们的异常过滤器:

public override void OnException(HttpActionExecutedContext context) {

    //  Any custom AD API Exception thrown will be serialized into our custom response
    //  Any other exception will be handled by the Microsoft framework
    if (context.Exception is OurAdApiException contextException) {

        try {

            //  This lets us use HTTP Status Codes to indicate REST results.
            //  An invalid parameter value becomes a 400 BAD REQUEST, while
            //  a configuration error is a 503 SERVICE UNAVAILABLE, for example.
            //  (Code for CreateCustomErrorResponse available upon request...
            //   we basically copied the .NET framework code because it wasn't
            //   possible to modify/override it :(
            context.Response = context.Request.CreateCustomErrorResponse(contextException.HttpStatusCode, contextException);

        }
        catch (Exception exception) {                 
            exception.Swallow($"Caught an exception creating the custom response; IIS will generate the default response for the object");
        }

    }

}

This allows us to throw custom exceptions from the API and have them serialized to the caller, using HTTP status codes to indicate the REST call results. 这使我们可以使用HTTP状态代码指示REST调用结果,从API抛出自定义异常并将其序列化到调用方。 We may add code in the future to log the inner exception and optionally strip it before generating the custom response. 我们将来可能会添加代码来记录内部异常,并有选择地在生成自定义响应之前将其剥离。

Usage: 用法:

catch (UserNotFoundException userNotFoundException) {
    ldsResult = NotFound;
    throw new OurAdApiException($"User '{userCN}' not found in LDS", HttpStatusCode.NotFound, adResult, ldsResult, userNotFoundException);
}

Deserializing from the RestSharp caller: 从RestSharp调用者反序列化:

public IRestResponse<T> Put<T, W, V>(string ResourceUri, W MethodParameter) where V : Exception
                                                                            where T : new() {

    //  Handle to any logging context established by caller; null logger if none was configured
    ILoggingContext currentContext = ContextStack<IExecutionContext>.CurrentContext as ILoggingContext ?? new NullLoggingContext();

    currentContext.ThreadTraceInformation("Building the request...");
    RestRequest request = new RestRequest(ResourceUri, Method.PUT) {
        RequestFormat = DataFormat.Json,
        OnBeforeDeserialization = serializedResponse => { serializedResponse.ContentType = "application/json"; }
    };
    request.AddBody(MethodParameter);

    currentContext.ThreadTraceInformation($"Executing request: {request} ");
    IRestResponse<T> response = _client.Execute<T>(request);

    #region -  Handle the Response  -

    if (response == null)            {
        throw new OurBaseException("The response from the REST service is null");
    }

    //  If you're not following the contract, you'll get a serialization exception
    //  You can optionally work with the json directly, or use dynamic 
    if (!response.IsSuccessful) {                
        V exceptionData = JsonConvert.DeserializeObject<V>(response.Content);
        throw exceptionData.ThreadTraceError();
    }

    //  Timed out, aborted, etc.
    if (response.ResponseStatus != ResponseStatus.Completed) {
        throw new OurBaseException($"Request failed to complete:  Status '{response.ResponseStatus}'").ThreadTraceError();
    }

    #endregion

    return response;

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 REST API在尝试对其进行POST时返回401未经授权的异常 - Rest API returns 401 unauthorized exception when trying to POST to it 在C#中使用Rest JSON API时获得401未经授权的Web异常 - Get 401 unauthorized web exception when consuming rest json api in c# 使用WebRequest SSL进行401未经授权的异常 - 401 Unauthorized Exception with WebRequest of SSL 从服务器收到的身份验证 header 是“协商”内部异常:远程服务器返回错误:(401)未经授权 - The authentication header received from the server was 'Negotiate' Inner Exception: The remote server returned an error: (401) Unauthorized Exchange Web Service API 和 401 未授权异常 - Exchange Web Service API and 401 unauthorized exception 如何在 Sharepoint 中引发 401(未经授权的访问)异常? - How to raise a 401 (Unauthorized access) exception in Sharepoint? Aweber API .NET SDK 401未经授权的异常 - Aweber API .NET SDK 401 unauthorized exception Discord.net 401 未经授权的异常 - Discord.net 401 Unauthorized Exception 尝试访问REST API时为401(未经授权) - 401 (Unauthorized) when trying to access REST API 无法使用Google Admin SDK创建用户-异常401未经授权 - Unable to Create User using Google Admin SDK - Exception 401 Unauthorized
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM