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

asp.net登陆页面模板
(图片来源网络,侵删)
  1. ASP.NET Identity(推荐) 现代化、功能强大的会员系统,支持社交登录、双因素认证、OWIN 等,适用于 ASP.NET MVC 和 ASP.NET Core。
  2. 经典的 ASP.NET Membership / Forms Authentication:旧技术,主要在 Web Forms 项目中使用,不推荐新项目采用。

本指南将重点介绍 ASP.NET Identity,因为它更灵活、更安全、更符合现代开发需求。


ASP.NET Identity (MVC / Core) - 推荐方案

这是目前最标准、最强大的实现方式。

步骤 1:创建项目并配置 Identity

如果您的新项目还没有配置 Identity,请按以下步骤操作。

对于 ASP.NET Core MVC:

asp.net登陆页面模板
(图片来源网络,侵删)
  1. 创建新项目时,选择 "ASP.NET Core Web App (Model-View-Controller)"。
  2. 在配置窗口中,务必勾选 "Authentication Type" 为 "Individual User Accounts",Visual Studio 会自动为您配置好所有 Identity 相关的 NuGet 包、数据库上下文、登录/注册 UI 等。

对于 ASP.NET MVC (非 Core):

  1. 通过 NuGet 包管理器控制台,安装以下包:
    Install-Package Microsoft.AspNet.Identity.EntityFramework
    Install-Package Microsoft.AspNet.Identity.Owin
    Install-Package Microsoft.Owin.Host.SystemWeb
  2. 项目会自动生成 AccountControllerIdentityConfig.csStartup.Auth.cs 以及相关的视图(Login.cshtml, Register.cshtml 等)。

步骤 2:自定义登录页面 UI

即使自动生成了页面,我们也经常需要自定义其外观和布局。

  1. 找到登录页面视图

    • MVC / Core: 位于 Views/Account/Login.cshtml (Core) 或 Views/Account/Login.cshtml (MVC)。
    • Core (Razor Pages): 位于 Pages/Account/Login.cshtml.cs (后端代码) 和 Pages/Account/Login.cshtml (前端视图)。
  2. 修改 Login.cshtml 视图

    asp.net登陆页面模板
    (图片来源网络,侵删)

这是一个典型的、功能完整的登录页面模板,它包含了用户名/邮箱、密码、记住我、外部登录(如 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; }
}

最佳实践与安全建议

  1. 始终使用 HTTPS:在生产环境中,强制所有页面通过 HTTPS 访问,以加密传输的登录凭据。
  2. 防止暴力破解: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;
    });
  3. 使用 Anti-Forgery Token:在表单中添加 @Html.AntiForgeryToken() 或使用 [ValidateAntiForgeryToken] 特性,以防止 CSRF 攻击,MVC/Razor Pages 默认已集成。
  4. 密码策略:在 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;
    });
  5. 不要存储明文密码:ASP.NET Identity 会自动对密码进行哈希处理,你永远不应该在数据库或代码中存储或记录明文密码。
  6. 考虑双因素认证 (2FA):为敏感账户启用 2FA,大大提高安全性。
方案 优点 缺点 适用场景
ASP.NET Identity (内置UI) 快速、安全、功能全、与框架深度集成。 UI 定制性相对较低,可能需要覆盖默认视图。 大多数标准 Web 应用,快速开发。
纯 HTML/CSS/JS + Identity API UI 完全自由,可以构建任何现代前端体验。 需要额外编写前端逻辑和 API,工作量大。 需要复杂单页应用体验、前后端分离项目。

对于绝大多数项目,直接使用和自定义 ASP.NET Identity 的内置登录模板是最佳选择,它提供了开箱即用的安全性和功能,同时保留了足够的灵活性来满足 UI 定制需求。