WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

非ドメインユーザーの誘導

by WebSurfer 2015年7月9日 22:03

イントラネット内の Active Directory ドメイン環境で、統合 Windows 認証によってシングルサインオンが実現されているサイトがあって、そこに非ドメインユーザーがアクセスしてきたとき、そのユーザーを誘導する(例えば、ドメインユーザー専用である旨通知するとか、別のサイトにリダイレクトする)方法について書きます。(MSDN フォーラム の話をまとめたものです)

Windows 認証のやり取り

クライアントが Windows 認証サイトにアクセスしてくると、認証手続きが始まって、認証が成功し、要求したページが表示されるまで、上の画像(キャプチャツール Fiddler2 のもの。NTLM の場合)のように要求 / 応答がやり取りされます。

NTLM の場合、具体的には以下のステップ 1 ~ 9 のようになります。上の Fiddler2 の画像の左側のウィンドウのライン #1 がステップ 1 ~ 4、#2 がステップ 5 ~ 6、#3 がステップ 7 ~ 8 に該当します。

Kerberos の場合は NTLM より一回ラウンドトリップが少なくなります。以下のステップ 5 ~ 6 がないという感じです。(あくまで「感じ」です。詳しくは MSDN Blog の記事 Two easy ways to pick Kerberos from NTLM in an HTTP capture を見てください)

  1. ブラウザからページ(この例では Default.aspx)を GET 要求。
  2. サーバーは 401 応答を返す。その際、サーバー側で AuthenticateRequest, EndRequest イベント発生。イベントハンドラで User は null になる。
  3. クライアントには認証ダイアログが表示される。
  4. ユーザー名とパスワード入力して[OK]ボタンをクリック。
  5. ブラウザは再度 Default.aspx を GET 要求。
  6. サーバーは 401 応答を返す。この時はサーバー側で AuthenticateRequest, EndRequest イベントは発生しない。
  7. ブラウザは再々度 Default.aspx を GET 要求。
  8. サーバーは 200 応答を返す。サーバー側で AuthenticateRequest, EndRequest イベント発生。イベントハンドラで User はログインユーザーの WindowsPrincipal になる。
  9. ブラウザには Default.aspx が表示される。

統合 Windows 認証の場合、How IIS authenticates browser clients によると、認証済み / 未認証ユーザーの手続きの違いは、Kerberos でも NTLM でも、ステップ 3 ~ 4 の有無ということです。(自分の PC を立ち上げた時にドメインにログイン済みのユーザーの場合はステップ 3 はスキップ、ステップ 4 は自動的に行われる)

ステップ 3 でダイアログが表示された時、非ドメインユーザーができることは、[キャンセル]または[X]ボタンをクリックするか、3 回ユーザー名とパスワードを入力して認証に失敗するかですが、いずれの場合でも標準のエラーページが表示されます。

従って、アクセスしたサイトがドメインユーザー専用であることを説明するなどしたカスタムエラーページを作り、標準のエラーページと差し替えることによって、非ドメインユーザーを適切に誘導することができます。

自分の開発環境の IIS7 の場合ですが、applicationHost.config ファイルの httpErrors 要素に、カスタムページ ErrorPage.htm を以下のように設定することで差し替えることができます。

(注:自分の環境では applicationHost.config で httpErrors に対しては overrideModeDefault="Deny" と設定されているので web.config では httpErrors を設定できませんが、IIS7.5 では web.config で設定可能とのことです。ご自分の環境で確認ください)

<location path="Default Web Site/WindowsAuthentication">
  <system.webServer>
    <httpErrors errorMode="Custom">
      <remove statusCode="401" />
      <error statusCode="401" 
        path="C:\inetpub\custerr\ja-JP\ErrorPage.htm"
        responseMode="File" />
    </httpErrors>
  </system.webServer>
</location>

認証ダイアログが表示された直後に[キャンセル]または[X]ボタンをクリックした場合は、差し替えたカスタムエラーページが表示されます。

しかし、自分の環境で試した限りですが、一旦認証ダイアログに ID / パスワードを入力して認証に失敗した後に返ってくるエラーページは最初の標準エラーページとは異なり、これをカスタムエラーページに差し替えることはではできませんでした。

(注:IIS7.5 ではカスタムエラーページに差し替えられたとの報告があります。その場合は以下の対応は不要となります。ご自分の環境で確認ください)

その場合、EndRequest イベントで User.Identity.IsAuthenticated が true、Response.StatusCode が 401 になることで判定でき、以下のようにすることにより対応できます。ステップ 5 ~ 6 ではイベントは発生しないのでこのやり方でうまくいきます。

void Application_EndRequest(object sender, EventArgs e)
{
  if (User != null)
  {
    bool auth = User.Identity.IsAuthenticated;
    int statusCode = Response.StatusCode;

    if (auth && statusCode == 401)
    {
      HttpContext.Current.Response.Redirect("リダイレクト先");
    }
  }
}

ちなみに正しい ID / パスワードが入力され認証に成功した場合は Response.StatusCode が 200 になりますので、リダイレクトされてしまうということはありません。

ユーザー名とパスワードを入力するダイアログが表示されるのは避けられないとうところが難ありですが、それでよければ上記の案で非ドメインユーザーを適切に誘導することができそうです。興味があればお試しください。

なお、統合 Windows 認証は IE2 以降でのみサポートされているということなので(参考:認証について)、User Agent を調べてブラウザが IE 以外なら即リダイレクト等の処置をとってもいいかもしれません。

Tags: ,

Authentication

GridView の最終ページに空行追加

by WebSurfer 2015年7月7日 16:14

GridView と SqlDataSource を使用してページングを行う際、最終ページの行数が PageSize より少ない場合、最終データ行の次に即ページャー行が表示されます。(下の画像のように空行は表示されません)

最終ページに空行を挿入

それを、上の画像のように空行を挿入(例えば、PageSize が 10 で、最終ページのデータ行が 7 行の場合、空のデータ行を 3 行追加する)する方法を書きます。

自力でコードを書いて DataTable を作って、それを GridView にデータバインドしているような場合は、DataTable に空行を追加するのが簡単そうですが、GridView と SqlDataSource を組み合わせて使っているような場合はその手が使えません。(ウラワザ的なことをすれば話は別かもしれませんが)

なので、DataTable を細工するのは諦めて、GridView.RowCreated イベントでデータ行の行数を調べ、それが PageSize より少ない場合は空の GridViewRow を追加する方法を取ってみました。

簡単に書くと、(1) ヘッダ行で RowCreated イベントが発生した際にそのセル数を取得。[ヘッダ行のセル数] = [データ行のセル数] というのが前提です、(2) ページャー行で RowCreated イベントが発生した際、[データ行] < [PageSize] であればその差の分空のデータ行を生成し GridView に追加する・・・ということです。

そのコードは以下のようになります。コメントに上記 (1), (2) の詳細を書きましたので見てください。データベースは、Microsoft が提供しているサンプル Northwind の Products テーブルを使っています。

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

  // データ行のセル数
  int numberOfCells = 0;
    
  protected void GridView1_RowCreated(object sender, 
                                      GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.Header)
    {
      // ヘッダ行のセル数=データ行のセル数が前提
      numberOfCells = e.Row.Cells.Count;
    }        
    else if (e.Row.RowType == DataControlRowType.DataRow)
    {
      // データ行では何もしない
    }
    else if (e.Row.RowType == DataControlRowType.Footer)
    {
      // フッタ行でも何もしない。ページャー行よりこち
      // らの方のイベント発生が先になるが
    }
    else if (e.Row.RowType == DataControlRowType.Pager)
    {
      // AllowPaging="False" に設定されているとここに
      // 制御は飛んでこない。なので、データ行追加の小
      // 細工はここで行った方がよさそう。
            
      GridView gridView = (GridView)sender;
      int numberOfRows = gridView.Rows.Count;
      int pageSize = gridView.PageSize;

      // データ行 < PageSize の場合、その差の分だけ
      // 空のデータ行とその中身(空セル)を生成
      for (int i = numberOfRows; i < pageSize; i++)
      {
        GridViewRow row = new GridViewRow(
                i,
                -1,
                DataControlRowType.DataRow,
                DataControlRowState.Normal);

        for (int j = 0; j < numberOfCells; j++)
        {
          TableCell cell = new TableCell();
          row.Cells.Add(cell);
        }

        // 空のデータ行を GridView に追加。
        // Collection の中の順番は Footer/Pager 行の
        // 後になるが、HTML にレンダリングされる時は
        // 期待した順序になる。上の GridViewRow のコ
        // ンストラクタで設定した i (RowIndex) と
        // DataControlRowType.DataRow を見ている?
        gridView.Controls[0].Controls.Add(row);
      }           
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
  <style type="text/css">
      table tr
      {
        /* 行(空行を含む)の高さ設定 */
        height: 30px;
      }    
  </style>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:SqlDataSource ID="SqlDataSource1" 
      runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand="SELECT * FROM [Products]">
    </asp:SqlDataSource>

    <asp:GridView ID="GridView1" runat="server" 
      AllowPaging="True" 
      AutoGenerateColumns="False" 
      DataKeyNames="ProductID" 
      DataSourceID="SqlDataSource1" 
      OnRowCreated="GridView1_RowCreated">
      <Columns>
        <asp:BoundField DataField="ProductID" 
          HeaderText="ProductID" InsertVisible="False" 
          ReadOnly="True" SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" 
          HeaderText="ProductName" 
          SortExpression="ProductName" />
        <asp:BoundField DataField="SupplierID" 
          HeaderText="SupplierID" 
          SortExpression="SupplierID" />
        <asp:BoundField DataField="CategoryID" 
          HeaderText="CategoryID" 
          SortExpression="CategoryID" />
        <asp:BoundField DataField="QuantityPerUnit" 
          HeaderText="QuantityPerUnit" 
          SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice" 
          HeaderText="UnitPrice" 
          SortExpression="UnitPrice" />
        <asp:BoundField DataField="UnitsInStock" 
          HeaderText="UnitsInStock" 
          SortExpression="UnitsInStock" />
        <asp:BoundField DataField="UnitsOnOrder" 
          HeaderText="UnitsOnOrder" 
          SortExpression="UnitsOnOrder" />
        <asp:BoundField DataField="ReorderLevel" 
          HeaderText="ReorderLevel" 
          SortExpression="ReorderLevel" />
        <asp:CheckBoxField DataField="Discontinued" 
          HeaderText="Discontinued" 
          SortExpression="Discontinued" />
      </Columns>
    </asp:GridView>
  </div>
  </form>
</body>
</html>

Tags:

Paging

正規表現パターンにコメント

by WebSurfer 2015年7月4日 17:33

.NET Framework の C# のコードで、正規表現のパターンにコメントをつける方法を備忘録として書いておきます。

正規表現のパターンにコメント付与

上の画像を見れば一目瞭然で、それ以上の説明は不要かもしれませんが、それではブログの記事としてはちょっと寂しいので追加情報なども以下に書いておきます。

正規表現については、自分的には MSDN の記事 ASP.NET の正規表現 が一番分かりやすく、いつも参考にさせてもらっています。

おかげさまで、自分でもある程度パターンを作れるようにはなったのですが、自分で作っておきながら後になって読むと意味不明ということがあります。

そういう時のために、正規表現パターンにコメントをつけておくと良いという MDSN の記事を見つけて真似しています。

その記事見つからなくなってしまったので、以前自分が書いたコードを探して、それを参考にしてコメントをつけていましたが、探して見つけるのが結構大変ということで、ブログに書いておくことにしました。

以下は完全に余談ですが・・・

例として、パスワードの文字列で、条件として「半角大文字アルファベットと半角数字のみの 4 文字以上、8 文字以下で構成され、それぞれ最低 1 文字を含む」というケースを考えてみます。

そのようなケースでは、上に紹介した「ASP.NET の正規表現」のページの「高度なトピック」のセクションに書いてある "ルックアラウンド処理" を利用するのが便利だと思います。

「正の先読み」および「負の先読み」の両方のケースで書いてみました。前者が下のコードの regex1、後者が regex2 です。

Regex regex1 = new Regex(@"
    ^               # 開始のアンカー
    (?=.*\d)        # 数字が最低 1 文字あること
    (?=.*[A-Z])     # 英大文字が最低 1 文字あること
    [A-Z0-9]{4,8}   # 英大文字または数字が 4 ~ 8 文字
    $               # 終了のアンカー",
    RegexOptions.IgnorePatternWhitespace);

Regex regex2 = new Regex(@"
    (?!^[0-9]*$)    # 全部が数字ということはない
    (?!^[A-Z]*$)    # 全部が英大文字ということはない
    ^               # 開始のアンカー
    ([A-Z0-9]{4,8}) # 英大文字または数字が 4 ~ 8 文字
    $               # 終了のアンカー",
   RegexOptions.IgnorePatternWhitespace);

string[] testStrings = { "AB1", "AB12", "ABCDEF", "123456", 
                         "ABCDEFG1", "1234567A", "ABCD12345", 
                         "ABC123dE", "ABCあ123", "ABC%1234" };

foreach (string s in testStrings)
{
    Console.WriteLine(s + " => " + regex1.IsMatch(s) + " (1)");
    Console.WriteLine(s + " => " + regex2.IsMatch(s) + " (2)");
}

// 結果は:
// AB1 => False (1)
// AB1 => False (2)
// AB12 => True (1)
// AB12 => True (2)
// ABCDEF => False (1)
// ABCDEF => False (2)
// 123456 => False (1)
// 123456 => False (2)
// ABCDEFG1 => True (1)
// ABCDEFG1 => True (2)
// 1234567A => True (1)
// 1234567A => True (2)
// ABCD12345 => False (1)
// ABCD12345 => False (2)
// ABC123dE => False (1)
// ABC123dE => False (2)
// ABCあ123 => False (1)
// ABCあ123 => False (2)
// ABC%1234 => False (1)
// ABC%1234 => False (2)

「正の先読み」というのが (?=<pattern>) という形のもので、対象文字列を先読みしていって <pattern> の条件に合えば true になります。例えば、上のコードでいうと regex1 の (?=.*\d) が該当します。<pattern> は .*\d で、「任意の文字 0 回以上の繰り返しのあと数字がある」という条件になります。

「負の先読み」というのは (?!<pattern>) という形のもので、「正の先読み」の逆すなわち否定になり、<pattern> の条件に合わないものが true になります。上のコードの regex2 の (?!^[0-9]*$) の場合、<pattern> は ^[0-9]*$ で、「文字列の最初から最後まで全ての文字が数字ではない」という条件になります。

後者の方はホントにこれでいいのか自信がないですが(汗)、上のコードの中のコメントに書いたマッチするか否かの結果を見る限りでは、よさそうな感じです。(笑)

Tags:

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar