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">&nbsp;&nbsp;&nbsp;</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">&nbsp;&nbsp;&nbsp;</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>  

     

posted @ 2016-01-28 13:32  凌锋  阅读(700)  评论(0编辑  收藏  举报