ASP.NET 登录功能主要有两种主流实现方式:

- ASP.NET Identity:(推荐) 现代化、功能强大的会员系统,支持社交登录、双因素认证、OWIN 等,适用于 ASP.NET MVC 和 ASP.NET Core。
- 经典的 ASP.NET Membership / Forms Authentication:旧技术,主要在 Web Forms 项目中使用,不推荐新项目采用。
本指南将重点介绍 ASP.NET Identity,因为它更灵活、更安全、更符合现代开发需求。
ASP.NET Identity (MVC / Core) - 推荐方案
这是目前最标准、最强大的实现方式。
步骤 1:创建项目并配置 Identity
如果您的新项目还没有配置 Identity,请按以下步骤操作。
对于 ASP.NET Core MVC:

- 创建新项目时,选择 "ASP.NET Core Web App (Model-View-Controller)"。
- 在配置窗口中,务必勾选 "Authentication Type" 为 "Individual User Accounts",Visual Studio 会自动为您配置好所有 Identity 相关的 NuGet 包、数据库上下文、登录/注册 UI 等。
对于 ASP.NET MVC (非 Core):
- 通过 NuGet 包管理器控制台,安装以下包:
Install-Package Microsoft.AspNet.Identity.EntityFramework Install-Package Microsoft.AspNet.Identity.Owin Install-Package Microsoft.Owin.Host.SystemWeb
- 项目会自动生成
AccountController、IdentityConfig.cs、Startup.Auth.cs以及相关的视图(Login.cshtml,Register.cshtml等)。
步骤 2:自定义登录页面 UI
即使自动生成了页面,我们也经常需要自定义其外观和布局。
-
找到登录页面视图:
- MVC / Core: 位于
Views/Account/Login.cshtml(Core) 或Views/Account/Login.cshtml(MVC)。 - Core (Razor Pages): 位于
Pages/Account/Login.cshtml.cs(后端代码) 和Pages/Account/Login.cshtml(前端视图)。
- MVC / Core: 位于
-
修改
Login.cshtml视图:
(图片来源网络,侵删)
这是一个典型的、功能完整的登录页面模板,它包含了用户名/邮箱、密码、记住我、外部登录(如 Google, Facebook)等选项。
Views/Account/Login.cshtml (ASP.NET MVC / Core MVC 模板)
@* 引入必要的样式和脚本 *@
@{
ViewData["Title"] = "Log in";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-6">
<section>
<h4>Use a local account to log in.</h4>
<hr />
@* 显示 ModelState 中的错误信息,用户名或密码错误” *@
@if (!ViewData.ModelState.IsValid)
{
<div class="alert alert-danger" asp-validation-summary="All">
@* 错误信息会自动显示在这里 *@
</div>
}
<form id="account" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-floating">
<input asp-for="Input.Email" class="form-control" autocomplete="email" aria-required="true" />
<label asp-for="Input.Email" class="form-label"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-floating">
<input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" />
<label asp-for="Input.Password" class="form-label"></label>
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="checkbox mb-3">
<label asp-for="Input.RememberMe" class="form-label">
<input class="form-check-input" asp-for="Input.RememberMe" />
@Html.DisplayNameFor(m => m.Input.RememberMe)
</label>
</div>
<div class="d-grid">
<button id="login-submit" type="submit" class="btn btn-lg btn-primary">Log in</button>
</div>
<div>
<p>
<a asp-action="ForgotPassword">Forgot your password?</a>
</p>
<p>
<a asp-action="Register" asp-route-returnUrl="@ViewData["ReturnUrl"]">Register as a new user</a>
</p>
</div>
</form>
</section>
</div>
<div class="col-md-6">
<section>
<h4>Use another service to log in.</h4>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
about setting up this ASP.NET application to support logging in via external services</a>.
</p>
</div>
}
else
{
<form id="external-account" asp-action="ExternalLogin" asp-route-returnUrl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
@* 客户端验证脚本 *@
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Pages/Account/Login.cshtml (ASP.NET Core Razor Pages 模板)
@page
@model LoginModel
@{
ViewData["Title"] = "Log in";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<section>
<h4>Use a local account to log in.</h4>
<hr />
@* 显示错误信息 *@
@if (Model.ErrorMessage != null)
{
<div class="alert alert-danger">
@Model.ErrorMessage
</div>
}
<form id="account" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-floating">
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" />
<label asp-for="Input.Email" class="form-label"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-floating">
<input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" />
<label asp-for="Input.Password" class="form-label"></label>
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="checkbox mb-3">
<label asp-for="Input.RememberMe" class="form-label">
<input class="form-check-input" asp-for="Input.RememberMe" />
@Html.DisplayNameFor(m => m.Input.RememberMe)
</label>
</div>
<div class="d-grid">
<button id="login-submit" type="submit" class="btn btn-lg btn-primary">Log in</button>
</div>
<div>
<p>
<a id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a>
</p>
<p>
<a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
</p>
</div>
</form>
</section>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to log in.</h4>
<hr />
@* 外部登录按钮 *@
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
about setting up this ASP.NET application to support logging in via external services</a>.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
@* 客户端验证 *@
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
步骤 3:理解后端逻辑 (以 Razor Pages 为例)
后端代码 (Login.cshtml.cs) 处理表单提交、验证和身份认证。
Pages/Account/Login.cshtml.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
namespace WebAppWithAuth.Pages.Account
{
public class LoginModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<LoginModel> _logger;
public LoginModel(SignInManager<IdentityUser> signInManager, ILogger<LoginModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public string ReturnUrl { get; set; }
[TempData]
public string ErrorMessage { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
public async Task OnGetAsync()
{
ReturnUrl = Url.Content("~/"); // 默认返回首页
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
// 如果用户已登录,则直接重定向
if (User.Identity.IsAuthenticated)
{
Response.Redirect(ReturnUrl);
}
}
public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
// ModelState 无效,则返回页面显示验证错误
if (ModelState.IsValid)
{
// 查找用户,使用 Email 或 UserName
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl); // 登录成功,重定向到 returnUrl
}
if (result.RequiresTwoFactor)
{
// 需要双因素认证
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
// 登录失败
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// 如果我们到达这一步,说明在提交表单时发生了错误。
return Page();
}
}
}
纯 HTML/CSS/JS 登录页面 (前端模板)
如果您不希望使用 ASP.NET Identity 的内置 UI,或者想创建一个完全自定义的前端页面,可以采用这种方式,后端仍然需要使用 ASP.NET Identity 来处理认证逻辑。
创建一个简单的 HTML 登录页面
在 wwwroot 文件夹下创建一个 login.html 文件。
wwwroot/login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">Login</title>
<!-- 引入 Bootstrap CSS 以快速美化界面 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-color: #f8f9fa;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-container {
width: 100%;
max-width: 400px;
padding: 15px;
}
</style>
</head>
<body>
<div class="login-container">
<div class="card shadow-sm">
<div class="card-body">
<h2 class="card-title text-center mb-4">Welcome Back</h2>
<!-- 错误信息显示区域 -->
<div id="error-message" class="alert alert-danger d-none" role="alert">
Invalid username or password.
</div>
<form id="login-form">
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" placeholder="name@example.com" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" required>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="remember-me">
<label class="form-check-label" for="remember-me">Remember me</label>
</div>
<button type="submit" class="btn btn-primary w-100">Sign In</button>
</form>
<div class="mt-3 text-center">
<a href="#">Forgot password?</a>
</div>
</div>
</div>
</div>
<!-- 引入 Bootstrap JS 和 Popper.js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.getElementById('login-form').addEventListener('submit', function(event) {
event.preventDefault(); // 阻止表单默认提交行为
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const rememberMe = document.getElementById('remember-me').checked;
const errorMessage = document.getElementById('error-message');
// 使用 Fetch API 调用后端的登录 API
fetch('/api/account/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
Email: email,
Password: password,
RememberMe: rememberMe
})
})
.then(response => {
if (!response.ok) {
// 如果后端返回 4xx 或 5xx 错误,抛出错误
throw new Error('Login failed');
}
return response.json();
})
.then(data => {
// 登录成功,重定向到首页或指定页面
window.location.href = data.returnUrl || '/';
})
.catch(error => {
// 登录失败,显示错误信息
errorMessage.classList.remove('d-none');
console.error('Login error:', error);
});
});
</script>
</body>
</html>
创建后端 API 控制器
我们需要一个 API 端点来处理前端的登录请求,这个 API 将使用 ASP.NET Identity 的逻辑。
Controllers/AccountApiController.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using WebAppWithAuth.Models; // 确保你有 IdentityUser 或自定义 User 模型
[ApiController]
[Route("api/[controller]")]
public class AccountApiController : ControllerBase
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
public AccountApiController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(model.Email);
if (user != null)
{
var result = await _signInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
// 登录成功,返回成功信息和重定向URL
return Ok(new { returnUrl = model.ReturnUrl ?? "/" });
}
if (result.IsLockedOut)
{
return BadRequest(new { error = "User account locked out." });
}
}
// 登录失败
return BadRequest(new { error = "Invalid login attempt." });
}
// 如果模型验证失败
return BadRequest(ModelState);
}
}
public class LoginRequest
{
public string Email { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
public string ReturnUrl { get; set; }
}
最佳实践与安全建议
- 始终使用 HTTPS:在生产环境中,强制所有页面通过 HTTPS 访问,以加密传输的登录凭据。
- 防止暴力破解:ASP.NET Identity 默认启用了账户锁定功能,在
Startup.cs(MVC) 或Program.cs(Core) 中配置:// ASP.NET Core services.AddIdentity<IdentityUser, IdentityRole>(options => { options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); options.Lockout.MaxFailedAccessAttempts = 5; options.Lockout.AllowedForNewUsers = true; }); - 使用 Anti-Forgery Token:在表单中添加
@Html.AntiForgeryToken()或使用[ValidateAntiForgeryToken]特性,以防止 CSRF 攻击,MVC/Razor Pages 默认已集成。 - 密码策略:在
ConfigureServices中设置强密码策略。services.AddIdentity<IdentityUser, IdentityRole>(options => { options.Password.RequireDigit = true; options.Password.RequireLowercase = true; options.Password.RequireNonalphanumeric = true; options.Password.RequireUppercase = true; options.Password.RequiredLength = 8; }); - 不要存储明文密码:ASP.NET Identity 会自动对密码进行哈希处理,你永远不应该在数据库或代码中存储或记录明文密码。
- 考虑双因素认证 (2FA):为敏感账户启用 2FA,大大提高安全性。
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| ASP.NET Identity (内置UI) | 快速、安全、功能全、与框架深度集成。 | UI 定制性相对较低,可能需要覆盖默认视图。 | 大多数标准 Web 应用,快速开发。 |
| 纯 HTML/CSS/JS + Identity API | UI 完全自由,可以构建任何现代前端体验。 | 需要额外编写前端逻辑和 API,工作量大。 | 需要复杂单页应用体验、前后端分离项目。 |
对于绝大多数项目,直接使用和自定义 ASP.NET Identity 的内置登录模板是最佳选择,它提供了开箱即用的安全性和功能,同时保留了足够的灵活性来满足 UI 定制需求。
