Creare un'app Web ASP.NET MVC 5 sicura con accesso, messaggi di posta elettronica di conferma e reimpostazione della password (C#)
Questa esercitazione illustra come creare un'app Web ASP.NET MVC 5 con la conferma tramite posta elettronica e la reimpostazione della password usando il sistema di appartenenza ASP.NET Identity.
Per una versione aggiornata di questa esercitazione che usa .NET Core, vedere Conferma dell'account e ripristino della password in ASP.NET Core.
Creare un'app MVC ASP.NET
Per iniziare, installare ed eseguire Visual Studio Express 2013 per Web o Visual Studio 2013. Installare Visual Studio 2013 Update 3 o versione successiva.
Nota
Avviso: per completare questa esercitazione è necessario installare Visual Studio 2013 Update 3 o versione successiva.
Creare un nuovo progetto Web ASP.NET e selezionare il modello MVC. Web Form supporta anche ASP.NET Identity, quindi è possibile seguire passaggi simili in un'app Web Form.
Lasciare l'autenticazione predefinita come Account utente singoli. Se si vuole ospitare l'app in Azure, lasciare selezionata la casella di controllo. Più avanti nell'esercitazione verrà distribuita in Azure. È possibile aprire gratuitamente un account Azure.
Impostare il progetto per l'uso di SSL.
Eseguire l'app, fare clic sul collegamento Registra e registrare un utente. A questo punto, l'unica convalida sul messaggio di posta elettronica è con l'attributo [EmailAddress].
In Esplora server passare a Connessioni dati\DefaultConnection\Tables\AspNetUsers, fare clic con il pulsante destro del mouse e selezionare Apri definizione tabella.
L'immagine seguente mostra lo
AspNetUsers
schema:Fare clic con il pulsante destro del mouse sulla tabella AspNetUsers e selezionare Mostra dati tabella.
A questo punto il messaggio di posta elettronica non è stato confermato.Fare clic sulla riga e selezionare Elimina. Questo messaggio di posta elettronica verrà aggiunto di nuovo nel passaggio successivo e verrà inviato un messaggio di posta elettronica di conferma.
Conferma tramite posta elettronica
È consigliabile confermare l'indirizzo di posta elettronica di una nuova registrazione utente per verificare che non stiano rappresentando un altro utente( ovvero non sono stati registrati con un messaggio di posta elettronica di un altro utente). Si supponga di avere avuto un forum di discussione, si vuole impedire "bob@example.com"
la registrazione come "joe@contoso.com"
. Senza la conferma tramite posta elettronica, "joe@contoso.com"
potrebbe ricevere un messaggio di posta elettronica indesiderato dall'app. Si supponga che Bob sia stato registrato accidentalmente come "bib@example.com"
e non l'avesse notato, non sarebbe in grado di usare il ripristino della password perché l'app non ha il messaggio di posta elettronica corretto. La conferma tramite posta elettronica offre solo una protezione limitata dai bot e non fornisce protezione da spammer determinati, ma hanno molti alias di posta elettronica funzionanti che possono usare per la registrazione.
In genere si vuole impedire ai nuovi utenti di pubblicare dati nel sito Web prima che siano stati confermati tramite posta elettronica, sms o un altro meccanismo. Nelle sezioni seguenti si abiliterà la conferma tramite posta elettronica e si modificherà il codice per impedire agli utenti appena registrati di accedere fino alla conferma del messaggio di posta elettronica.
Associare SendGrid
Le istruzioni contenute in questa sezione non sono aggiornate. Per istruzioni aggiornate, vedere Configurare il provider di posta elettronica SendGrid.
Anche se questa esercitazione illustra solo come aggiungere notifiche tramite SendGrid, è possibile inviare messaggi di posta elettronica usando SMTP e altri meccanismi (vedere risorse aggiuntive).
Nella Console di Gestione pacchetti immettere il comando seguente:
Install-Package SendGrid
Passare alla pagina di iscrizione di Azure SendGrid e registrarsi per ottenere un account SendGrid gratuito. Configurare SendGrid aggiungendo codice simile al seguente in 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); } } }
È necessario aggiungere quanto segue:
using SendGrid;
using System.Net;
using System.Configuration;
using System.Diagnostics;
Per semplificare questo esempio, le impostazioni dell'app verranno archiviate nel file 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>
Avviso
Sicurezza: non archiviare mai i dati sensibili nel codice sorgente. L'account e le credenziali vengono archiviati in appSetting. In Azure è possibile archiviare questi valori in modo sicuro nella scheda Configura nella portale di Azure. Vedere Procedure consigliate per la distribuzione di password e altri dati sensibili in ASP.NET e Azure.
Abilitare la conferma tramite posta elettronica nel controller dell'account
//
// 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);
}
Verificare che il file Views\Account\ConfirmEmail.cshtml abbia la sintassi razor corretta. ( Il carattere @ nella prima riga potrebbe non essere presente. )
@{
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>
Eseguire l'app e fare clic sul collegamento Registra. Dopo aver inviato il modulo di registrazione, si è connessi.
Controllare l'account di posta elettronica e fare clic sul collegamento per confermare il messaggio di posta elettronica.
Richiedi conferma tramite posta elettronica prima dell'accesso
Attualmente dopo che un utente ha completato il modulo di registrazione, viene eseguito l'accesso. In genere si vuole confermare il messaggio di posta elettronica prima di accedervi. Nella sezione seguente il codice verrà modificato in modo da richiedere ai nuovi utenti di avere un messaggio di posta elettronica confermato prima dell'accesso (autenticato). Aggiornare il HttpPost Register
metodo con le modifiche evidenziate seguenti:
//
// 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);
}
Commentando il SignInAsync
metodo, l'utente non verrà connesso dalla registrazione. La TempData["ViewBagLink"] = callbackUrl;
riga può essere usata per eseguire il debug dell'app e testare la registrazione senza inviare messaggi di posta elettronica. ViewBag.Message
viene usato per visualizzare le istruzioni di conferma. L'esempio di download contiene il codice per testare la conferma tramite posta elettronica senza configurare il messaggio di posta elettronica e può essere usato anche per eseguire il debug dell'applicazione.
Creare un Views\Shared\Info.cshtml
file e aggiungere il markup razor seguente:
@{
ViewBag.Title = "Info";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>
Aggiungere l'attributo Authorize al Contact
metodo action del controller Home. È possibile fare clic sul collegamento Contatto per verificare che gli utenti anonimi non abbiano accesso e che gli utenti autenticati abbiano accesso.
[Authorize]
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
È anche necessario aggiornare il HttpPost Login
metodo di azione:
//
// 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);
}
}
Aggiornare la visualizzazione Views\Shared\Error.cshtml per visualizzare il messaggio di errore:
@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>
}
}
Eliminare tutti gli account nella tabella AspNetUsers che contengono l'alias di posta elettronica con cui si vuole eseguire il test. Eseguire l'app e verificare che non sia possibile accedere fino a quando non è stato confermato l'indirizzo di posta elettronica. Dopo aver confermato l'indirizzo di posta elettronica, fare clic sul collegamento Contatto .
Ripristino/reimpostazione della password
Rimuovere i caratteri di commento dal HttpPost ForgotPassword
metodo di azione nel controller dell'account:
//
// 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);
}
Rimuovere i caratteri di ForgotPassword
commento da ActionLink nel file di visualizzazione 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")
}
Nella pagina Accedi verrà ora visualizzato un collegamento per reimpostare la password.
Invia di nuovo il collegamento di conferma tramite posta elettronica
Dopo che un utente crea un nuovo account locale, viene inviato tramite posta elettronica un collegamento di conferma da usare prima di poter accedere. Se l'utente elimina accidentalmente il messaggio di posta elettronica di conferma o il messaggio di posta elettronica non arriva mai, sarà necessario il collegamento di conferma inviato di nuovo. Le modifiche al codice seguenti illustrano come abilitare questa opzione.
Aggiungere il metodo helper seguente alla fine del file 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;
}
Aggiornare il metodo Register per usare il nuovo helper:
//
// 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);
}
Aggiornare il metodo Login per inviare nuovamente la password se l'account utente non è stato confermato:
//
// 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);
}
}
Combinare account di accesso di social network e locali
È possibile combinare account locali e social facendo clic sul collegamento di posta elettronica. Nella sequenza RickAndMSFT@gmail.com seguente viene prima creato come account di accesso locale, ma è possibile creare prima l'account come accesso di social networking e quindi aggiungere un account di accesso locale.
Fare clic sul collegamento Gestisci . Si notino gli account di accesso esterni: 0 associati a questo account.
Fare clic sul collegamento a un altro servizio di accesso e accettare le richieste dell'app. I due account sono stati combinati, sarà possibile accedere con uno dei due account. È possibile che gli utenti aggiungano account locali nel caso in cui il servizio di autenticazione del proprio account di social networking sia inattivo o che abbiano perso l'accesso al proprio account di social networking.
Nell'immagine seguente, Tom è un log di social networking (che è possibile visualizzare dagli account di accesso esterni: 1 mostrato nella pagina).
Facendo clic su Seleziona una password è possibile aggiungere un accesso locale associato allo stesso account.
Conferma tramite posta elettronica in modo più approfondito
L'esercitazione Conferma account e ripristino password con identità ASP.NET illustra questo argomento con altri dettagli.
Debug dell'app
Se non si riceve un messaggio di posta elettronica contenente il collegamento:
- Controllare la cartella posta indesiderata o posta indesiderata.
- Accedere all'account SendGrid e fare clic sul collegamento Attività di posta elettronica.
Per testare il collegamento di verifica senza posta elettronica, scaricare l'esempio completato. Il collegamento di conferma e i codici di conferma verranno visualizzati nella pagina.
Risorse aggiuntive
- Collegamenti alle risorse consigliate per l'identità di ASP.NET
- Conferma dell'account e ripristino delle password con identità ASP.NET Vengono fornite informazioni più dettagliate sul ripristino della password e sulla conferma dell'account.
- App MVC 5 con Facebook, Twitter, LinkedIn e Google OAuth2 Questa esercitazione illustra come scrivere un'app ASP.NET MVC 5 con l'autorizzazione Facebook e Google OAuth 2. Illustra anche come aggiungere dati aggiuntivi al database Identity.
- Distribuire un'app MVC ASP.NET sicura con appartenenza, OAuth e database SQL in Azure. Questa esercitazione aggiunge la distribuzione di Azure, come proteggere l'app con ruoli, come usare l'API di appartenenza per aggiungere utenti e ruoli e funzionalità di sicurezza aggiuntive.
- Creazione di un'app Google per OAuth 2 e connessione dell'app al progetto
- Creazione dell'app in Facebook e connessione dell'app al progetto
- Configurazione di SSL nel progetto