YesodのHashDBプラグインを利用した認証処理をやってみた
いま、最も熱いWeb FrameworkのYesod...って言いたいけど、最近、西のほうのPython遣いたちに追いやられている感はあるものの、でもやっぱYesodは最高ということで、Yesodの認証処理を調べていて分かったことを忘れないうちにまとめてみました
Yesodの認証処理
Yesodの認証処理には複数のプラグインがありましてyesod-authというパッケージにまとめられています、主なプラグインは
- OpenID認証
- 試していません Orz...
- BrowserID認証
- 試していません Orz...
- email認証
- メールアドレスでアカウント発行、ログインまでを実装してあるプラグイン、アカウント新規発行時は確認メールを送信するため、サーバーでpostfixなんかのMTAを動かさなければならないのでチョット面倒、外部のメールサーバーにも出来る
- Hash認証
- 通常のアカウントとパスワードによる認証機構、仕組みを理解するのに簡単なので今回はこちらでお勉強 と言う感じで外部の認証機構をそのまま利用できる今時のプラグインが実装されている。
認証のサンプルを作成
とりあえずScaffoldサイトを構築する、MySQLを利用するのでその辺も起動しておく
Scaffoldの作成
cuomo@karky7 ~/Code $ yesod init Welcome to the Yesod scaffolder. I'm going to be creating a skeleton Yesod project for you. What do you want to call your project? We'll use this for the cabal name. Project name: YesodAuthSample Yesod uses Persistent for its (you guessed it) persistence layer. This tool will build in either SQLite or PostgreSQL or MongoDB support for you. We recommend starting with SQLite: it has no dependencies. s = sqlite p = postgresql pf = postgresql + Fay (experimental) mongo = mongodb mysql = MySQL simple = no database, no auth url = Let me specify URL containing a site (advanced) So, what'll it be? mysql That's it! I'm creating your files now... ... ...
HashDB認証を構築
まず必要なパッケージをインストール
karky7 ~ # emerge -pv dev-haskell/yesod-auth-hashdb These are the packages that would be merged, in order: Calculating dependencies... done! [ebuild R ~] dev-haskell/yesod-auth-hashdb-1.4.1.1:0/1.4.1.1::karky7 USE="doc hscolour profile {-test}" 0 KiB Total: 1 packages (1 reinstalls), Size of downloads: 0 KiB * IMPORTANT: 27 news items need reading for repository 'gentoo'. * Use eselect news to read news items. karky7 ~ #上のパッケージはgentoo-haskellへmergeしていただいたのでそちらでもインストール出きるはずです
データベースへの接続設定をする
Databaseへ接続するためsettings.ymlを修正
cuomo@karky7 ~/Code $ cd YesodAuthSample ~/Code/YesodAuthSample $ git diff diff --git a/config/settings.yml b/config/settings.yml index 4145c02..62a7dd4 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -16,9 +16,9 @@ ip-from-header: "_env:IP_FROM_HEADER:false" database: user: "_env:MYSQL_USER:YesodAuthSample" - password: "_env:MYSQL_PASSWORD:YesodAuthSample" + password: "_env:MYSQL_PASSWORD:abc12345" host: "_env:MYSQL_HOST:localhost" - port: "_env:MYSQL_PORT:5432" + port: "_env:MYSQL_PORT:3306" database: "_env:MYSQL_DATABASE:YesodAuthSample" poolsize: "_env:MYSQL_POOLSIZE:10" ~/Code/YesodAuthSample $
modelの作成
ログインアカウントを管理するUser型を作成、他のモデルは削除しても構いません
User email Text password Text UniqueUser email deriving Typeable -- By default this file is used in Model.hs (which is imported by Foundation.hs)
ルーティングを若干修正
HomeRのPOSTを利用しないので削除
/static StaticR Static appStatic /auth AuthR Auth getAuth /favicon.ico FaviconR GET /robots.txt RobotsR GET / HomeR GET
YesodAuthインスタンスを修正
ディフォルトだとBrowserIDの認証設定になっているので、ここをHashDBプラグインへ修正する、User型をHashDBUserクラスのインスタンスへすることも忘れずに。renderAuthMessageはログイン後に出力されるメッセージをカスタマイズするためにloginMessage関数へ差し替えています。詳しくはYesod.Auth.Messageモジュールを参照してみてください(japaneseMessageとかもあるよ)
instance YesodAuth App where type AuthId App = UserId -- ログイン後のリダイレクト先 loginDest _ = HomeR -- ログアウト後のリダイレクト先 logoutDest _ = HomeR -- Override the above two destinations when a Referer: header is present redirectToReferer _ = True -- UserIdを取得するための関数を設定、maybeAuthId系の関数が利用可能になる getAuthId creds = getAuthIdHashDB AuthR (Just . UniqueUser) creds -- プラグインをauthHashDBへ修正 authPlugins _ = [authHashDB (Just . UniqueUser)] -- ログイン後のメッセージをディフォルトから修正 renderAuthMessage _ _ = loginMessage -- authManagerはそのまま authHttpManager = getHttpManager -- ログイン後に出力されるメッセージ loginMessage :: AuthMessage -> Text loginMessage _ = "乱入ではなくログインしました" -- Userモデルを認証用としSha1のパスワードで認証するように HashDBUserのインスタンスに設定 instance HashDBUser User where userPasswordHash = Just . userPassword setPasswordHash h u = u { userPassword = h }
Home.hsを修正
ログイン、ログアウト後のリダイレクト先を作成、POSTは使わないので削除
module Handler.Home where import Import -- This is a handler function for the GET request method on the HomeR -- resource pattern. All of your resource patterns are defined in -- config/routes -- -- The majority of the code you will write in Yesod lives in these handler -- functions. You can spread them across multiple files if you are so -- inclined, or create a single monolithic file. getHomeR :: Handler Html getHomeR = do maid <- maybeAuthId let handlerName = "getHomeR" :: Text defaultLayout $ do setTitle "HashDB認証サンプル" $(widgetFile "homepage")
テンプレート類の修正
色々削除してしまったので、不要な展開変数やタグを削除
homepage.hamlet
<h2>ログインサンプル $maybe uid <- maid <p>#{show uid} <a href=@{AuthR LogoutR}>ログアウト $nothing <p> <a href=@{AuthR LoginR}>ログイン画面へhomepage.julius
全て削除
homepage.lucius
h1 { text-align: center } h2 { color: #990 }
cabalファイルに依存パッケージを追加
YesodAuthSample.cabalファイルを修正
~/Code/YesodAuthSample $ git diff YesodAuthSample.cabal diff --git a/YesodAuthSample.cabal b/YesodAuthSample.cabal index 0bc29ae..f196287 100644 --- a/YesodAuthSample.cabal +++ b/YesodAuthSample.cabal @@ -79,6 +79,7 @@ library , containers , vector , time + , yesod-auth-hashdb executable YesodAuthSample if flag(library-only) ~/Code/YesodAuthSample $
スキーマを作成
yesod develを実行すればマイグレーションして自動でスキーマを作成してくれる
cuomo@karky7 ~/Code $ cd YesodAuthSample cuomo@karky7 ~/Code/YesodAuthSample $ mysqladmin -u root create YesodAuthSample cuomo@karky7 ~/Code/YesodAuthSample $ yesod devel Yesod devel server. Press ENTER to quit Warning: The package list for \'hackage.haskell.org\' does not exist. Run \'cabal update\' to download it. Resolving dependencies... Configuring YesodAuthSample-0.0.0... Rebuilding application... (using cabal) Starting development server... Starting devel application Migrating: CREATe TABLE `user`(`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,`email` TEXT CHARACTER SET utf8 NOT NULL,`password` TEXT CHARACTER SET utf8 NOT NULL) 30/Dec/2014:12:00:06 +0900 [Debug#SQL] \"CREATe TABLE `user`(`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,`email` TEXT CHARACTER SET utf8 NOT NULL,`password` TEXT CHARACTER SET utf8 NOT NULL)\" [] @(persistent-2.1.1:Database.Persist.Sql.Raw ./Database/Persist/Sql/Raw.hs:55:18) Migrating: ALTER TABLE `user` ADD CONSTRAINT `unique_user` UNIQUE(`email`(200)) Devel application launched: http://localhost:3000 30/Dec/2014:12:00:06 +0900 [Debug#SQL] \"ALTER TABLE `user` ADD CONSTRAINT `unique_user` UNIQUE(`email`(200))\" [] @(persistent-2.1.1:Database.Persist.Sql.Raw ./Database/Persist/Sql/Raw.hs:55:18) cuomo@karky7 ~/Code/YesodAuthSample $
ログインアカウントデータを挿入
sha1でhash化する
cuomo@karky7 ~/Code/YesodAuthSample $ mysql -u root YesodAuthSample Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 14 Server version: 5.6.21-log Source distribution ... ... ... mysql> INSERT INTO user(email, password) VALUES('karky7@sample.jp', sha1('karky7')); Query OK, 1 row affected (0.37 sec) mysql> quit Bye
動かしてみる
cuomo@karky7 ~/Code/YesodAuthSample $ yesod devel
初期画面
ログイン前はログイン画面へのリンクを表示する
ログイン画面
「http://localhost:3000/auth/login」というURLでログイン画面が出力される、先ほどINSERTした認証でログインを実行する
ログイン完了
見た目は悪いのですがログイン完了の画面、URLもHomeRへ戻り、getAuthIdで取得できた認証情報を表示している。User情報のIDを表示しているだけですが。
こんな感じで認証ができる、ログインフォームまで作ってくれるので楽です、細かい動作や出力の調整はクラスの関数をオリジナルのものへ修正すれば可能なので、基本的にディフォルトの動作は実装されています。
コードを書いているとほとんどの悪い箇所をコンパイラが拾ってくれるので凄く楽。Haskell+Yesodが今のところ、一番のお気に入りフレームワークかな。