CORSへの対応
最終更新日: 2021年7月28日
R8 | R9
CORS(Cross-Origin Resource Sharing)とは、WebブラウザがHTMLを読み込んだ以外のサーバからデータを取得する仕組みです。
利用者のWebブラウザが、あるWebアプリケーションを使っているが、Wagbyアプリケーション(REST API提供)からAjaxでデータを取得したいという場合があります。このとき、WagbyアプリケーションをCORSに対応させる必要があります。
CORSはREST APIを提供しているサーバ(例 http://localhost:8921)が異なるドメインからの呼び出しについて、どのように対応するかをHTTPヘッダにて返す仕組みです。詳細は下記をご覧ください。
IE10以上、Google Chrome、Firefoxなどが対応しています。詳細は下記URLをご覧下さい。
IE9はPartially supportedとなっていますが、withCredentialsの指定によるCookieの扱いに対応していないため、未対応となります。
3rd party cookieを許可してください。
適切な設定を行わない場合、JavaScriptエラーが発生するなどで動作しません。
Wagby に同梱されている Spring Security の CORS 設定を有効にします。
myapplication.properties に次のように記載します。アクセスを許可する URL を指定します。
変更したファイルを customize/resources フォルダに保存します。ビルド時にこの内容が含まれます。
URLの末尾に '/' はつけないようにしてください。上の例では http://example01.com/ と記述しないということです。
JavaScriptコードにてAjax対応コードを作成します。XMLHttpRequest オブジェクトを用意します。
このオブジェクトに対して、クッキーを用いることを指定します。
その後、通信を行います。
CORS の動作を確認するテストコードを用意しました。
1. customize/resource/myapplication.properties を用意します。次の内容とします。
CORSとは
技術の詳細
対応ブラウザ
稼働するブラウザ
ブラウザへの設定
設定ファイル
設定方法
wagby.security.corsAllowedOrigins[0]=http://example01.com
wagby.security.corsAllowedOrigins[1]=http://example02.com
...
注意
XMLHttpRequestオブジェクトの操作
httpObj = new XMLHttpRequest();
httpObj.withCredentials = true;
httpObj.send(/*パラメータ*/);
簡易テスト
wagby.security.corsAllowedOrigins[0]=http://localhost:18921
2. プロジェクト識別子 wagby (デフォルト) でビルドします。
3. ビルドされた wagbyapp をコピーした、wagbyapp1 を用意します。この時点で二つのアプリケーション wagbyapp, wagbyapp1 が存在します。
4. wagbyapp1/conf/server.xml を手動で編集します。変更する場所は以下のとおりで、ポート番号に 10000 を加算したものとなります。
...
<Server port="18005" shutdown="SHUTDOWN">
...
<Connector port="18921" protocol="HTTP/1.1"
...
enableLookups="false" redirectPort="18443" acceptCount="100"
...
<Connector port="18009" protocol="AJP/1.3" redirectPort="18443"
...
5. wagbyapp1/webapps/wagby/に test.html (後述) をコピーします。この HTML に書かれている JavaScript では、http://localhost:8921にログオンし、jnewsの一覧を取得するREST APIを呼び出すようになっています。
6. wagbyapp, wagbyapp1を起動する。ポート番号を書き換えたため、二つのアプリケーションを起動することができます。(ここで起動エラーとなった場合、ポート番号が重複しています。上の設定ファイルを見直してください。)
7. http://localhost:18921/wagby/ にアクセスし、admin にログオンします。つまり wagbyapp1 にログオンした状態とします。ログオン後、メニュー画面が表示されることを確認します。
8. 同じブラウザ(Google Chrome)を使って http://localhost:18921/wagby/test.html を開きます。
9. アクセスした画面で Chrome のデベロッパーツールを開き、Networkタブを開きます。
10. test.htmlの送信ボタンを押します。成功すると、取得されたJSONオブジェクトのテキストが画面に表示されます。
また、デベロッパーツールで、CORSフィルタに対応する Access-Control-... のレスポンスヘッダを確認することができます。
"Failed to load http://localhost:8921/wagby/rest/session: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:18921' is therefore not allowed access."
test.html
ここで用いた test.html は次のとおりです。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Wagby get jnews rest test</title>
<script type="text/javascript" src="system/common.js" charset="UTF-8"></script>
<script type="text/javascript">
function createXMLHttpRequest() {
var httpObj;
try {
if(window.XMLHttpRequest) {
httpObj = new XMLHttpRequest();
} else if(window.ActiveXObject) {
httpObj = new ActiveXObject("Microsoft.XMLHTTP");
} else {
httpObj = false;
}
} catch(e) {
httpObj = false;
}
return httpObj;
}
function httpXMLRequestText(
httpObj, target_url, param, method, functionReference, failedFunctionReference)
{
// タイマーが動作中の場合は、タイマーをストップする。
if (timerId != '') {
clearInterval(timerId);
timerId = '';
}
// HTTPリクエストを送信中の場合は、HTTPリクエストを中断する
if (httpObj != false) {
httpObj.abort();
httpObj = false;
}
// 処理失敗時の関数参照を格納する
failedFuncRef = failedFunctionReference;
// httpObjの作成
try {
if(window.XMLHttpRequest) {
httpObj = new XMLHttpRequest();
} else if(window.ActiveXObject) {
httpObj = new ActiveXObject("Microsoft.XMLHTTP");
} else {
httpObj = false;
}
} catch(e) {
httpObj = false;
}
if(! httpObj) {
// httpObjの作成に失敗した場合
alert('not supported your web browser!!');
failedFuncRef();
return false;
}
// タイマーをセット
timerTimeoutSec = httpXMLRequest_DefaultTimerTimeoutSec;
timerBeginTime = new Date().getTime();
timerId = setInterval('timeoutCheck()', 1000);
// HTTPリクエストを送信
httpObj.open(method, target_url, true);
httpObj.onreadystatechange = function() {
if (typeof(httpObj) != 'undefined' && httpObj.readyState == 4) {
// タイマーをストップする
if (timerId != '') {
clearInterval(timerId);
timerId = '';
}
var timerEndTime = new Date().getTime();
if (httpObj.status == 200) {
// リクエストの受信に成功した場合
functionReference(httpObj);
if (httpXMLRequest_IsShowProcessTime) {
alert('process time '+(timerEndTime-timerBeginTime)+'ms');
}
httpObj = false;
} else {
// リクエストの受信に失敗した場合
failedFuncRef();
var alertmsg = 'httpXMLRequest '+httpObj.status + ' : ' + httpObj.statusText;
if (httpXMLRequest_IsShowProcessTime) {
alertmsg =
alertmsg + '\n' +
'process time '+(timerEndTime-timerBeginTime)+'ms';
}
window.status = alertmsg;
httpObj = false;
return false;
}
}
}
if (param != '') {
httpObj.setRequestHeader(
'Content-Type', 'application/x-www-form-urlencoded');
}
httpObj.withCredentials = true;
httpObj.send(param);
return true;
}
function clear(elem) {
var childs = elem.childNodes;
for (i=0;i<childs.length; i++) {
elem.removeChild(childs[i]);
}
}
function failedCall(httpObj) {
alert("failed call "+httpObj.status + ' : ' + httpObj.statusText);
}
function clearAll() {
var jsonstrelem = document.getElementById("jsonstr");
clear(jsonstrelem);
var elem = document.getElementById("getjnews_return");
clear(elem);
}
function appendResponseTable1(jsData, elem) {
var jsonstrelem = document.getElementById("jsonstr");
clear(jsonstrelem);
jsonstrelem.appendChild(document.createTextNode(jsData));
clear(elem);
elem.appendChild(document.createTextNode(jsData));
}
</script>
</head>
<body>
<p>
<input type="button" value="クリア" onclick="clearAll()"/><br>
JSON文字列:
<input type="button" value="表示" onclick="document.getElementById('jsonstr').style.display='block'"/>
<input type="button" value="非表示" onclick="document.getElementById('jsonstr').style.display='none'"/><br>
<div id="jsonstr" style="display:none">なし</div>
</p>
<script language="JavaScript">
function callGetJnews() {
var target_url = "http://localhost:8921/wagby/rest/session";
// R7
/*
var param = "user=admin&pass=admin";
httpXMLRequestText(
createXMLHttpRequest(),
target_url, param, 'PUT', successCallLogon,
failedCall);
*/
// R8
var param = "user=admin&pass=wagby";
httpXMLRequestText(
createXMLHttpRequest(),
target_url, param, 'POST', successCallLogon,
failedCall);
}
function successCallLogon(httpObj) {
var target_url = "http://localhost:8921/wagby/rest/jnews/list";
var param = "";
httpXMLRequestText(
createXMLHttpRequest(),
target_url, param, 'GET', successCallGetJnews,
failedCall);
}
function successCallGetJnews(httpObj) {
var elem = document.getElementById("getjnews_return");
clear(elem);
appendResponseTable1(httpObj.responseText, elem);
}
</script><p>
GET jnews/list:
返り値:<div id="getjnews_return">なし</div>
<input type="button" value="送信" onclick="callGetJnews()"/>
</p>
</body>
</html>
CSRFへの対応
REST API では、上記の CORS 対応を行うことで CSRF 対策になります。
なお、CORSで許可したサイトの方にCSRFの問題があった場合には、Wagbyで防ぐことはできません。当該サイトにて個別に対応を行うようにしてください。