Criar um aplicativo Web seguro do ASP.NET MVC 5 com logon, confirmação por email e redefinição de senha (C#)
por Rick Anderson
Este tutorial mostra como criar um aplicativo Web ASP.NET MVC 5 com confirmação de email e redefinição de senha usando o sistema de associação de identidade do ASP.NET.
Para obter uma versão atualizada deste tutorial que usa o .NET Core, confira Confirmação da conta e recuperação de senha no ASP.NET Core.
Criar um aplicativo MVC ASP.NET
Comece instalando e executando o Visual Studio Express 2013 para Web ou Visual Studio 2013. Instale Visual Studio 2013 Atualização 3 ou superior.
Observação
Aviso: você deve instalar Visual Studio 2013 Atualização 3 ou superior para concluir este tutorial.
Crie um novo projeto Web ASP.NET e selecione o modelo MVC. Web Forms também dá suporte ao ASP.NET Identity, para que você possa seguir etapas semelhantes em um aplicativo web forms.
Deixe a autenticação padrão como Contas de Usuário Individuais. Se você quiser hospedar o aplicativo no Azure, deixe a caixa marcar marcada. Posteriormente no tutorial, implantaremos no Azure. Você pode abrir uma conta do Azure gratuitamente.
Defina o projeto para usar SSL.
Execute o aplicativo, clique no link Registrar e registre um usuário. Neste ponto, a única validação no email é com o atributo [EmailAddress] .
Em Servidor Explorer, navegue até Conexões de Dados\DefaultConnection\Tables\AspNetUsers, clique com o botão direito do mouse e selecione Abrir definição de tabela.
A imagem a seguir mostra o
AspNetUsers
esquema:Clique com o botão direito do mouse na tabela AspNetUsers e selecione Mostrar Dados da Tabela.
Neste ponto, o email não foi confirmado.Clique na linha e selecione excluir. Você adicionará esse email novamente na próxima etapa e enviará um email de confirmação.
confirmação Email
É uma prática recomendada confirmar o email de um novo registro de usuário para verificar se ele não está se passando por outra pessoa (ou seja, eles não se registraram com o email de outra pessoa). Suponha que você teve um fórum de discussão, você gostaria de impedir "bob@example.com"
de se registrar como "joe@contoso.com"
. Sem confirmação por email, "joe@contoso.com"
o pode receber emails indesejados do seu aplicativo. Suponha que Bob se registrou acidentalmente como "bib@example.com"
e não tivesse notado, ele não seria capaz de usar a recuperação de senha porque o aplicativo não tem seu email correto. Email confirmação fornece apenas proteção limitada contra bots e não fornece proteção contra spammers determinados, eles têm muitos aliases de email de trabalho que podem usar para se registrar.
Geralmente, você deseja impedir que novos usuários postem dados em seu site antes que eles sejam confirmados por email, uma mensagem de texto SMS ou outro mecanismo. Nas seções abaixo, habilitaremos a confirmação por email e modificaremos o código para impedir que usuários recém-registrados entrem até que seus emails sejam confirmados.
Conectar o SendGrid
As instruções nesta seção não são atuais. Consulte Configurar o provedor de email do SendGrid para obter instruções atualizadas.
Embora este tutorial mostre apenas como adicionar notificação por email por meio do SendGrid, você pode enviar emails usando SMTP e outros mecanismos (consulte recursos adicionais).
No Console do Gerenciador de Pacotes, digite o seguinte comando:
Install-Package SendGrid
Acesse a página de inscrição do Azure SendGrid e registre-se para obter uma conta gratuita do SendGrid. Configure o SendGrid adicionando código semelhante ao seguinte em App_Start/IdentityConfig.cs:
public class EmailService : IIdentityMessageService { public async Task SendAsync(IdentityMessage message) { await configSendGridasync(message); } // Use NuGet to install SendGrid (Basic C# client lib) private async Task configSendGridasync(IdentityMessage message) { var myMessage = new SendGridMessage(); myMessage.AddTo(message.Destination); myMessage.From = new System.Net.Mail.MailAddress( "Joe@contoso.com", "Joe S."); myMessage.Subject = message.Subject; myMessage.Text = message.Body; myMessage.Html = message.Body; var credentials = new NetworkCredential( ConfigurationManager.AppSettings["mailAccount"], ConfigurationManager.AppSettings["mailPassword"] ); // Create a Web transport for sending email. var transportWeb = new Web(credentials); // Send the email. if (transportWeb != null) { await transportWeb.DeliverAsync(myMessage); } else { Trace.TraceError("Failed to create Web transport."); await Task.FromResult(0); } } }
Você precisará adicionar os seguintes inclusões:
using SendGrid;
using System.Net;
using System.Configuration;
using System.Diagnostics;
Para manter este exemplo simples, armazenaremos as configurações do aplicativo no arquivo web.config :
</connectionStrings>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<!-- Markup removed for clarity. -->
<add key="mailAccount" value="xyz" />
<add key="mailPassword" value="password" />
</appSettings>
<system.web>
Aviso
Segurança – Nunca armazene dados confidenciais em seu código-fonte. A conta e as credenciais são armazenadas no appSetting. No Azure, você pode armazenar com segurança esses valores na guia Configurar no portal do Azure. Confira Práticas recomendadas para implantar senhas e outros dados confidenciais no ASP.NET e no Azure.
Habilitar a confirmação de email no controlador de conta
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id,
"Confirm your account", "Please confirm your account by clicking <a href=\""
+ callbackUrl + "\">here</a>");
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Verifique se o arquivo Views\Account\ConfirmEmail.cshtml tem a sintaxe razor correta. ( O caractere @ na primeira linha pode estar ausente. )
@{
ViewBag.Title = "Confirm Email";
}
<h2>@ViewBag.Title.</h2>
<div>
<p>
Thank you for confirming your email. Please @Html.ActionLink("Click here to Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })
</p>
</div>
Execute o aplicativo e clique no link Registrar. Depois de enviar o formulário de registro, você estará conectado.
Verifique sua conta de email e clique no link para confirmar seu email.
Exigir confirmação de email antes de fazer logon
Atualmente, depois que um usuário conclui o formulário de registro, ele é conectado. Geralmente, você deseja confirmar seus emails antes de fazer logon neles. Na seção abaixo, modificaremos o código para exigir que novos usuários tenham um email confirmado antes de serem conectados (autenticados). Atualize o HttpPost Register
método com as seguintes alterações realçadas:
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// Comment the following line to prevent log in until the user is confirmed.
// await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Confirm your account",
"Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");
// Uncomment to debug locally
// TempData["ViewBagLink"] = callbackUrl;
ViewBag.Message = "Check your email and confirm your account, you must be confirmed "
+ "before you can log in.";
return View("Info");
//return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Ao comentar o SignInAsync
método , o usuário não será conectado pelo registro. A TempData["ViewBagLink"] = callbackUrl;
linha pode ser usada para depurar o aplicativo e testar o registro sem enviar email. ViewBag.Message
é usado para exibir as instruções de confirmação. O exemplo de download contém código para testar a confirmação de email sem configurar o email e também pode ser usado para depurar o aplicativo.
Crie um Views\Shared\Info.cshtml
arquivo e adicione a seguinte marcação razor:
@{
ViewBag.Title = "Info";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>
Adicione o atributo Authorize ao Contact
método de ação do controlador Home. Você pode clicar no link Contato para verificar se os usuários anônimos não têm acesso e se os usuários autenticados têm acesso.
[Authorize]
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
Você também deve atualizar o método de HttpPost Login
ação:
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Require the user to have a confirmed email before they can log on.
var user = await UserManager.FindByNameAsync(model.Email);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
ViewBag.errorMessage = "You must have a confirmed email to log on.";
return View("Error");
}
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
Atualize a exibição Views\Shared\Error.cshtml para exibir a mensagem de erro:
@model System.Web.Mvc.HandleErrorInfo
@{
ViewBag.Title = "Error";
}
<h1 class="text-danger">Error.</h1>
@{
if (String.IsNullOrEmpty(ViewBag.errorMessage))
{
<h2 class="text-danger">An error occurred while processing your request.</h2>
}
else
{
<h2 class="text-danger">@ViewBag.errorMessage</h2>
}
}
Exclua todas as contas na tabela AspNetUsers que contenham o alias de email com o qual você deseja testar. Execute o aplicativo e verifique se você não pode fazer logon até confirmar seu endereço de email. Depois de confirmar seu endereço de email, clique no link Contato .
Recuperação/redefinição de senha
Remova os caracteres de comentário do método de HttpPost ForgotPassword
ação no controlador de conta:
//
// POST: /Account/ForgotPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
{
// Don't reveal that the user does not exist or is not confirmed
return View("ForgotPasswordConfirmation");
}
string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Reset Password", "Please reset your password by clicking <a href=\"" + callbackUrl + "\">here</a>");
return RedirectToAction("ForgotPasswordConfirmation", "Account");
}
// If we got this far, something failed, redisplay form
return View(model);
}
Remova os caracteres de comentário do ForgotPassword
ActionLink no arquivo de exibição razor Views\Account\Login.cshtml :
@using MvcPWy.Models
@model LoginViewModel
@{
ViewBag.Title = "Log in";
}
<h2>@ViewBag.Title.</h2>
<div class="row">
<div class="col-md-8">
<section id="loginForm">
@using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<h4>Use a local account to log in.</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
@Html.CheckBoxFor(m => m.RememberMe)
@Html.LabelFor(m => m.RememberMe)
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Log in" class="btn btn-default" />
</div>
</div>
<p>
@Html.ActionLink("Register as a new user", "Register")
</p>
@* Enable this once you have account confirmation enabled for password reset functionality *@
<p>
@Html.ActionLink("Forgot your password?", "ForgotPassword")
</p>
}
</section>
</div>
<div class="col-md-4">
<section id="socialLoginForm">
@Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl })
</section>
</div>
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
A página Fazer logon agora mostrará um link para redefinir a senha.
Reenviar link de confirmação de email
Depois que um usuário cria uma nova conta local, ele é enviado por email um link de confirmação que ele precisa usar antes de fazer logon. Se o usuário excluir acidentalmente o email de confirmação ou o email nunca chegar, ele precisará do link de confirmação enviado novamente. As alterações de código a seguir mostram como habilitar isso.
Adicione o seguinte método auxiliar à parte inferior do arquivo Controllers\AccountController.cs :
private async Task<string> SendEmailConfirmationTokenAsync(string userID, string subject)
{
string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = userID, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(userID, subject,
"Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");
return callbackUrl;
}
Atualize o método Register para usar o novo auxiliar:
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// Comment the following line to prevent log in until the user is confirmed.
// await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");
ViewBag.Message = "Check your email and confirm your account, you must be confirmed "
+ "before you can log in.";
return View("Info");
//return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Atualize o método Login para reenviar a senha se a conta de usuário não tiver sido confirmada:
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Require the user to have a confirmed email before they can log on.
// var user = await UserManager.FindByNameAsync(model.Email);
var user = UserManager.Find(model.Email, model.Password);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account-Resend");
// Uncomment to debug locally
// ViewBag.Link = callbackUrl;
ViewBag.errorMessage = "You must have a confirmed email to log on. "
+ "The confirmation token has been resent to your email account.";
return View("Error");
}
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
Combinar contas de logon locais e sociais
Você pode combinar contas locais e sociais clicando em seu link de email. Na sequência RickAndMSFT@gmail.com a seguir é criada primeiro como um logon local, mas você pode criar a conta como um logon social primeiro e, em seguida, adicionar um logon local.
Clique no link Gerenciar . Observe os Logons Externos: 0 associados a essa conta.
Clique no link para outro serviço de logon e aceite as solicitações do aplicativo. As duas contas foram combinadas, você poderá fazer logon com qualquer uma das contas. Talvez você queira que os usuários adicionem contas locais caso o log social deles no serviço de autenticação esteja inativo ou, provavelmente, eles tenham perdido o acesso à conta social.
Na imagem a seguir, Tom é um logon social (que você pode ver nos Logons Externos: 1 mostrado na página).
Clicar em Escolher uma senha permite adicionar um logon local associado à mesma conta.
Email confirmação mais detalhada
Meu tutorial Confirmação da Conta e Recuperação de Senha com ASP.NET Identidade entra neste tópico com mais detalhes.
Depurando o aplicativo
Se você não receber um email contendo o link:
- Verifique sua pasta de lixo eletrônico ou spam.
- Faça logon em sua conta do SendGrid e clique no link Atividade do Email.
Para testar o link de verificação sem email, baixe o exemplo concluído. O link de confirmação e os códigos de confirmação serão exibidos na página.
Recursos adicionais
- Links para recursos recomendados de identidade de ASP.NET
- Confirmação da conta e recuperação de senha com identidade ASP.NET Entra em mais detalhes sobre a recuperação de senha e a confirmação da conta.
- Aplicativo MVC 5 com Facebook, Twitter, LinkedIn e Logon do Google OAuth2 Este tutorial mostra como escrever um aplicativo ASP.NET MVC 5 com autorização do Facebook e do Google OAuth 2. Ele também mostra como adicionar dados adicionais ao banco de dados de identidade.
- Implante um aplicativo Secure ASP.NET MVC com Associação, OAuth e Banco de Dados SQL no Azure. Este tutorial adiciona a implantação do Azure, como proteger seu aplicativo com funções, como usar a API de associação para adicionar usuários e funções e recursos de segurança adicionais.
- Criando um aplicativo do Google para OAuth 2 e conectando o aplicativo ao projeto
- Criando o aplicativo no Facebook e conectando o aplicativo ao projeto
- Configurando o SSL no Projeto
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de