HttpContext.Current 在 TokenCache.BeforeAccess 上为空

Posted

技术标签:

【中文标题】HttpContext.Current 在 TokenCache.BeforeAccess 上为空【英文标题】:HttpContext.Current is null on TokenCache.BeforeAccess 【发布时间】:2017-10-10 00:56:02 【问题描述】:

我正在针对 Azure AD 使用 OWIN 和 OpenID Connect 测试一个 Web 项目。我正在使用此示例中的大部分代码:https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect

我遇到了一个问题,我在此文件的第 27 行遇到空异常:https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect/blob/master/TodoListWebApp/Utils/NaiveSessionCache.cs

我得到了异常,因为 HttpContext.Current 为空。

我可以看到 Load() 是从 BeforeAccessNotification() 调用的。

我的框架版本是 4.5.2,我的 web.config 中有 <httpRuntime targetFramework="4.5.2" ... >

为什么 HttpContext.Current 在这种情况下为空?


更新:

我与示例的唯一区别是我的控制器上的 ActionResult 不是异步的。 我在使用标准 ActionResult 中的 .Wait() 调用的异步任务中调用 AcquireTokenSilentAsync。 我在不允许我使用异步 ActionResults 的 CMS 中工作。


这是 OnAuthorizationCodeReceived:

    private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
    
        var code = context.Code;

        var credential = new ClientCredential(ClientId, AppKey);

        var userObjectID =
            context.AuthenticationTicket.Identity.FindFirst(
                "http://schemas.microsoft.com/identity/claims/objectidentifier").Value;

        var authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));

        var uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));

        var result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, GraphUrl);
    

这是堆栈跟踪:

[NullReferenceException: Object reference not set to an instance of an object.]
   MyTest.NaiveSessionCache.Load() in C:\Workspace\MyTest\src\Website\NaiveSessionCache.cs:26
   MyTest.NaiveSessionCache.BeforeAccessNotification(TokenCacheNotificationArgs args) in C:\Workspace\MyTest\src\Website\NaiveSessionCache.cs:53
   Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache.OnBeforeAccess(TokenCacheNotificationArgs args) +94
   Microsoft.IdentityModel.Clients.ActiveDirectory.<RunAsync>d__55.MoveNext() +3751
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.IdentityModel.Clients.ActiveDirectory.<AcquireTokenByAuthorizationCodeCommonAsync>d__48.MoveNext() +479
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.IdentityModel.Clients.ActiveDirectory.<AcquireTokenByAuthorizationCodeAsync>d__30.MoveNext() +386
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +31
   MyTest.<OnAuthorizationCodeReceived>d__12.MoveNext() in C:\Workspace\MyTest\src\Website\Startup.cs:86
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task) +14139265
   Microsoft.Owin.Security.OpenIdConnect.<AuthenticateCoreAsync>d__1a.MoveNext() +5965
   System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() +31
   Microsoft.Owin.Security.OpenIdConnect.<AuthenticateCoreAsync>d__1a.MoveNext() +7305
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.Owin.Security.Infrastructure.<BaseInitializeAsync>d__0.MoveNext() +824
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.Owin.Security.Infrastructure.<Invoke>d__0.MoveNext() +334
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<RunApp>d__5.MoveNext() +204
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.Owin.Security.Infrastructure.<Invoke>d__0.MoveNext() +777
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<RunApp>d__5.MoveNext() +204
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<DoFinalWork>d__2.MoveNext() +194
   Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.StageAsyncResult.End(IAsyncResult ar) +96
   System.Web.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +363
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +157

【问题讨论】:

当您在发起网络调用的线程之外的线程上调用它时,它通常为空。您能否告诉我们您是否进行了一些更改或共享调用堆栈以查看此调用的来源? 问题已更新 我无法让它工作,所以我停止使用会话,现在使用 HttpRuntime.Cache。我基本上只是用 HttpRuntime.Cache[CacheId] 替换了 HttpContext.Current.Session[CacheId]。似乎运作良好。 【参考方案1】:

我让它工作了。您必须传递 HttpContextBase 才能创建会话缓存对象。 HttpContext.Current 在不同线程上执行时变为 null。

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Threading;
using System.Web;

namespace AzureADWebApp

public class NaiveSessionCache: TokenCache

    private static ReaderWriterLockSlim SessionLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
    string UserObjectId = string.Empty;
    string CacheId = string.Empty;
    HttpContextBase HttpContext = null;
    public MSALSessionCache(string userId, HttpContextBase httpContext)
    
        UserObjectId = userId;
        CacheId = UserObjectId + "_TokenCache";
        this.HttpContext = httpContext;
        this.AfterAccess = AfterAccessNotification;
        this.BeforeAccess = BeforeAccessNotification;
        Load();
    

    public void Load()
    
        SessionLock.EnterReadLock();
        this.Deserialize((byte[])HttpContext.Session[CacheId]);
        SessionLock.ExitReadLock();
    

    public void Persist()
    
        SessionLock.EnterWriteLock();

        // Optimistically set HasStateChanged to false. We need to do it early to avoid losing changes made by a concurrent thread.
        this.HasStateChanged = false;

        // Reflect changes in the persistent store
        HttpContext.Session[CacheId] = this.Serialize();
        SessionLock.ExitWriteLock();
    

    // Empties the persistent store.
    public override void Clear()
    
        base.Clear();
        HttpContext.Session.Remove(CacheId);
    

    // Triggered right before ADAL needs to access the cache.
    // Reload the cache from the persistent store in case it changed since the last access.
    void BeforeAccessNotification(TokenCacheNotificationArgs args)
    
        Load();
    

    // Triggered right after ADAL accessed the cache.
    void AfterAccessNotification(TokenCacheNotificationArgs args)
    
        // if the access operation resulted in a cache update
        if (this.HasStateChanged)
        
            Persist();
        
    


并在 AuthenticationCodeReceived 通知中使用如下附加参数创建您的 NaiveSessionCache 对象:

 new NaiveSessionCache(userObjectID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase));

【讨论】:

【参考方案2】:

ADAL 为从版本 3.13.0 开始的所有异步方法添加 configureAwait(false),这导致 NaiveSession 中的 HttpContext.Current 变为 NULL,因为它很可能在新线程中。 您可以使用 3.12.0 版本的 ADAL,它工作正常。

【讨论】:

【参考方案3】:

我不确定是不是你的情况,但有时 HttpContext.Current null 的根本原因是 web.config 中缺少键:

&lt;add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /&gt;

【讨论】:

【参考方案4】:

通过在异步调用中添加等待,您正在启动另一个 HTTPContext.Current 为空的线程。您要么需要调整 nativesession 提供程序以将 httpcontext 作为参数,要么更改程序流程以使用 await 运算符使其在单个线程中工作,以便您可以适当地访问变量。

【讨论】:

以上是关于HttpContext.Current 在 TokenCache.BeforeAccess 上为空的主要内容,如果未能解决你的问题,请参考以下文章

在单元测试中设置 HttpContext.Current.Session

HttpContext.Current并非无处不在

设置HttpContext.Current.User会抛出null引用异常

HttpContext.Current并非无处不在

使用 IAuthenticationFilter 时 HttpContext.Current 为空

HttpContext.Current.Server未将对象引用到实例