等待SignInManager.PasswordSignInAsync在自定义身份验证期间挂起

时间:2018-04-22 21:46:28

标签: c# authentication xamarin.forms

在"移动应用程序(Xamarin.Forms)" + .NET Standard + MobileAppService解决方案,我尝试使用http://blog.isharepoint.co.uk/?p=83中的代码。 await SignInManager.PasswordSignInAsync方法调用无限期挂起。我使用Postman调用Auth控制器,可以确认收到的电子邮件和密码是否正常。一旦发生挂起,我无法调试以找到错误。在添加自定义身份验证代码之前,该项目工作正常。最后,我在本地系统和Azure Web服务器上都获得了相同的行为。

这是AuthController.cs文件(参见IsPasswordValid方法):

namespace MyAzure.MobileAppService.Controllers
{
    public class AuthController : ApiController
    {
        private ApplicationUserManager _userManager;
        private ApplicationSignInManager _signInManager;
        private string signingKey, audience, issuer;

        public ApplicationUserManager UserManager
        {
            get
            {
                return _userManager ?? HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
            }
            private set
            {
                _userManager = value;
            }
        }

        public ApplicationSignInManager SignInManager
        {
            get
            {
                return _signInManager ?? HttpContext.Current.GetOwinContext().Get<ApplicationSignInManager>();
            }
            private set
            {
                _signInManager = value;
            }
        }

        public AuthController()
        { }

        public AuthController(ApplicationUserManager userManager, ApplicationSignInManager signInManager)
        {
            UserManager = userManager;
            SignInManager = signInManager;
        }

        public IHttpActionResult Post([FromBody] JObject assertion)
        {
            bool passValid = IsPasswordValid(assertion).Result;

            if (passValid)
            {
                if (Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY") == null)
                {
                    signingKey = ConfigurationManager.AppSettings["SigningKey"];
                    audience = ConfigurationManager.AppSettings["ValidAudience"];
                    issuer = ConfigurationManager.AppSettings["ValidIssuer"];
                }
                else
                {
                    signingKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
                    var azureAppServiceEndpoint = Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME");
                    audience = $"https://{azureAppServiceEndpoint}/";
                    issuer = $"https://{azureAppServiceEndpoint}/";
                }

                string username = assertion.GetValue("email").Value<string>();

                JwtSecurityToken token = AppServiceLoginHandler.CreateToken(new Claim[] { new Claim(JwtRegisteredClaimNames.Sub, username) },
                    signingKey,
                    audience,
                    issuer,
                    TimeSpan.FromHours(24));

                return Ok(new LoginResult()
                {
                    AuthenticationToken = token.RawData,
                    User = new LoginResultUser() { UserId = username }
                });
            }
            else // user assertion was not valid
            {
                return Content(HttpStatusCode.Unauthorized, "Wrong credentials");
            }
        }

        private async Task<bool> IsPasswordValid(JObject assertion)
        {
            // this is where we would do checks agains a database
            try
            {
                string username = assertion.GetValue("email").ToString();
                string password = assertion.GetValue("password").ToString();

                var signInStatus = await SignInManager.PasswordSignInAsync(username, password, true, shouldLockout: true);

                switch (signInStatus)
                {
                    case SignInStatus.Success:
                        return true;
                    case SignInStatus.LockedOut:
                        return false;
                    case SignInStatus.RequiresVerification:
                        return false;
                    case SignInStatus.Failure:
                    default:
                        return false;
                }

            }
            catch (Exception ex)
            {
                return false;
            }
        }
    }
}

这是ApplicationDbContext.cs

    using System.Data.Entity;
    using Microsoft.AspNet.Identity.EntityFramework;
    using MyAzure.MobileAppService.DataObjects;
    using System.Data.Entity.ModelConfiguration.Conventions;
    using Microsoft.Azure.Mobile.Server.Tables;
    using System.Linq;

    namespace MyAzure.MobileAppService.Models
    {
        public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
        {
            private const string connectionStringName = "Name=MS_TableConnectionString";

            public DbSet<Item> Items { get; set; }

            public ApplicationDbContext()
                : base(connectionStringName, throwIfV1Schema: false)
            {
                Configuration.ProxyCreationEnabled = false;
                Configuration.LazyLoadingEnabled = false;
            }

            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);

                modelBuilder.Conventions.Add(
                    new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
                        "ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
            }

            public static ApplicationDbContext Create()
            {
                return new ApplicationDbContext();
            }
        }
    }

ApplicationUser.cs

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System.Security.Claims;
using System.Threading.Tasks;

namespace MyAzure.MobileAppService.Models
{
    public class ApplicationUser : IdentityUser
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }
    }
}

我在Debug控制台输出中看到的最后一个条目是:

  

&#39; iisexpress.exe&#39; (CLR v4.0.30319:   / LM / W3SVC / 2 / ROOT-1-131689041142085279):已加载   &#39; C:\ Windows \ Microsoft.Net \组件\ GAC_32 \ System.Data.OracleClient的\ v4.0_4.0.0.0__b77a5c561934e089 \ System.Data.OracleClient.dll&#39 ;.   跳过加载符号。模块已优化并具有调试器选项   &#39; Just My Code&#39;已启用。

     

&#39; iisexpress.exe&#39; (CLR v4.0.30319:   / LM / W3SVC / 2 / ROOT-1-131689041142085279):已加载   &#39; EntityFrameworkDynamicProxies-MyAzure.MobileAppService&#39;

在日志中提到System.Data.OracleClient非常奇怪。我在项目中找不到引用它的任何地方。

1 个答案:

答案 0 :(得分:1)

混合使用.Result.Wait()等异步和阻止调用可能会导致死锁。

...IsPasswordValid(assertion).Result; 

使控制器操作异步,然后等待必要的方法调用。

public async Task<IHttpActionResult> Post([FromBody] JObject assertion) {
    bool passValid = await IsPasswordValid(assertion);

    //...code removed for brevity
}

参考Async/Await - Best Practices in Asynchronous Programming