防止没有确认电子邮件的用户使用Identity 2登录ASP.Net MVC

时间:2014-08-10 15:06:13

标签: asp.net-mvc asp.net-identity

在微软身份2中,用户可以确认我在此项目中确认了我从here下载的身份2示例项目的电子邮件地址,用户之间没有任何区别确认他们的电子邮件,以及谁没有&#39我希望人们如何确认他们的电子邮件无法登录这是我尝试过的:

public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }


        var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: true);
        switch (result)
        {
            case SignInStatus.Success:
                {


                    var user = await UserManager.FindByNameAsync(model.Email);
                    if (user != null)
                    {
                        if (!await UserManager.IsEmailConfirmedAsync(user.Id))
                        {
                            //first I tried this.
                            //return LogOff();
                            HttpContext.Server.TransferRequest("~/Account/LogOff");
                            return RedirectToAction("Login");
                        }
                    }

                    return RedirectToLocal(returnUrl);
                }
            case SignInStatus.LockedOut:
                return View("Lockout");
            case SignInStatus.RequiresVerification:
                return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
            case SignInStatus.Failure:
            default:
                ModelState.AddModelError("", "Invalid login attempt.");
                return View(model);

我尝试通过调用LogOff()操作方法强制用户注销,但它没有工作,用户仍然经过身份验证。然后我尝试使用Server.TransferRequest(),但我不知道为什么完成了这项工作,但它使用returnUrl =&#34; Account / Logoff&#34;将用户重定向到登录页面。 所以在他们确认了他们的电子邮件并试图登录他们得到注销后我真的很困惑! 这是我的LogOff()操作方法:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult LogOff()
    {
        AuthenticationManager.SignOut();
        return RedirectToAction("About", "Home");
    }

我用Google搜索了几天没有运气!!!!

6 个答案:

答案 0 :(得分:31)

也许它有点晚了,但我希望它可以帮助别人。

添加此

var userid = UserManager.FindByEmail(model.Email).Id;
        if (!UserManager.IsEmailConfirmed(userid))
        {
            return View("EmailNotConfirmed");
        }

var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);

第一个代码块只是检查模型中的电子邮件是否存在于数据库中并获取它的id以检查它是否未被确认,如果是,则返回给用户的视图,如果确认,则确认用户登录。

并删除对结果开关的更改,如此

switch (result)
        {
            case SignInStatus.Success:
                    return RedirectToLocal(returnUrl);
            case SignInStatus.LockedOut:
                return View("Lockout");
            case SignInStatus.RequiresVerification:
                return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
            case SignInStatus.Failure:
            default:
                ModelState.AddModelError("", "Invalid login attempt.");
                return View(model);
        }

答案 1 :(得分:6)

为什么不完成这个并重定向到正确的操作/视图,而不是移动到另一个页面:

if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
    return RedirectToAction("ConfirmEmailAddress", new { ReturnUrl = returnUrl });
}

你确实需要一个名为ConfirmEmailAddress的动作(可能还有一个视图)。

答案 2 :(得分:2)

有一个解决方案,可能不是最好的方法,但它有效。首先让我试着弄清楚为什么你的方法不起作用。

在其中一条评论中,AuthenticationManager使用cookies。要更新cookie,您需要使用其他页面将其发送到客户端。这就是TransferRequest不起作用的原因。

如何处理电子邮件验证?我使用的策略:

1)在SignInStatus.Success上,这意味着用户已登录。

2)未确认电子邮件时:向已使用的电子邮件地址发送电子邮件。这是安全的,因为用户已经登录。我们只是阻止进一步访问,直到验证电子邮件。每次用户尝试登录而未验证电子邮件时,都会发送新电子邮件(具有相同的链接)。这可以通过跟踪已发送电子邮件的数量来限制。

3)我们不能使用LogOff:这是HttpPost并使用ValidateAntiForgeryToken。

4)重定向到页面(HttpGet,需要授权),显示已发送电子邮件的消息。在输入时注销用户。

5)对于其他验证错误,重定向到另一个方法退出(HttpGet,需要授权)。无需查看,重定向到登录页面。

在代码中:将AccountController.Login中的代码更新为:

        case SignInStatus.Success:
        {
            var currentUser = UserManager.FindByNameAsync(model.Email);
            if (!await UserManager.IsEmailConfirmedAsync(currentUser.Id))
            {
                // Send email
                var code = await UserManager.GenerateEmailConfirmationTokenAsync(currentUser.Id);
                var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = currentUser.Id, code = code}, protocol: Request.Url.Scheme);
                await UserManager.SendEmailAsync(currentUser.Id, "Confirm your account", string.Format("Please confirm your account by clicking this link: <a href=\"{0}\">link</a>", callbackUrl));
                // Show message
                return RedirectToAction("DisplayEmail");
            }
            // Some validation
            if (true)
            {
                return RedirectToAction("SilentLogOff");
            }
            return RedirectToLocal(returnUrl);
        }

向AccountController添加方法:

// GET: /Account/SilentLogOff
[HttpGet]
[Authorize]
public ActionResult SilentLogOff()
{
    // Sign out and redirect to Login
    AuthenticationManager.SignOut();
    return RedirectToAction("Login");
}

// GET: /Account/DisplayEmail
[HttpGet]
[Authorize]
public ActionResult DisplayEmail()
{
    // Sign out and show DisplayEmail view
    AuthenticationManager.SignOut();
    return View();
}

DisplayEmail.cshtml

@{
    ViewBag.Title = "Verify e-mail";
}

<h2>@ViewBag.Title.</h2>
<p class="text-info">
    Please check your email and confirm your email address.
</p>

您会注意到,在验证电子邮件之前,用户无法访问其他网页。我们可以使用SignInManager的功能。

使用这种方法存在一个可能的问题(我能想到),用户登录的时间是发送电子邮件并且用户被重定向到DisplayMessage视图。这可能不是一个真正的问题,但它表明我们并没有阻止用户登录,只是在登录后通过自动注销用户来拒绝进一步访问。

===更新====

请注意,必须正确处理例外情况。授予用户访问权限,然后在此方案中撤消访问权限。但是如果在注销之前发生异常并且没有捕获此异常,则用户仍然会登录。

当邮件服务器不可用或凭据为空或无效时,可能会发生异常。

===============

答案 3 :(得分:1)

我会让管理员创建没有任何密码的用户。带链接的电子邮件应该发送给用户。然后,用户被定向到SetPassword页面以设置新密码。这样,除非他确认并设置密码,否则没有人可以访问用户帐户。

在没有密码的情况下调用CreateAsync

var adminresult = await UserManager.CreateAsync(user);

将管理员重定向到新的自定义视图,例如&#34;电子邮件发送给用户&#34;

@{
    ViewBag.Title = "New User created and Email is Sent";
}
<h2>@ViewBag.Title.</h2>
<p class="text-info">
    The New User has to follow the instructions to complete the user creation process.
</p>
<p class="text-danger">

    Please change this code to register an email service in IdentityConfig to send an email.
</p>

答案 4 :(得分:0)

如果电子邮件根本不存在于数据存储中,@ INFINITY_18的答案可能会导致Object reference not set to an instance of an object错误。为什么不在这种情况下返回带有模型错误的Login视图呢?

我建议如下:

var userid = UserManager.FindByEmail(model.Email)?.Id;
if (string.IsNullOrEmpty(userid) || !UserManager.IsEmailConfirmed(userid)))
{
    ModelState.AddModelError("", "Invalid login attempt.");
    return View(model);
}

答案 5 :(得分:0)

需要电子邮件确认

最佳做法是确认新用户注册的电子邮件,以确认他们没有假冒其他人(也就是说,他们尚未在其他人的电子邮件中注册)。假设您有一个讨论论坛,并且想阻止“ yli@example.com”注册为“ nolivetto@contoso.com”。如果没有电子邮件确认,“ nolivetto@contoso.com”可能会从您的应用程序收到不需要的电子邮件。假设用户不小心注册了“ ylo@example.com”,而没有注意到“ yli”的拼写错误,那么他们将无法使用密码恢复,因为该应用程序没有正确的电子邮件。电子邮件确认仅提供有限的保护,使其免受僵尸程序的侵害,而无法阻止那些拥有可用于注册的有效电子邮件别名的确定的垃圾邮件发送者。

您通常希望阻止新用户在收到确认电子邮件之前将任何数据发布到您的网站上。

更新ConfigureServices以要求确认电子邮件:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
      options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>(config =>
        {
            config.SignIn.RequireConfirmedEmail = true;
        })
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    // Add application services.
    services.AddTransient<IEmailSender, EmailSender>();

    services.AddMvc();

    services.Configure<AuthMessageSenderOptions>(Configuration);
}