WebSurfer's Home

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

IE11 と Referer

by WebSurfer 2017年8月13日 17:02

ASP.NET 4 の Web Forms アプリで、クエリ文字列に日本語を含めて要求をかけた場合、Windows 10 の IE11 と Edge ではポストバックの際 Referer が送信されないという話を書きます。(ASP.NET 4.5 では問題は解消されています。ASP.NET 3.5 以下は未確認です)

Fiddler によるキャプチャ

そもそもの原因は Windows 10 の IE11 や Edge ではなく、ASP.NET 4(3.5 以前も?・・・未確認です)が応答の form 要素に設定する action 属性にあるようです。

例えば、IE11 のアドレスバーに直接以下の URL(クエリ文字列は "日本語" を UTF-8 のパーセントエンコードしたものです)を入力して直接ページを GET 要求したとします。

http://aspnet4site/test.aspx?P1=%e6%97%a5%e6%9c%ac%e8%aa%9e

そうすると、ASP.NET により応答の form 要素の action 属性に設定される URL は以下のようになります。

action="./test.aspx?P1=%u65e5%u672c%u8a9e"

つまり、要求 URL のクエリ文字列の "日本語" は UTF-8 のパーセントエンコーディングなのに、応答の form 要素の action 属性に設定されるのは Unicode のパーセントエンコーディングになってしまいます。そこに問題があるようです。

具体的には下にアップした簡単なサンプルのページ test.aspx で再現できます。

上の画像は、UTF-8 でパーセントエンコーディングした "日本語" をクエリ文字列に含めた URL を IE11 のアドレスバーに直接入力して test.aspx を要求し、表示されたページで 2 回ポストバックを行ったときの Fiddler によるキャプチャ画像です。

上の画像の #1 が初回の要求・応答で、#3 が 1 回目のポストバック、#4 が 2 回目のポストバックの際の要求・応答です。

問題は 2 回目のポストバックのときで、ブラウザからリファラが送信されません。Edge も同様で 2 回目のポストバックの際はリファラは送信されません。

どのような動きになるかと言うと・・・

#1 の初回要求に対して ASP.NET が返す応答の form 要素の action 属性に同じページの URL が設定されますが、URL に含まれるクエリ文字列の "日本語" は Unicode のパーセントエンコーディング %u65e5%u672c%u8a9e となってしまいます。

なので、1 回目のポストバックの時は要求 URL に含まれるクエリ文字列は %u65e5%u672c%u8a9e となります。上の画像の #3 を見てください。

ただし、1 回目のポストバックの際にブラウザから送信されるリファラは、最初の要求の URL(すなわちアドレスバーに直接入力した URL)になります。つまり、クエリ文字列の "日本語は" UTF-8 のパーセントエンコーディングなので問題なくリファラとして送信されます。問題は 2 回目以降です。

2 回目のポストバックの時(上の画像の #4)は、1 回目のポストバックの際の要求 URL(すなわちクエリ文字列の "日本語" が %u65e5%u672c%u8a9e)をリファラとして送信するはずですが、Windows 10 の IE11 と Edge では送信されません。3 回目、4 回目も同様です。

Microsoft の公式文書が見つけられないので想像ですが、Windows 10 の IE11 と Edge ではセキュリティ対策が強化され、URL に UTF-8 として解釈できないコードがあるとリファラを送信しないということではないかと思っています。

ちなみに、Windows 7 の IE11 では %u65e5%u672c%u8a9e となってしまっていてもリファラは送信されるそうです(聞いた話で未検証・未確認)。Chrome 60.0.3112.90, Firefox 54.0.1 では %u65e5%u672c%u8a9e でも送信されることは確認しました。

やはり、URL にはクエリ文字列を含めて ASCII 文字のみ使用することで徹底するのがよさそうです。

サンプルページ test.aspx

<%@ 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">
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.UrlReferrer != null)
        {
            Label1.Text = Request.UrlReferrer.ToString();
        }
        else
        {
            Label1.Text = "無し";
        }
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <h1>Page B</h1>
    <asp:Button ID="Button1" runat="server" Text="Button" />
    <br />
    UrlReferrer: 
    <asp:Label ID="Label1" runat="server"></asp:Label>
    </form>
</body>
</html>

Tags: , ,

ASP.NET

プロファイル情報を ClaimsIdentity へ追加

by WebSurfer 2017年4月16日 14:13

ASP.NET Identity 2.0 でプロファイル情報を Claim として ClaimsIdentity オブジェクトに追加し、拡張メソッドを使って ClaimsIdentity オブジェクトからプロファイル情報を取得・表示する方法を書きます。(この記事は .NET Framework 版です。Core 版は別の記事「ASP.NET Core MVC の ClaimsIdentity」に書きましたのでそちらを見てください)

プロファイル情報の表示

プロファイル情報とは、ユーザーのメールアドレス、電話番号などのユーザー固有の情報です。ASP.NET プロファイル機能は、フォーム認証のためのクレデンシャル情報(ユーザー名とパスワード)と共に、プロファイル情報を個々のユーザーに関連付けてデータベースに格納します。

ASP.NET Identity 2.0 では、プロファイル情報として Email, PhoneNumber が IdentityUser クラスに定義済みです。それらに加えて、CodeZine の記事 ASP.NET Identityのプロファイル情報のカスタマイズにあるように、姓、名、誕生日などの任意の情報を追加することができます。

プロファイル情報の標準的な設定方法や取得方法は CodeZine の記事を読んでいただければわかるのでここでは書きません。(手抜きでスミマセン。上にリンクを張った CodeZine の記事は ASP.NET Identity 1.0 のもので、2.0 のものとは AspNetUsers テーブルの内容などが異なりますが、基本は同じです)

ここでは、CodeZine の記事のようにその都度データベースから情報を取得するのではなく、User.Identity プロパティから取得できる ClaimsIdentity オブジェクトにプロファイル情報を含めておき、それから取得する方法を書きます。

そのような方法を取る理由は、例えば上の画像のようにマスターページの右上に常にユーザー情報を表示するような場合、ページを描画するたびにデータベースにクエリを発行してプロファイル情報を取得するのは負荷が重そうに感じたからです。

ClaimsIdentity オブジェクトにプロファイル情報を含めれば、ユーザー認証後は認証クッキーに含まれたプロファイル情報がクライアントから送信されてきて、それをベースに ClaimsIdentity オブジェクトを再生成するのだと思います。(それが書いてある Microsoft の公式文書が見つからないので想像の域を出ませんが、実際にいろいろ試した結果からその想像は合っていると思います)

であれば、再生成された ClaimsIdentity オブジェクトからプロファイル情報を取得する方が、データベースから取得するより、負荷は軽そうです。(実は気にするほどの差はないのかもしれませんが)

以下に、例として、PhoneNumber という定義済みのプロファイル情報を Claim として ClaimsIdentity へ追加するコード、ClaimsIdentity からプロファイル情報を取得するための拡張メソッドのコードを載せておきます。

ベースは ASP.NET Web Forms の Web アプリケーションプロジェクトを Visual Studoi 2015 Community のテンプレートを使って自動生成した IdentityModel.cs です。それに Claim を追加するコードと拡張メソッドを追加しています。(Web サイトプロジェクトでは、自動生成される IdentityModel.cs がかなり異なり、同じようにできるかどうかは未確認です。)

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using WebFormsApp.Models;

// 拡張メソッドで FirstOrDefault を使うため追加
using System.Linq;

namespace WebFormsApp.Models
{
  public class ApplicationUser : IdentityUser
  {
    public ClaimsIdentity GenerateUserIdentity(
            ApplicationUserManager manager)
    {
      var userIdentity = manager.CreateIdentity(
        this, DefaultAuthenticationTypes.ApplicationCookie);

      // ここでは例として PhoneNumber を Claim として追加。
      // 未登録(DB 上で NULL)の場合 this.PhoneNumber プロ
      // パティは null を返す。null の場合は追加しても意味
      // がないので追加しない。 (null のまま追加しようとす
      // ると Claim コンストラクタで例外がスローされる)
      if (!string.IsNullOrEmpty(this.PhoneNumber))
      {
        // ClaimTypes クラスは System.Security.Claims 名前
        // 空間に定義済みなのでそれを利用。PhoneNumber プロ
        // パティは IdentityUser クラスに定義済み。
        userIdentity.AddClaim(
          new Claim(ClaimTypes.HomePhone, this.PhoneNumber));
      }

      return userIdentity;
    }
    // ・・・中略・・・
  }

  // ClaimsIdentity から PhoneNumber を取得する拡張メソッド
  // PhoneNumber が Claims にない場合は null を返す。
  public static class MyExtensions
  {
    public static string GetPhoneNumber(
        this System.Security.Principal.IIdentity identity)
    {
      var claimsIdentity = identity as ClaimsIdentity;
      if (claimsIdentity != null)
      {
        var claim = claimsIdentity.Claims.
          FirstOrDefault(c => c.Type == ClaimTypes.HomePhone);
        if (claim != null)
        {
          return claim.Value;
        }
      }
      return null;
    }
  }
  // ・・・中略・・・
}

MVC5 アプリでは、テンプレートで自動生成される IdentityModel.cs のコードが上の Web Forms アプリのものとは少々異なりますが、自力で書いて追加する部分のコードは上記と全く同じになります。

上記の拡張メソッドは名前空間をインポートすればスコープの中に取り込むことができます。例えば、上の画像のようにマスターページの右上に表示する場合は以下のようにします。

<%@ Import Namespace="WebFormsApp.Models" %>

<a runat="server" href="~/Account/Manage" 
  title="Manage your account">
  Hello, <%: Context.User.Identity.GetUserName()  %> !
  Phone: <%: Context.User.Identity.GetPhoneNumber() %>
</a>

Tags: ,

ASP.NET

WCF サービスで HTTP 404.17 エラー

by WebSurfer 2017年2月5日 17:25

先日買った Windows 10 Pro 64-bit のノート PC に旧マシンから開発環境を移しています。

WCF サービスをホストしている ASP.NET Web Forms アプリをソリューションごとコピーして .aspx ページは問題なく動くようになったのですが、何故か WCF サービスだけが動きません。(汗)

ブラウザから .svc ファイルを要求すると HTTP 404.17 エラーとなります。エラーメッセージは「要求されたコンテンツはスクリプトであり、静的ファイル ハンドラーで処理されない可能性があります。」でした。

IIS のハンドラマッピングをみると .svc 用のハンドラは 3 つ存在していたのですが、何故静的ファイルハンドラーにマップされたのでしょう?

3 時間ぐらいハマってしまったのですが、ググって調べて stackoverflow の記事 What causes WCF Error 404.17 to be shown? にたどり着きました。

その記事の質問欄に "I then finally fixed it by enabling "HTTP Activation" (as suggested here)" とあります。その here のリンク先が HTTP Error 404.17 - Not Found で、その記事の回答にある画像すなわち ASP.NET 4.5 の WCF サービスの HTTP アクティブ化が正解のようです。

自分の Windows 10 Pro で該当するのは、以下の画像の「Windows の機能の有効化または無効化」で [.NET Framemwork 4.6 Advanced Services] => [WCF サービス] => [HTTP アクティブ化] のようですので、それにチェックを入れて有効化してみました。

WCF サービスの HTTP アクティブ化

結果、ブラウザから .svc ファイルを要求すると応答が返ってくるようになり、アプリから WCF サービスのメソッドを呼んだ場合も正常に応答が返ってくるようになりました。やはりこれが正解だったようです。

IIS のハンドラマッピングをみると HTTP アクティブ化前には 3 つしかなかった .svc 用のハンドラが、以下の画像のように 6 つに増えています。

ハンドラマッピング

HTTP アクティブ化の前/後でどう変わったのかはっきりしませんが、3 つ増えたのは間違いなくて、たぶん 4.0 用が追加されたのだと思います。

また、上に紹介した stackoverflow の記事に、

"The Http Activation Module (System.ServiceModel.Activation.HttpModule) is registered in the machine.config file. .NET 2.0 and .NET 4.0 have separate machine.configs. The Http Activation feature also exists for both .NET versions separately."

・・・と書いてありましたが、HTTP アクティブ化によって、ハンドラの追加だけではなく、.NET 4 用の Http Activation Module の有効化も行われたのかもしれません。

(ググってヒットした記事の中には ServiceModel 登録ツールを使って解決したという話もありました。HTTP アクティブ化もこのツールを使っているのかもしれません。ただし、確認はできていません。ご参考まで)

Tags: ,

ASP.NET

About this blog

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

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar