|
ASP.NET Tips |
|
No.0001 Windowsユーザーアカウントを使わずにBASIC認証を行う |
BASIC認証はほとんどのWebサーバー、Webブラウザが対応しており、簡単に使えることが特徴ですが、パスワードはクリアテキスト(平文)で送られます。
実際はE-Mailの添付ファイルでよく使われるBASE64という形式でエンコードされていますが、暗号化されているわけではないのでデコードして改ざんすることも容易です。
ですから、多くの場合はSSLとあわせて暗号化して用いられます。
IISにも、このBASIC認証(基本認証)を行う機能が付いています。ASP.NETでは「Web.config」ファイルで「<authentication mode="Windows" />」を指定し、
IISの「ディレクトリセキュリティ」-「認証方法」で「基本認証」にチェックを付けることで利用できます。
ただし、IISではBASIC認証でログインするユーザーは “Windowsにユーザーアカウントを持っている” 必要があります。つまり、
「BASIC認証のパスワード=Windowsユーザーアカウントのパスワード」となっています。
Webからのアクセス許可をしたいだけの場合もWindowsのユーザーアカウントを作らねばなりません。
できればDBに入っているユーザー名、パスワードを使って認証をする、とかしたいところです。
そういうわけでSSLによる暗号化までは行っていませんが、サンプルを作成しました。まずは「Web.config」の設定です。
|
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation defaultLanguage="c#" debug="true" />
<customErrors mode="RemoteOnly" />
<authentication mode="None" />
<authorization>
<deny users="?" />
</authorization>
<globalization
requestEncoding="Shift_JIS"
responseEncoding="Shift_JIS"
/>
</system.web>
<appSettings>
<add key="AuthRealm" value="Authentication Test" />
</appSettings>
</configuration>
|
|
「authentication mode」は他に「Forms」「Passport」「Windows」がありますが、今回は独自に実装するので「None」に設定しておきます。
「authorization」では「deny users="?"」を設定しています。これは未認証のユーザーのみを拒否する設定です。認証を通っていればどのユーザーでもアクセスを許可するということです。
ユーザーロールをみて許可したい場合、例えば「Users」のみを許可したい場合は「allow roles="Users"」と指定します。「deny」や「allow」は組み合わせて設定できます。
「appSettings」の「AuthRealm」は認証のダイアログに表示する文字列を設定しています。これはソースコードに含めることもできますが、今回は「Web.config」に書き出しました。
次は「Global.asax」です。認証の処理はすべてここに書きます。
|
<%@ Application language="C#" %>
<%@ Import Namespace="System.Configuration" %>
<%@ Import Namespace="System.Security.Principal" %>
<script runat="server">
public void Application_OnAuthenticateRequest(Object sender, EventArgs e) {
HttpApplication app = (HttpApplication)sender;
string strAuth = app.Request.Headers["Authorization"];
if (strAuth == null || strAuth.Length == 0) {
return;
}
strAuth = strAuth.Trim();
if (strAuth.IndexOf("Basic", 0) != 0) {
return;
}
string encodedString = strAuth.Substring(6);
byte[] decodedBytes = Convert.FromBase64String(encodedString);
string decodedString = new ASCIIEncoding().GetString(decodedBytes);
string[] arrSplited = decodedString.Split(new char[] {':'});
string username = arrSplited[0];
string password = arrSplited[1];
string[] roles;
if (AuthenticateUser(username, password, out roles)) {
app.Context.User = new GenericPrincipal(new GenericIdentity(username), roles);
} else {
app.Response.StatusCode = 401;
app.Response.StatusDescription = "Unauthorized";
app.Response.Write("<html>\r\n<head>\r\n<title>");
app.Response.Write("401 Unauthorized</title>\r\n</head>\r\n<body>\r\n");
app.Response.Write("<h1>401 Access Denied</h1>\r\n");
app.Response.Write("</body>\r\n</html>\r\n");
app.CompleteRequest();
return;
}
}
public void Application_OnEndRequest(Object sender, EventArgs e) {
HttpApplication app = (HttpApplication)sender;
if (app.Response.StatusCode == 401) {
string strRealm = ConfigurationSettings.AppSettings["AuthRealm"];
strRealm = "Basic Realm=" + strRealm;
app.Response.AppendHeader("WWW-Authenticate", strRealm);
}
}
private bool AuthenticateUser(string username, string password, out string[] roles) {
bool ret = false;
if (username == "test" && password == "test") {
ret = true;
}
roles = new string[1] {"Users"};
return ret;
}
</script>
|
|
「AuthenticateRequest」は「HttpApplication」クラスのパブリックイベントで、セキュリティモジュールがユーザーのIDを確立すると発生します。
「Global.asax」ではこれを「Application_OnAuthenticateRequest」メソッドで処理します。
「Application_OnAuthenticateRequest」のsenderオブジェクトは「HttpApplication」です。まずはこれを取得します。
そしてリクエストヘッダから「Authorization」ヘッダを探して取得します。「Authorization」ヘッダが取得できなかった場合はここで処理を終え、
後述の「Application_OnEndRequest」で処理を行います。さて、ここで「Authorization」ヘッダが得られた場合、これの処理を行います。「Authorization」ヘッダの中身は次のようになっています。
|
Basic [BASE64でエンコードされたユーザー名とパスワード]
|
|
ここからユーザー名とパスワードを取り出すために「Substring」メソッドで文字列の切り出し、デコードを行います。
デコード後の文字列は
となっているので、これを「Split」メソッドで分割してユーザー名とパスワードを取得します。
ユーザー名とパスワードが得られたので、次に「AuthenticateUser」で実際の認証を行います。「AuthenticateUser」は成功か否かでbool型を返すようにし、
ユーザーロールの取得も行いたいので「out」パラメータをつけて1次元の文字列配列を渡します。(「out」パラメータは「ref」パラメータと同様、
参照渡しをするためのキーワードですが、「ref」とは異なり、あらかじめ初期化する必要がありません。)ここで実際の認証処理を行います。
サンプルでは単純にソースコードに「test」「test」を埋め込んでユーザーロールとして「Users」を返すようにしていますが、実際にはここでDBへのアクセスを行い、
ユーザー名、パスワードをチェック、そのユーザーが属するロールの配列を取得、などの処理を行います。DBでは大げさな場合はXMLファイルでもよいかもしれません。
ここではユーザーロールとして「Users」のみを割り当てていますが、
|
roles = new string[3] {"Users", "Administrators", "Developers"};
|
|
などとして、複数割り当てることもできます。
さて、「AuthenticateUser」がtrueを返した場合、「HttpContext」クラス(app.Context)Userプロパティのセットを行います。
プリンシパルオブジェクトを代入しなければならないので、「GenericPrincipal」クラスのインスタンスを入れます。
ユーザー名をそのまま使うことはできないので「GenericIdentity」クラスで標準ユーザーを作成して渡します。第2引数には先ほど作成したロールの文字列配列を渡します。
これでBASIC認証の処理は完了です。
一方、「AuthenticateUser」がtrueを返した場合は認証失敗です。ステータスコードを「401」(未認証,認証失敗)にし、それを表すHTMLを出力します。
最後の「CompleteRequest」メソッドは「EndRequest」を即時実行させるメソッドです。
「EndRequest」は「HttpApplication」クラスのパブリックイベントで、ASP.NETが要求に応答するときに、実行のHTTPパイプラインチェインの最後のイベントとして発生します。
つまり「Application_OnEndRequest」メソッドは最後に実行されるメソッドです。ステータスコードが「401」の場合(「Authorization」ヘッダがない場合と、未認証(または失敗している)場合)に
「WWW-Authenticate」ヘッダを追加します。内容は
|
Basic realm="[ダイアログに表示する文字列]"
|
|
です。ここではダイアログに表示する文字列は「Web.config」の「AuthRealm」から読み取っています。これで認証を要求するダイアログが表示されます。
これで認証の仕組みは完成です。「Web.config」と「Global.asax」をASP.NETアプリケーションのフォルダに入れます(ここでは「c:\Inetpub\wwwroot\authtest」とします)。
このフォルダには「Default.aspx」等の認証をかけたいASP.NETのページファイルも入れておいてください。次にIISの設定を行います。IISの設定ウィンドウから「authtest」フォルダを右クリックし、
「プロパティ」のウィンドウを開きます。
「ディレクトリ」タブの「アプリケーションの設定」で「作成」ボタンをクリックします。すると上図のようになっていると思います。次に「ディレクトリ セキュリティ」タブの
「匿名アクセスおよび認証コントロール」で「編集」ボタンをクリックします。
上図のウィンドウが開くので、図のように「匿名アクセス」のみチェックし、他のチェックをすべてはずします。これですべての作業が完了です。
Webブラウザから「http://localhost/authtest/Default.aspx」等の「*.aspx」ファイルにアクセスしてください。認証のダイアログが開き、正しいユーザー名とパスワードを入力すると、ページを見ることができ、
不正な値を入力するとアクセスははじかれます。
以上のようにしてWindowsユーザーアカウントを使わないBASIC認証は実装できます。 注意点としては(どの認証方式にも言えることですが)ASP.NETのファイル以外には認証がかからないことが挙げられます。
「*.html」や「*.gif; *.jpg; *.png」等は「http://localhost/authtest/test.html」と直接アクセスすれば認証不要で表示できます。すべてをASP.NETのファイルで構成するか、
その他のファイルをASP.NETのファイルとして認識するようにしなければいけないと思います。(確かそんな感じ)
|
|
|
|