JSON のセキュリティ対策は、以下の記事に記載した通りですが、これを ASP.NET で実現するにはどうしたらよいでしょうか。この記事では、JSON セキュリティ対策のサンプルプログラムを提示します。
ASP.NET で JSON データを返す Web サービスを作成するには、.NET Framework 3.0 から提供されている WCF(Windows Communication Foundation) を使用します。
また、ASP.NET 内のデータは .NET のクラスを使用しますが、クラスのインスタンスを JSON の形式に変換するのに、今回はオープンソースの JSON.NET を使用します。JSON.NET は、.NET Framework 2.0 以降で動作します。
目次
概要
この記事で作成する WCF の Web サービスでは、JSON.NET を使用して .NET のインスタンスを JSON に変換します。
クライアントからリクエストを受け取ると、Web サービスは該当するデータを検索して、一致したデータを JSON の形式でクライアントに返します。
呼び出し元のクライアントは、jQuery と prototype.js を使用した HTML を作成します。
準備
今回は、Visual Studio 2012 for Web を使用して Web サービスを作成しますが、その前に NuGet を使用して、JSON.NET, jQuery, prototype.js のパッケージをインストールしておきます。
WCF Web サービスの作成
WCF の Web サービスを作成するには、WCF のインターフェース、インターフェースの実装、Web サービスの定義が必要です。また、データクラスとして Person クラスを作成します。
WCF のインターフェース
GET リクエストでパラメーターを受け付け、JSON で値を返すよう定義します。
・IService1.cs (C#)
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; using System.Web; using System.ServiceModel.Channels; namespace WcfService1 { [ServiceContract] public interface IService1 { [OperationContract] [WebInvoke(UriTemplate = "GetPerson?name={name}", Method = "GET")] Message GetPerson(string name); } }
・IService1.vb(VB.NET)
Imports System.ServiceModel Imports System.ServiceModel.Web Imports System.ServiceModel.Channels <ServiceContract()> Public Interface IService1 <OperationContract()> <WebInvoke(UriTemplate:="GetPerson?name={name}", Method:="GET")> Function GetPerson(ByVal name As String) As Message End Interface
インターフェースの実装
まず、リクエストヘッダーを見て XMLHttpRequest のリクエストかチェックします。そして、受け取ったパラメーターを条件にしてデータを検索し、一致したデータを JSON に変換して値を返します。また、セキュリティ上必要なレスポンスヘッダーも付与します。
・Service1.svc.cs(C#)
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; using System.Web; using System.ServiceModel.Channels; using Newtonsoft.Json; namespace WcfService1 { public class Service1 : IService1 { public Message GetPerson(string name) { //check XMLHttpRequest/JSONP string xhr = WebOperationContext.Current.IncomingRequest.Headers["X-Requested-With"]; if (xhr != "XMLHttpRequest") { WebOperationContext ctx = WebOperationContext.Current; ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Forbidden; throw new HttpException("Forbidden"); } //create data List<Person> list = new List<Person>(); list.Add(new Person() { Name = "Yamada", Age = 10 }); list.Add(new Person() { Name = "Sato", Age = 20 }); list.Add(new Person() { Name = "Tanaka", Age = 30 }); //select person by LINQ var person = from t in list where t.Name == name select t; //object to json by JSON.NET string body = JsonConvert.SerializeObject(person); //Add 'X-Content-Type-Options: nosniff' header WebOperationContext .Current.OutgoingResponse.Headers.Add ("X-Content-Type-Options", "nosniff"); //Return JSON return WebOperationContext.Current.CreateTextResponse(body, "application/json; charset=utf-8", Encoding.UTF8); } } }
・Service1.svc.vb(VB.NET)
Imports System.ServiceModel.Channels Imports Newtonsoft.Json Public Class Service1 Implements IService1 Public Sub New() End Sub Public Function GetPerson(name As String) As Message Implements IService1.GetPerson 'check XMLHttpRequest/JSONP' Dim xhr As String = WebOperationContext.Current.IncomingRequest.Headers("X-Requested-With") If xhr <> "XMLHttpRequest" Then Dim ctx As WebOperationContext = WebOperationContext.Current ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Forbidden Throw New HttpException("Forbidden") End If 'create data' Dim list As List(Of Person) = New List(Of Person) list.Add(New Person() With {.Name = "Yamada", .Age = 10}) list.Add(New Person() With {.Name = "Sato", .Age = 20}) list.Add(New Person() With {.Name = "Tanaka", .Age = 30}) 'select person by LINQ' Dim person = From t In list _ Where t.Name = name _ Select t 'object to json by JSON.NET' Dim body As String = JsonConvert.SerializeObject(person) 'Add 'X-Content-Type-Options: nosniff' header' WebOperationContext.Current.OutgoingResponse.Headers.Add("X-Content-Type-Options", "nosniff") 'Return JSON' Return WebOperationContext.Current.CreateTextResponse(body, "application/json; charset=utf-8", Encoding.UTF8) End Function End Class
Web サービスの定義
Service1.svc ファイルをマークアップで表示し、Factory=”System. ServiceModel. Activation. WebServiceHostFactory” を追加します。
・C#
<%@ ServiceHost Language="C#" Debug="true" Service="WcfService1.Service1" CodeBehind="Service1.svc.cs" Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>
・VB.NET
<%@ ServiceHost Language="VB" Debug="true" Service="WcfService2.Service1" CodeBehind="Service1.svc.vb" Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>
データクラスの作成
Person クラスを作成します。
・Person.cs (C#)
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace WcfService1 { public class Person { public string Name { get; set; } public int Age { get; set; } } }
・Person.vb(VB.NET)
Public Class Person Private _Name As String Public Property Name() As String Get Return _Name End Get Set(ByVal value As String) _Name = value End Set End Property Private _Age As Integer Public Property Age() As Integer Get Return _Age End Get Set(ByVal value As Integer) _Age = value End Set End Property End Class
クライアントの作成
Web サービスにリクエストを送信し、レスポンスのデータを表示するクライアントを作成します。レスポンスのデータを表示する際、HTML エスケープしていることに注意してください。
jQuery で作成
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <script src="Scripts/jquery-2.0.3.min.js"></script> <script type="text/javascript"> $.ajax({ type: "GET", url: "Service1.svc/GetPerson", data: "name=Yamada", success: function (data, textStatus, xhr) { //HTML escape and append $("#person").text(data[0].Name + " " + data[0].Age); } }); </script> </head> <body> <div id="person"></div> </body> </html>
実行結果は、以下のようになります。
Yamada 10
jQuery によって、以下の行がリクエストヘッダーに追加されていることに注意してください。
- X-Requested-With: XMLHttpRequest
GET http://localhost:58069/Service1.svc/GetPerson?name=Yamada HTTP/1.1 Host: localhost:58069 Connection: keep-alive Accept: */* X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36 Referer: http://localhost:58069/HtmlPage1.html Accept-Encoding: gzip,deflate,sdch Accept-Language: ja,en-US;q=0.8,en;q=0.6
以下の2行が、レスポンスヘッダー追加されていることに注意してください。
- Content-Type: application/json; charset=utf-8
- X-Content-Type-Options: nosniff
HTTP/1.1 200 OK Cache-Control: private Content-Length: 31 Content-Type: application/json; charset=utf-8 Server: Microsoft-IIS/8.0 X-Content-Type-Options: nosniff X-AspNet-Version: 4.0.30319 X-SourceFiles: =(cut) X-Powered-By: ASP.NET Date: Mon, 29 Jul 2013 13:02:18 GMT
prototype.js で作成
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <script src="Scripts/prototype.js"></script> <script type="text/javascript"> new Ajax.Request("Service1.svc/GetPerson", { method: "get", parameters:{name:"Yamada"}, onSuccess: function (data) { //Text to JSON and security check var json = data.responseText.evalJSON(true); var str = json[0].Name + " " + json[0].Age; $("person").update(str.escapeHTML()); //HTML escape and update } }); </script> </head> <body> <div id="person"></div> </body> </html>
実行結果は、以下のようになります。
Yamada 10
prototype.js によって、以下の行がリクエストヘッダー追加されていることに注意してください。
- X-Requested-With: XMLHttpRequest
GET http://localhost:58069/Service1.svc/GetPerson?name=Yamada HTTP/1.1 Host: localhost:58069 Connection: keep-alive Accept: text/javascript, text/html, application/xml, text/xml, */* X-Prototype-Version: 1.7 X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36 Referer: http://localhost:58069/HtmlPage2.html Accept-Encoding: gzip,deflate,sdch Accept-Language: ja,en-US;q=0.8,en;q=0.6
以下の2行がレスポンスヘッダー追加されていることに注意してください。
- Content-Type: application/json; charset=utf-8
- X-Content-Type-Options: nosniff
HTTP/1.1 200 OK Cache-Control: private Content-Length: 31 Content-Type: application/json; charset=utf-8 Server: Microsoft-IIS/8.0 X-Content-Type-Options: nosniff X-AspNet-Version: 4.0.30319 X-SourceFiles: =(cut) X-Powered-By: ASP.NET Date: Mon, 29 Jul 2013 13:09:09 GMT
参考
Webアプリケーションセキュリティに関する記事は、以下のページにまとまっています。ぜひご確認ください。
コメント