Nancy跨平台开发总结(四)三层构架之Form认证登录
这一节介绍使用Nancyfx的Form认证方式创建一个用户登录功能。
继续之前的Hello world
- 通过nuget添加引用
- Nancy.Authentication.Forms
- Autofac
- Nancy.Bootstrappers.Autofac
- 在项目中添加如下文件,修改_Layout.cshtml添加bootstrap和jquery的引用
-
在Models中添加AccountViewModel.cs文件,包含三个类UserInfo, UserIdentity和UserMapper.
UserMapper用于设置Form认证, GetUserFromIdentifier根据GUID获取用户信息,如果是null代表用户没有登录或登录已失效。
using System; using System.Collections.Generic; using Nancy; using Nancy.Security; using Nancy.Authentication.Forms; namespace WebSite.Models { [Serializable] public class UserInfo { public string UserName { get; set; } public string Password { get; set; } } public class UserIdentity : IUserIdentity { public UserIdentity(string userName) : this(userName, new List<string>()) { } public UserIdentity(string userName, IEnumerable<string> claims) { this.UserName = userName; this.Claims = claims; } public IEnumerable<string> Claims { get; private set; } public string UserName { get; private set; } } public class UserMapper : IUserMapper { public IUserIdentity GetUserFromIdentifier(Guid identifier,NancyContext context) { UserInfo userRecord = context.Request.Session[identifier.ToString()] as UserInfo; return userRecord == null ? null : new UserIdentity(userRecord.UserName); } } }
-
在Views下添加文件夹Account,添加文件 Login.cshtml
<style> .login-panel { margin-top: 25%; } </style> <form method="post"> @Html.AntiForgeryToken() <div class="row"> <div class="col-md-4 col-md-offset-4"> <div class="login-panel panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">登录</h3> </div> <div class="panel-body"> <div class="form-group"> <div class="input-group"> <span class="input-group-addon">用户名</span> <input type="text" name="UserName" class="form-control" placeholder="用户名" aria-describedby="basic-addon1"> </div> </div> <div class="form-group"> <div class="input-group"> <span class="input-group-addon">密 码</span> <input type="Password" name="Password" class="form-control" placeholder="密码" aria-describedby="basic-addon1"> </div> </div> <div class="form-group"> 记住帐号 <input name="RememberMe" type="checkbox" /> </div> <div class="form-group"> <button type="submit" class="btn btn-danger btn-block">登 录</button> </div> <div class="form-group"> @ViewBag.ErrorMsg </div> </div> </div> </div> </div> </form>
- 在Cotroller中添加AccountController类,并添加一个Get方法显示登录界面
using Nancy;
using Nancy.Extensions;
using Nancy.Security;
using Nancy.ModelBinding;
using Nancy.Authentication.Forms;using WebSite.Models;
namespace WebSite.Controller { public class AccountController : NancyModule { public AccountController() : base("Account") { Get["/Login"] = paramters => { //生成CSRF token. this.CreateNewCsrfToken(); ViewBag.Title = "登录"; ViewBag.ErrorMsg = this.Request.Query.error; return View["Login"]; }; } } }
其中this.CreateNewCsrfToken();会在页面初始化时创建一个Token,然后在Post方法中验证Token
- 修改IndexController,在构造函数中添加以下代码
this.RequiresAuthentication();
这样可限定用户在访问这个Controller时必须进行认证
-
在App_Start目录下添加类Bootstrapper继承自AutofacNancyBootstrapper,重写Bootstrapper事件配置程序
ConfigureApplicationContainer中添加IOC容器注册
ApplicationStartup启动CRST认证
RequestStartup中设置form认证并启用session,程序启动后如果没有登录将自动跳到Login页面登录using Nancy; using Nancy.Bootstrapper; using Nancy.Security; using Nancy.Authentication.Forms; using Nancy.Session; using Nancy.Bootstrappers.Autofac; using Autofac; using WebSite.Models; namespace WebSite { public class Bootstrapper : AutofacNancyBootstrapper { protected override void ConfigureApplicationContainer(ILifetimeScope container) { var builder = new ContainerBuilder(); // Here we register our user mapper as a per-request singleton. // As this is now per-request we could inject a request scoped // database "context" or other request scoped services. builder.RegisterType<UserMapper>().As<IUserMapper>().InstancePerLifetimeScope(); builder.Update(container.ComponentRegistry); } protected override void ApplicationStartup(ILifetimeScope container, IPipelines pipelines) { base.ApplicationStartup(container, pipelines); StaticConfiguration.EnableRequestTracing = true; //显示详细错误信息 StaticConfiguration.DisableErrorTraces = false; //启用AntiToken Csrf.Enable(pipelines); } protected override void RequestStartup(ILifetimeScope container, IPipelines pipelines, NancyContext context) { base.RequestStartup(container, pipelines, context); // At request startup we modify the request pipelines to // include forms authentication - passing in our now request // scoped user name mapper. // // The pipelines passed in here are specific to this request, // so we can add/remove/update items in them as we please. var formsAuthConfiguration = new FormsAuthenticationConfiguration() { RedirectUrl = "~/account/login", UserMapper = container.Resolve<IUserMapper>(), }; FormsAuthentication.Enable(pipelines, formsAuthConfiguration); //启用Session CookieBasedSessions.Enable(pipelines); } } }
- 运行,页面自动跳转到登录界面
- 登录,在构造函数中添加一个用于登录验证的Post方法,运行,输入用户名,密码,验证通过后自动跳转到Index页面
Post["/Login"] = _ => { //CSRF token 检验 this.ValidateCsrfToken(); string userName = (string)this.Request.Form.UserName; string password = (string)this.Request.Form.Password; return Login(userName, password); }; private dynamic Login(string userName, string password) { if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password)) { return this.Context.GetRedirect(string.Format("~/account/login?error={0}", "用户名或密码不能为空!")); } var userGuid = ValidateUser(this.Context, userName, password); if (userGuid == null) { return this.Context.GetRedirect(string.Format("~/account/login?error={0}", "用户不存在或密码错误!")); } DateTime? expiry = null; if (this.Request.Form.RememberMe.HasValue) { expiry = DateTime.Now.AddDays(7); } return this.LoginAndRedirect(userGuid.Value, expiry); } private Guid? ValidateUser(NancyContext context, string username, string password) { Guid? guid = null; if (username == "admin" && password == DateTime.Today.ToString("yyyyMMdd")) { guid = Guid.NewGuid(); context.Request.Session[guid.ToString()] = new UserInfo { UserName = username, Password = password }; return guid; } return null; }
- 退出。修改AccountController,添加Get方法,清作Session,退出登录
Get["/Logout"] = x => { this.CreateNewCsrfToken(); Session.DeleteAll(); return this.LogoutAndRedirect("~/"); }
修改Index页面,添加一个超链接按钮,点击退出登录
<a href="@Url.Content("~/account/logout")">退出</a>