Antoine.st Login Form

Login Form

よくありがちなログインフォーム。ところが、この必要性って結構微妙です。というのも、アプリケーションを動かす時点ですでに Windows PC、もしくは、Windows Domain にログオンしているからです。

そう考えると、アプリケーションでログインを制御するのは二重管理になってしまうのですが、かといって 「Windows におまかせ」と行かないのも事実。その辺をもう少し深く考えてみることにしましょう。

Necessity

なぜログインの管理が必要なのか。それは、ユーザーの資格によって何らかの制限を行いたいからに他なりません。たとえば、「管理者のみがマスタを修正できる」とか「管理者ではない場合、自分の作成したデータだけが修正、削除できる」とか。逆に言えば、そういった制限が不要であれば、ログインの管理はしなくても一向に構いません。考慮すべき「制限」なんですが、ふたつのものがあります。

  • 機能を制限する
  • データを制限する

機能ってのは、ようするにアプリケーションのメニューに相当します。特定のユーザーのみがマスタを操作することができ、他のユーザーはその画面を開くことができないようにする、ってわけです。これはユーザーインターフェイスの面から考えると、メニューやボタンを Disable して、画面を開けないようにすると便利です。

データってのは、レコードレベルで制限をかけるってことです。たとえば、稟議を承認するアプリケーションを考えてみます。営業部、経理部、システム部など、部署ごとの稟議があり、一覧で表示されます。各部署のマネージャは、所属する部署の稟議は承認できますが、他の部署の稟議は見ることしかできません。このような場合、各レコードごとに処理ができる人とできない人が出てくる、そして、その際に用いられる判断の元がログインのときの資格となります。

この辺の細かいところまで考えると、Windows 認証とか SQL Server 認証って結構使いづらいもんです。

How to implement?

さてと、だいたいどんなことをやる必要があるのかわかったところで、実装方法を検討していきましょう。セキュリティの考え方については別の機会に考えるとして、あくまでログインフォームに関するものです。

  • Windows 認証
  • SQL Server 認証
  • 独自認証

と、考慮する認証方法は 3 つあります。このうち、実装が一番面倒なのが Windows 認証です。というのも、Windows、もしくは Domain にユーザー名とパスワードを渡して、「はい、確認してくださいな」という方法が用意されていない (単に、わたしが気づいてないだけかも) ためです。また、単に確認するだけではだめで、SQL Server における Windows 認証を利用するのであれば、結局ユーザーを偽装しなくてはなりません。

となると、LogonUser() API の出番となります。今のところ、.NET Framework での解決策はないようです。この状況は、Whidbey でも変わらないようで、偽装こそ可能になりましたが、ログオン自体は API に頼る必要があるようです。

で、簡単にソースを書いてみました。こんな感じで、Windows 認証を通し、成功なら True を返して偽装を開始。失敗なら False を返して終了って感じでいけるみたいです。sp_who システムストアドプロシージャで確認してみましたが、成功時はちゃんとユーザーが切り替わってますし。finally でゴニョゴニョやるのは、お行儀悪いですかね?


    private bool ImpersonateUser(
      string domainName, 
      string userName, 
      string password ) {

      IntPtr tokenHandle;

      tokenHandle = new IntPtr( 0 );

      const int LOGON32_PROVIDER_DEFAULT = 0;
      const int LOGON32_LOGON_INTERACTIVE = 2;

      try {

        return LogonUser( userName, domainName, password,
            LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
            ref tokenHandle );

      } finally {

        if ( tokenHandle != IntPtr.Zero ) {
          WindowsIdentity newId;
          WindowsImpersonationContext impersonatedUser;

          newId = new WindowsIdentity( tokenHandle );
          impersonatedUser = newId.Impersonate();
          CloseHandle( tokenHandle );

        }
      }
    }

残りの 2 つに関しては、それほど面倒なことありません。SQL Server 認証の場合は、そのユーザー名とパスワードで接続できるかどうかを試せばいいわけですし、独自認証のときは、(おそらく) データベースにストアされた情報と付き合わせればいいわけですし。

Supplementation

ほんと、できることなら Windows 認証で終わりにしたいんですが、そうも行かないケースの紹介なんぞを。前に経験したのは、PC の電源を朝入れて、夜落とすまでそのまんま、ってやつです。そんで、アプリケーションは入れ替わり立ち替わり、いろんな人が利用するので、そのたびにいちいちログオフしてられないというものでした。

アプリケーションの立ち上げなおしなら、数十秒ですみますけど、ログオフだと結構時間がかかる (速いマシンならそうでもないんでしょうけどね) ってことで、今になってみると、こんな認証方法もありかな、って思います。

Functionality

いろいろ実装案を考えていきましょう。ということで、こんな感じで。とりあえず、インターフェイスだけ作っておいて、実装は後回しです。

AuthenticationMode Property

Windows, SQLServer, Original の 3 種類でいいでしょうね。とりあえず。

User/Password/Domain Property

ユーザー名、パスワード、ドメインを設定。SQL Server 認証では Domain は無視されます。Original 認証では、使うかどうかは実装者にお任せです。

OriginalAuthenticate Delegate

Delegate に Method を登録しておくと、Atuhenticate() メソッドから呼び出されます。後は、独自に実装した認証機構で認証を行う、と。

Authenticate() Method

あらかじめ設定されたユーザー名、パスワード、ドメインによって認証を行います。Windows 認証では認証成功後に偽装を行い、そのままの権限に移行します。Original 認証では、Delegate を呼び出すだけです。