ASP.NET Core 用户登录与注销
在本章中,我们将讨论登录和注销功能。与登录相比,注销很容易实现。让我们继续使用 布局视图,因为我们希望构建一个具有一些链接的 UI。这将允许登录用户注销并显示用户名。
<!DOCTYPE html>
<html>
<head>
<meta name = "viewport" content = "width = device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
@DateTime.Now
</div>
<div>
@RenderBody()
</div>
</body>
</html>
- 对于匿名用户,我们将显示登录链接。
- 创建此 UI 所需的所有信息都可以从 Razor 视图上下文中获得。
- 首先,让我们在布局视图中添加命名空间 System.Security.Claims。
@using Microsoft.AspNetCore.Identity
@inject SignInManager<MyCoreWeb.Models.User> SignInManager
@inject UserManager<MyCoreWeb.Models.User> UserManager
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width = device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
@if (SignInManager.IsSignedIn(User))
{
<div>@User.Identity.Name</div>
<form method="post" asp-controller="Account" asp-action="Logout">
<input type="submit" value="注销" />
</form>
}
else
{
<a asp-controller="Account" asp-action="Login">登录</a>
<a asp-controller="Account" asp-action="Register">注册</a>
}
</div>
<div>
@DateTime.Now
</div>
<div>
@RenderBody()
</div>
</body>
</html>
- 每个 Razor 视图中都有一个
User
属性,我们希望构建一个显示登录用户名的 UI。这里还提供了扩展方法IsSignedIn
。 - 我们可以调用这个方法,如果它返回 true,我们可以在这里放置一些标记来显示用户名,显示注销按钮。
- 现在,如果用户已登录,我们可以使用助手方法
GetUserName
显示用户的用户名。 - 我们必须在表单中创建一个注销按钮,该按钮将 POST 请求到 web 服务器。必须这样做,因为如果您用简单的 GET 请求来允许用户退出,可能回造成一些问题。
- 我们将强制这个操作为 POST 方式,当用户提交此表单时,我们需要做的就是点击 注销 操作,我们将通过
AccountController
实现该操作,并注销用户。 - 如果用户未登录,并且我们有一个匿名用户,那么我们需要显示一个链接,该链接将转到 AccountController,特别是
Login
操作,它可以显示文本登录。 - 我们还需要添加一个链接,让新用户注册并直接转到注册页面。
现在,让我们转到 AccountController 并首先执行注销操作,如以下所示。
[HttpPost]
public async Task<IActionResult> Logout() {
await _signManager.SignOutAsync();
return RedirectToAction("Index", "Home");
}
- 此操作仅响应 HttpPost。这是一个异步操作。我们将不得不在 Identity 框架上调用另一个异步方法。
- 我们可以返回 IActionResult 任务,该操作名为
Logout
。 - 注销所需要做的就是等待 SignInManager 的
SignOutAsync
方法。 - 用户上下文现在已更改;我们现在有一个匿名用户。视图将重定向到主页,我们将返回员工列表。
现在让我们继续构建我们的登录功能。这里,我们需要一对操作,一个响应 HttpGet 请求并显示可用于登录的表单,另一个响应 HTTPPost 请求。
首先,我们需要一个新的 ViewModel 来获取登录数据,因为登录与注册非常不同。因此,让我们添加一个新类并将其称为 LoginViewModel。
public class LoginViewModel
{
public string Username { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember Me")]
public bool RememberMe { get; set; }
public string ReturnUrl { get; set; }
}
- 当用户登录时,他们必须提供一些信息,如用户名、密码。
- 第三条信息必须是登录 UI。这些都带有一个小复选框,上面写着 "Remember Me"。这是我们想要会话 cookie 还是想要更持久的 cookie 之间的选择。
- 为了实现这一功能,我们添加了布尔属性
RememberMe
,并使用了显示注释。现在,当我们创建标签时,文本 Remember Me 将显示为带有空格。 - 作为此 ViewModel 的一部分,我们实际需要的最后一个信息是具有将存储
ReturnUrl
的属性。
现在让我们添加 Login
操作,该操作将响应 Get 请求,如以下所示。
[HttpGet]
public IActionResult Login(string returnUrl = "") {
var model = new LoginViewModel { ReturnUrl = returnUrl };
return View(model);
}
- 我们将
returnUrl
作为查询字符串中的参数。 - 可能有时没有
returnUrl
。让我们使用空字符串作为默认值。
让我们在 Views → Account 文件夹下创建一个新视图,命名为 Login.chtml。然后添加如下代码:
@model LoginViewModel
@{
ViewBag.Title = "登录";
}
<h2>登录</h2>
<form method="post" asp-controller="Account" asp-action="Login"
asp-route-returnurl="@Model.ReturnUrl">
<div asp-validation-summary="ModelOnly"></div>
<div>
<label>用户名</label>
<input asp-for="Username" />
<span asp-validation-for="Username"></span>
</div>
<div>
<label>密码</label>
<input asp-for="Password" />
<span asp-validation-for="Password"></span>
</div>
<div>
<label>记住我</label>
<input asp-for="RememberMe" />
<span asp-validation-for="RememberMe"></span>
</div>
<input type="submit" value="登录" />
</form>
- 在这个登录视图中,我们将页面的标题设置为 登录,然后我们有一个表单将发布到
AccountLogin
操作。 - 我们需要使用一个标签帮助,即
asp-route-returnurl
,以确保表单返回的 URL 中有 ReturnUrl。 - 我们需要将该 ReturnUrl 发送回服务器,以便如果用户成功登录,我们可以将其发送到他们试图到达的位置。
- 在 asp route-、id 或 returnurl 之后添加的任何内容,都将进入请求的某个位置,要么进入 URL 路径,要么作为查询字符串参数。
- 我们有验证摘要和用户名、密码和 RememberMe 的输入,然后有一个 登录 按钮。
AccountController,执行 Post 操作。这是一个异步方法,因为我们需要调用 Identity 框架并返回一个任务或 IActionResult。
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
var result = await _signManager.PasswordSignInAsync(model.Username,
model.Password, model.RememberMe, false);
if (result.Succeeded)
{
if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
}
ModelState.AddModelError("", "Invalid login attempt");
return View(model);
}
- 我们调用
Login
,现在我们希望接收到 LoginViewModel。 - 我们需要检查
ModelState
是否有效。如果有效,则通过调用 SignInManager 上的 API 登录用户。 PasswordSignInAsync
方法将返回一个结果,如果结果成功,我们知道用户已成功登录。- 我们还有一个返回 URL。如果它是有效的本地 Url,我们将被重定向到返回 Url。
- 如果用户刚刚登录,没有设置实体的 ReturnUrl,我们会将用户重定向到 HomeController 的
Index
操作,即首页。 - 我们可能会遇到用户提供无效密码或无效用户名的情况。我们还需要一个
AddModelError
,提示是否存在无效登录尝试。这有助于用户知道是否出现了问题。
现在我们保存并运行代码,如下:
然后点击 登录 按钮,跳转到登录界面:
输入用户名和密码,点击 登录,跳转回首页:
再点击 注销,退出用户登录:
最后可以点击 注册 按钮,进行用户注册。