Nancyでのフォーム認証
1, Nancy.Authentication.Formsパッケージのインストール
2, IUserMapper実装クラスの作成
3, ログイン、ログアウトルートの実装
4, フォーム認証の設定
IUserMapper実装クラスの作成
IUserMapperインターフェイス、IUserIdentityインターフェイスの実装クラスを作成します。前者はログイン処理で入力されたユーザーID・パスワードのマッピング処理を、後者はユーザー識別情報を表します。NancyではユーザーIDをGUIDで表します。(そのようなNancyの仕様みたい)
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Nancy; using Nancy.Authentication.Forms; using Nancy.Security; namespace NancyFormAuthSample { public class SampleUserInfo : IUserIdentity { public IEnumerable<string> Claims { get; set; } public string UserName { get; set; } } public class UserDatabase : IUserMapper { //ユーザー情報ストア。サンプルの為、staticなメンバを使用している。 private static List<Tuple<string, string, Guid>> users = new List<Tuple<string, string, Guid>>(); public UserDatabase() { users.Add(new Tuple<string, string, Guid>("admin", "password", Guid.NewGuid())); users.Add(new Tuple<string, string, Guid>("user", "password", Guid.NewGuid())); } public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context) { var userRecord = users.Where(u => { return u.Item3 == identifier; }).FirstOrDefault(); return userRecord == null ? null : new SampleUserInfo() { UserName = userRecord.Item1 }; } //IUserMapperインターフェイスには含まれていないが、ここでユーザー認証処理を定義している。 //戻り値として、ログイン入力値に対応するユーザーID値を返す。 public static Guid? ValidateUser(string username, string password) { var userRecord = users.Where(u => { return u.Item1 == username && u.Item2 == password; }).FirstOrDefault(); if (userRecord == null) { return null; } return userRecord.Item3; } } }
ログイン、ログアウトルートの実装
以下サンプルソースの場合は、
/loginにPOSTリクエストし、ログインを行い、ログイン失敗したら/login?error=trueにリダイレクト。成功したら/secureへリダイレクトします。
/secureは認証済でないとアクセスできません。ログアウトするには/secure/logoutへアクセスします。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Nancy; using Nancy.Authentication.Forms; using Nancy.Extensions; using Nancy.Security; namespace NancyFormAuthSample { public class MyModule : NancyModule { public IndexModule() : base("/login") { Get["/"] = _ => { //クエリ文字列は、Context.Request.Queryで取得する if (Context.Request.Query.error != null) { return Response.AsText("ユーザーIDまたはパスワードが不正です。", "text/plain; charset=UTF-8"); } else { return Response.AsText("ログインする際はPostリクエストしてください。", "text/plain; charset=UTF-8"); } }; Post["/"] = _ => { var userGuid = UserDatabase.ValidateUser(Context.Request.Form.Username, Context.Request.Form.Password); if (userGuid == null) { //リダイレクト先起点は、モジュールのルートパス起点ではないので注意すること。 return Context.GetRedirect("/login?error=true"); } DateTime? expiry = null; if (Request.Form.RememberMe.HasValue) { expiry = DateTime.Now.AddDays(7); } return Nancy.Authentication.Forms.ModuleExtensions.LoginAndRedirect(this, userGuid, expiry, "/secure"); }; } } public class SecureModule : NancyModule { public SecureModule() : base("/secure") { //このモジュールルートへのアクセスは認証済である必要あり。 this.RequiresAuthentication(); Get["/"] = _ => { string msg = string.Format("{0}さん、セキュアなページです。", Context.CurrentUser.UserName); return Response.AsText(msg, "text/plain; charset=UTF-8"); }; Get["/logout"] = _ => { return this.LogoutAndRedirect("/login"); }; } } }
フォーム認証の設定
Bootstrapperのサブクラスを作成し、フォーム認証設定を行います。
using Nancy; using Nancy.Authentication.Forms; using Nancy.Bootstrapper; using Nancy.TinyIoc; namespace NancyFormAuthSample { public class MyBootstrapper : DefaultNancyBootstrapper { protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context) { base.ConfigureRequestContainer(container, context); //TinyIoCContainerにIUserMapperの割り付けを登録 container.Register<IUserMapper, UserDatabase>(); } protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context) { base.RequestStartup(container, pipelines, context); var formsAuthConfiguration = new FormsAuthenticationConfiguration() { RedirectUrl = "/login", //認証失敗時のリダイレクト先 UserMapper = container.Resolve<IUserMapper>() }; FormsAuthentication.Enable(pipelines, formsAuthConfiguration); //フォーム認証の有効化 } } }