mixi Developer Center (ミクシィ デベロッパーセンター)

mixi Apps

mixi Apps » Technical Specification » PC » 外部サーバの呼び出し » 署名付きリクエストの検証

署名付きリクエストの検証

この記事では、署名付きリクエストをどのように検証するかを説明します。もしあなたが署名付きリクエストとは何かわからない場合や、なぜこれを使わなければならないのかわからない場合は、署名付きリクエストの紹介ページをチェックしてください。

※ このページの内容は、「Validating Signed Requests」の翻訳に一部修正を加えたものです。

暗号化キーを入手する

署名付きリクエストは、現在3つのパラメータを含みます。

  • oauth_consumer_key
  • xoauth_signature_publickey
  • oauth_signature_method

oauth_consumer_keyパラメータは、リクエストが送信されたコンテナを示します。oauth_signature_methodパラメータは、リクエストを署名するために使われたメソッドを示します。RSA-SHA1で署名されたリクエストのために、xoauth_signature_publickeyはリクエストを署名するために使われた公開鍵の名前を含みます。

HMAC-SHA1のために、あなたはあなたのアプリケーションとコンテナの間でSecret keyを確立する必要があるでしょう。各コンテナは、それをするために異なるメカニズムを持ちますので、共有Secret keyを確立することに関する情報を得るために、あなたが対象とするコンテナのドキュメントを参照してください。

RSA-SHA1のために、あなたはxoauth_signature_publickeyによって参照される証明書を入手する必要があるでしょう。あなたの利便性のために、各証明書へのリンクを含む各コンテナで利用可能な公開鍵の一覧がhttps://opensocialresources.appspot.com/certificates/にあります。このサイトは、利便性のためのみのものであり、各コンテナによって承認されたリストではありません!ベストなセキュリティにおいて、承認された公開鍵の位置を検証するために、必ずあなたのコンテナのドキュメントをチェックしてください!

証明書は、あなたがパラメータを検証したい度に取得されるべきではありません。その代わりに、xoauth_signature_publickey、oauth_consumer_key、そしてoauth_signature_methodの値でインデックスされた、サーバサイドの鍵キャッシュ機構を実装してください。もしそれらの値に変更があった際には、あなたはあなたの鍵キャッシュの中で、新しい証明書をダウンロードし格納する必要があるでしょう。

OAuth署名メカニズム

パラメータの署名は、OAuthパラメータ署名仕様に従って実装されます。

署名ベース文字列を生成することに関する情報は、このページをチェックしてください。

PHP

クライアントサイドコード

あなたのクライアントコードは、以下の作法においてリクエストを作るべきです。私たちは、gadgets.io.AuthorizationType.SIGNEDリクエストを指定し、そしてJSONとして返される結果を期待するでしょう。

function makeSignedRequest() {
  var params = {};
  params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.SIGNED;
  params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
  var url = "http://graargh.returnstrue.com/buh/fetchme.php";
  gadgets.io.makeRequest(url, response, params);
};

function response(ret) {
  output(ret.data);

  var html = [ ret.data.validated, "<br />",
    "oauth_consumer_key: ", ret.data.query.oauth_consumer_key, "<br />",
    "oauth_nonce: ", ret.data.query.oauth_nonce, "<br />",
    "oauth_signature: ", ret.data.query.oauth_signature, "<br />",
    "oauth_signature_method: ", ret.data.query.oauth_signature_method, "<br />",
    "oauth_timestamp: ", ret.data.query.oauth_timestamp, "<br />",
    "oauth_token: ", ret.data.query.oauth_token, "<br />",
    "opensocial_appid: ", ret.data.query.opensocial_appid, "<br />",
    "opensocial_ownerid: ", ret.data.query.opensocial_ownerid, "<br />",
    "xoauth_signature_publickey: ", ret.data.query.xoauth_signature_publickey
  ].join("");

  output(html);
};

makeSignedRequest();
サーバサイドコード

以下のコードは、fetchme.phpというサーバサイドコードとなります。このコードは、オープンソースのOAuthライブラリプロジェクトにあるOAuth.php (リビジョン 526)に依存しています。

コードは、以下の手順で実装されています:

  • OAuthSignatureMethod_RSA_SHA1を継承したMixiSignatureMethodクラスを作成。
  • 受け取ったリクエストからOAuthRequestオブジェクトを生成。
  • MixiSignatureMethodのインスタンスを生成。
  • MixiSignatureMethodクラスを使ってリクエストをチェック。
  • JSONオブジェクトを出力。

この例は、プロダクション品質のコードではありません。ただ単に、あなたがサーバサイドで署名付きリクエストを検証することを試すための手順のデモに過ぎません。

require_once("OAuth.php");

class MixiSignatureMethod extends OAuthSignatureMethod_RSA_SHA1 {
  protected function fetch_public_cert(&$request) {
  return <<
-----BEGIN CERTIFICATE-----
MIICdzCCAeCgAwIBAgIJAOi/chE0MhufMA0GCSqGSIb3DQEBBQUAMDIxCzAJBgNV
BAYTAkpQMREwDwYDVQQKEwhtaXhpIEluYzEQMA4GA1UEAxMHbWl4aS5qcDAeFw0w
OTA0MjgwNzAyMTVaFw0xMDA0MjgwNzAyMTVaMDIxCzAJBgNVBAYTAkpQMREwDwYD
VQQKEwhtaXhpIEluYzEQMA4GA1UEAxMHbWl4aS5qcDCBnzANBgkqhkiG9w0BAQEF
AAOBjQAwgYkCgYEAwEj53VlQcv1WHvfWlTP+T1lXUg91W+bgJSuHAD89PdVf9Ujn
i92EkbjqaLDzA43+U5ULlK/05jROnGwFBVdISxULgevSpiTfgbfCcKbRW7hXrTSm
jFREp7YOvflT3rr7qqNvjm+3XE157zcU33SXMIGvX1uQH/Y4fNpEE1pmX+UCAwEA
AaOBlDCBkTAdBgNVHQ4EFgQUn2ewbtnBTjv6CpeT37jrBNF/h6gwYgYDVR0jBFsw
WYAUn2ewbtnBTjv6CpeT37jrBNF/h6ihNqQ0MDIxCzAJBgNVBAYTAkpQMREwDwYD
VQQKEwhtaXhpIEluYzEQMA4GA1UEAxMHbWl4aS5qcIIJAOi/chE0MhufMAwGA1Ud
EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAR7v8eaCaiB5xFVf9k9jOYPjCSQIJ
58nLY869OeNXWWIQ17Tkprcf8ipxsoHj0Z7hJl/nVkSWgGj/bJLTVT9DrcEd6gLa
H5TbGftATZCAJ8QJa3X2omCdB29qqyjz4F6QyTi930qekawPBLlWXuiP3oRNbiow
nOLWEi16qH9WuBs=
-----END CERTIFICATE-----
EOD;
  }
}

//Build a request object from the current request
$request = OAuthRequest::from_request(null, null, array_merge($_GET, $_POST));

//Initialize the new signature method
$signature_method = new MixiSignatureMethod();

//Check the request signature
@$signature_valid = $signature_method->check_signature($request, null, null, $_GET["oauth_signature"]);

//Build the output object
$payload = array();
if ($signature_valid == true) {
  $payload["validated"] = "Success! The data was validated";
} else {
  $payload["validated"] = "This request was spoofed";
}

//Add extra parameters to help debugging
$payload["query"] = array_merge($_GET, $_POST);
$payload["rawpost"] = file_get_contents("php://input");

//Return the response as JSON
print(json_encode($payload));
結果

あなたがサーバコードに対してクライアントサイドのスクリプトを実行したとき、”response”関数は以下のオブジェクトのひとつを伴って呼び出されるでしょう:

もしリクエストが検証された場合:

{ "validated" : "Success! The data was validated",
  "query" : {  }
}

もしリクエストが検証されなかった場合:

{ "validated" : "This request was spoofed",
  "query" : {  }
}

この結果は以下の書式で出力されます:

Success! The data was validated
oauth_consumer_key: mixi.jp
oauth_nonce: c970afeb19325be3
oauth_signature: #### REALLY LONG STRING ###
oauth_signature_method: RSA-SHA1
oauth_timestamp: 1201225242
oauth_token:
opensocial_appid: ############
opensocial_ownerid: ############
xoauth_signature_publickey: pub.1199819524.-1556113204990931254.cer

Java

クライアントサイドコード
<?xml version="1.0" encoding="UTF-8"?>
<Module>
  <ModulePrefs title="oAuth Java Example">
    <Require feature="opensocial-0.7"></Require>
    <Require feature="dynamic-height"></Require>
  </ModulePrefs>
  <Content type="html">
   <![CDATA[
    <script type="text/javascript">
      var servletUrl="http://oauthtest.s42.eatj.com/oauth/SignedFetchVerifyServlet";

      function response(data) {
        document.getElementById('dom_handle').innerHTML=data.text;
      };

      function request() {
        var params={};
        params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.SIGNED;
        gadgets.io.makeRequest(servletUrl,response,params);
      };

      gadgets.util.registerOnLoadHandler(request);
    </script>

    <div id="dom_handle"></div>
   ]]>
 </Content>
</Module>
サーバサイドコード

コードは以下の手順で実装されます:

  • OAuthServiceProviderを生成します。
  • OAuthConsumerを生成し、mixiの証明書を伴うRSA証明書を投入します。
  • HttpServletRequestからOAuthMessageを展開します。
  • consumerを使ってOAuthAccessorを生成します。
  • accessorを使ってOAuthMessageを検証します。

このコードは、オープンソースのOAuthライブラリプロジェクトのJava実装に依存します。

この例は、プロダクション品質のコードではありません。ただ単に、あなたがサーバサイドで署名付きリクエストを検証することを試すための手順のデモに過ぎません。

package net.oauth.example.provider.servlets;

import net.oauth.OAuth;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthMessage;
import net.oauth.OAuthProblemException;
import net.oauth.OAuthServiceProvider;
import net.oauth.server.OAuthServlet;
import net.oauth.signature.RSA_SHA1;

import java.util.ArrayList;
import java.io.IOException;
import java.util.Map;
import java.io.PrintWriter;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SignedFetchVerifyServlet extends HttpServlet {

    private final static String CERTIFICATE =
        "-----BEGIN CERTIFICATE-----\n"
        + "MIICdzCCAeCgAwIBAgIJAOi/chE0MhufMA0GCSqGSIb3DQEBBQUAMDIxCzAJBgNV\n"
        + "BAYTAkpQMREwDwYDVQQKEwhtaXhpIEluYzEQMA4GA1UEAxMHbWl4aS5qcDAeFw0w\n"
        + "OTA0MjgwNzAyMTVaFw0xMDA0MjgwNzAyMTVaMDIxCzAJBgNVBAYTAkpQMREwDwYD\n"
        + "VQQKEwhtaXhpIEluYzEQMA4GA1UEAxMHbWl4aS5qcDCBnzANBgkqhkiG9w0BAQEF\n"
        + "AAOBjQAwgYkCgYEAwEj53VlQcv1WHvfWlTP+T1lXUg91W+bgJSuHAD89PdVf9Ujn\n"
        + "i92EkbjqaLDzA43+U5ULlK/05jROnGwFBVdISxULgevSpiTfgbfCcKbRW7hXrTSm\n"
        + "jFREp7YOvflT3rr7qqNvjm+3XE157zcU33SXMIGvX1uQH/Y4fNpEE1pmX+UCAwEA\n"
        + "AaOBlDCBkTAdBgNVHQ4EFgQUn2ewbtnBTjv6CpeT37jrBNF/h6gwYgYDVR0jBFsw\n"
        + "WYAUn2ewbtnBTjv6CpeT37jrBNF/h6ihNqQ0MDIxCzAJBgNVBAYTAkpQMREwDwYD\n"
        + "VQQKEwhtaXhpIEluYzEQMA4GA1UEAxMHbWl4aS5qcIIJAOi/chE0MhufMAwGA1Ud\n"
        + "EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAR7v8eaCaiB5xFVf9k9jOYPjCSQIJ\n"
        + "58nLY869OeNXWWIQ17Tkprcf8ipxsoHj0Z7hJl/nVkSWgGj/bJLTVT9DrcEd6gLa\n"
        + "H5TbGftATZCAJ8QJa3X2omCdB29qqyjz4F6QyTi930qekawPBLlWXuiP3oRNbiow\n"
        + "nOLWEi16qH9WuBs=\n"
        + "-----END CERTIFICATE-----";

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        verifyFetch(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        verifyFetch(req, resp);
    }

    private void verifyFetch(HttpServletRequest request, HttpServletResponse resp)
            throws IOException, ServletException {
        resp.setContentType("text/html; charset=UTF-8");
        PrintWriter out = resp.getWriter();

        try {
            OAuthServiceProvider provider = new OAuthServiceProvider(null, null, null);
            OAuthConsumer consumer = new OAuthConsumer(null, "mixi.jp", null, provider);
            consumer.setProperty(RSA_SHA1.X509_CERTIFICATE, CERTIFICATE);

            String method = request.getMethod();
            String requestUrl = getRequestUrl(request);
            List requestParameters = getRequestParameters(request);

            OAuthMessage message = new OAuthMessage(method, requestUrl, requestParameters);

            OAuthAccessor accessor = new OAuthAccessor(consumer);
            out.print("*** OAuthMessage Params:");
            out.print("URL: " + OAuthServlet.htmlEncode(message.URL));
            for (java.util.Map.Entry param : message.getParameters()) {
                String key = param.getKey().toString();
                String value = param.getValue().toString();
                out.print("");
                out.print("Param Name-->" + OAuthServlet.htmlEncode(key));
                out.print(" ");
                out.print("Value-->" + OAuthServlet.htmlEncode(value));
            }
            out.print("");
            out.print(" VALIDATING SIGNATURE ");
            out.print("");
            message.validateSignature(accessor);
            out.print("REQUEST STATUS::OK");
            out.print("");
        } catch (OAuthProblemException ope) {
            out.print("");
            out.print("OAuthProblemException-->"
                + OAuthServlet.htmlEncode(ope.getProblem()));
        } catch (Exception e) {
            out.println(e);
            System.out.println(e);
            throw new ServletException(e);
        } finally {
            out.flush();
        }
    }

    /**
     * Constructs and returns the full URL associated with the passed request
     * object.
     *
     * @param  request Servlet request object with methods for retrieving the
     *         various components of the request URL
     */
    public static String getRequestUrl(HttpServletRequest request) {
        StringBuilder requestUrl = new StringBuilder();
        String scheme = request.getScheme();
        int port = request.getLocalPort();

        requestUrl.append(scheme);
        requestUrl.append("://");
        requestUrl.append(request.getServerName());

        if ((scheme.equals("http") && port != 80)
                || (scheme.equals("https") && port != 443)) {
            requestUrl.append(":");
            requestUrl.append(port);
        }

        requestUrl.append(request.getContextPath());
        requestUrl.append(request.getServletPath());

        return requestUrl.toString();
    }

    /**
     * Constructs and returns a List of OAuth.Parameter objects, one per
     * parameter in the passed request.
     *
     * @param  request Servlet request object with methods for retrieving the
     *         full set of parameters passed with the request
     */
    public static List getRequestParameters(HttpServletRequest request) {

        List parameters = new ArrayList();

        for (Object e : request.getParameterMap().entrySet()) {
            Map.Entry entry = (Map.Entry) e;

            for (String value : entry.getValue()) {
                parameters.add(new OAuth.Parameter(entry.getKey(), value));
            }
        }

        return parameters;
    }
}
結果

あなたがサーバコードに対してクライアントサイドのスクリプトを実行したとき、そしてリクエストが検証されたとき:

*** OAuthMessage Params:
URL: http://oauthtest.s42.eatj.com/oauth/SignedFetchVerifyServlet
Param Name-->oauth_consumer_key Value-->mixi.jp
Param Name-->oauth_nonce Value-->7fe2ce6b24e17c86
Param Name-->opensocial_app_id Value-->10449685582340194994
Param Name-->opensocial_viewer_id Value-->10972140642666904206
Param Name-->oauth_timestamp Value-->1211519778
Param Name-->opensocial_owner_id Value-->10972140642666904206
Param Name-->oauth_signature Value-->eYwX2yWLpOZ+gb5oEFeIy+EM3j237nrj/1rj3yp69jgo
                                       /QaPk/OnXbXP9imEcERxtQLj9QqDD8cjbjMJ46VINc7b
                                       Lab8qER1Xhkf4tTIkwfFXJW9tjQMBuGO8OVf9v0UAjAr
                                       uWfSN331LLgGFecKQR5UTD0qAAyzTnFY9aSmqg8=
Param Name-->xoauth_signature_publickey Value-->pub.1199819524.-1556113204990931254.cer
Param Name-->oauth_token Value-->
Param Name-->oauth_signature_method Value-->RSA-SHA1
VALIDATING SIGNATURE
REQUEST STATUS::OK

C#

サーバサイドコード

このコードは、オープンソースのOAuthライブラリプロジェクトのC#向けOAuthライブラリに依存しています。

この例は、プロダクション品質のコードではありません。ただ単に、あなたがサーバサイドで署名付きリクエストを検証することを試すための手順のデモに過ぎません。

// -----------------------------------------------------------------------------------
//
//  OBasePage.cs to validate oAuth signature - .NET
//  by XtremeHeights
//  Last Modification: 4th Oct 2008
//
//  For more information, visit:
//  http:/www.xtremeheights.com/
//  email: contact@xtremeheights.com
//
//
//  Thanks:
//  Khushal Patel for creating plug & plug code for implementing oAuth in .NET
//  Nemesh Singh for fixing the code which started spoofing every request after 25th sep 2008
//
// -----------------------------------------------------------------------------------

using System;
using System.Collections;
using System.Configuration;
using System.Web;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Collections.Specialized;
using OAuth;

public class oBasePage : System.Web.UI.Page
{
    HttpContext htp;
    public oBasePage(HttpContext _htp)
    {
        htp = _htp;
    }

    public bool isvalidrequest()
    {
        //If you want to import certificate file directly then uncomment the below line and comment the certificate
        //X509Certificate Cert = X509Certificate.CreateFromCertFile(htp.Request.PhysicalApplicationPath + "/bin/pub.1199819524.-1556113204990931254.cer");

        X509Certificate2 cert = new X509Certificate2();
        cert.Import(Encoding.ASCII.GetBytes(
            @"-----BEGIN CERTIFICATE-----
            MIICdzCCAeCgAwIBAgIJAOi/chE0MhufMA0GCSqGSIb3DQEBBQUAMDIxCzAJBgNV
            BAYTAkpQMREwDwYDVQQKEwhtaXhpIEluYzEQMA4GA1UEAxMHbWl4aS5qcDAeFw0w
            OTA0MjgwNzAyMTVaFw0xMDA0MjgwNzAyMTVaMDIxCzAJBgNVBAYTAkpQMREwDwYD
            VQQKEwhtaXhpIEluYzEQMA4GA1UEAxMHbWl4aS5qcDCBnzANBgkqhkiG9w0BAQEF
            AAOBjQAwgYkCgYEAwEj53VlQcv1WHvfWlTP+T1lXUg91W+bgJSuHAD89PdVf9Ujn
            i92EkbjqaLDzA43+U5ULlK/05jROnGwFBVdISxULgevSpiTfgbfCcKbRW7hXrTSm
            jFREp7YOvflT3rr7qqNvjm+3XE157zcU33SXMIGvX1uQH/Y4fNpEE1pmX+UCAwEA
            AaOBlDCBkTAdBgNVHQ4EFgQUn2ewbtnBTjv6CpeT37jrBNF/h6gwYgYDVR0jBFsw
            WYAUn2ewbtnBTjv6CpeT37jrBNF/h6ihNqQ0MDIxCzAJBgNVBAYTAkpQMREwDwYD
            VQQKEwhtaXhpIEluYzEQMA4GA1UEAxMHbWl4aS5qcIIJAOi/chE0MhufMAwGA1Ud
            EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAR7v8eaCaiB5xFVf9k9jOYPjCSQIJ
            58nLY869OeNXWWIQ17Tkprcf8ipxsoHj0Z7hJl/nVkSWgGj/bJLTVT9DrcEd6gLa
            H5TbGftATZCAJ8QJa3X2omCdB29qqyjz4F6QyTi930qekawPBLlWXuiP3oRNbiow
            nOLWEi16qH9WuBs=
            -----END CERTIFICATE-----"));

        //Getting Post variables
        string http_params;
        NameValueCollection _p = htp.Request.Form;
        Hashtable _hp = new Hashtable();

        foreach (string k in _p.Keys)
        {
            if (k != null)
            {
                _hp[k] = _p[k];
            }
        }

        SortedList _ps = new SortedList((IDictionary)_hp);

        string[] pairs1 = new string[_ps.Keys.Count];
        int l = 0;

        foreach (string name in _ps.Keys)
        {
            pairs1[l++] = name + "=" + Uri.EscapeDataString(_ps[name].ToString());
        }
        http_params = (String.Join("&", pairs1));

        if (_ps.Keys.Count != 0)
        {
            http_params = "&" + http_params;
        }

        /* RSACryptoServiceProvider Provider = CertUtil.GetCertPublicKey(Cert); // if importing the file directly... */
        RSACryptoServiceProvider Provider = (RSACryptoServiceProvider)cert.PublicKey.Key;
        OAuth.OAuthBase ba = new OAuthBase();

        string signature = (htp.Request.QueryString["oauth_signature"]);

        string baseString = ba.GenerateSignatureBase(htp.Request.Url,
            htp.Request.QueryString["oauth_consumer_key"],
            "",
            htp.Request.QueryString["oauth_token"],
            "",
            htp.Request.HttpMethod,
            htp.Request.QueryString["oauth_timestamp"],
            htp.Request.QueryString["oauth_nonce"],
            "RSA-SHA1",
            http_params);

        byte[] sign = Convert.FromBase64String(signature);
        byte[] bstring = Encoding.UTF8.GetBytes(baseString);
        htp.Response.Write(htp.Request.QueryString["oauth_token"]);

        return (Provider.VerifyData(bstring, "SHA1", sign));
    }
}

もしあなたがこのページに関して何かコメントもしくは質問をお持ちの場合は、OpenSocial Developerフォーラムにてディスカッションしてください。

TOP OF THIS PAGE