Javascriptソースコードを見られないように隠蔽してみた。


#はじめに

javascriptはいやおうなしに見られてしまうので、ソース隠せないかな、と考えて色々調べてみた結果、ここに行き着いた。
ここの方法は一般に「API」と呼ばれているので、もっと気になる方はそこを探すといいかも、

現状のままではPHPにJsソースを書くことになる欠点があるので、
これの対策verも~~そのうち実装します。~~しました。[実用的なカスタマイズ](https://qiita.com/yomogenium/items/881a55417dc047756e78#%E5%AE%9F%E7%94%A8%E7%9A%84%E3%81%AA%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA)、[Github](https://github.com/Yomogenium/SourceHide-lite)に書いてます。

なお今回はPHPを使って書いているが、ここはGo, Perl, Ruby, Pythonなどの別言語でも代替可。
RubyのSinatra、Wordpress構築済のPHPあたりが環境構築楽かな？


**2021/11/25追記：**
大変残念なことだが、**この方法を使っても見ようと思えば見れてしまうことが判明。**
「閲覧方法」で後述する。
結論から言って、マジで見てほしくない場合はもうPHP側であらかじめ計算したものを送信するしかない模様。というか大事な情報をそもそもJsに書くな。
それでも、相当ITに精通した人間でないと思いつけない手法ではあるので、一般人に見れなければOKというならここの方法を用いてもいいかな？


#どうやって実装するの？

まずもって、ユーザーがJavascriptのソースコードをのぞき見する主な手段は以下の２パターンある。

- URLバーに該当するJsのURLを直接打ち込んで、ページとして見る
- 右クリック→検証→左メニューの「Sources」から閲覧する

逆に言えば、今回やるべきはこの２つの手段を両方とも潰すことである。
それを踏まえて、本システムの要件は以下のとおりとする。

###要件
- **① 直接アクセスした場合にはエラーorニセのコードを出力し、ソースコードを見られないようにすること**
- **② 右クリック→検証→左メニューの「Sources」を使ってもソースコードが見られないようにすること**
- **③ あらかじめ決めておいたサイト以外からは呼び出せないように設定する**（場合によっては必要になる。理由は後述）


#Step1 直接アクセスの阻止

まず直接URLを指定してページを覗かれた場合だが、これは単純なJavascriptだけでは阻止しきれない。
なぜならJavascriptがクライアントサイドで動く言語だからだ。


![pic1-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416995/a417cdac-dbbe-77cd-3696-0b2220980283.png)
（※拾い画）

この図はウェブサイトが開かれ、見られるようになるまでの一連の流れ。

javascriptはまずコード全体が閲覧者の端末に送信され、閲覧者の端末の中（図で言うと⑤）で処理が実行される。見せたい見せたくないに関わらずページを開いた瞬間にコードを渡してしまうので、そのあとから隠蔽するのは不可能なのだ。

ならばどうするかというと、「サーバーサイド言語」というやつを経由させる。
サーバーサイド言語は図で言うと③で実行される言語で、サーバーマシンの中で処理を行ってからその結果だけを返すようになっている。
ということは、このサーバーサイド言語を噛ませて「条件を満たさなければJsソースを端末に送信しない」という処理を組み込んでやればよい。

今回はPHPを利用してこの処理を実装する。
サーバー内で処理できればそれでいいので、処理自体はPythonでもRubyでもNode.jsでも実装できる……はず。

とりあえず、以下のようにソースを書いてみた。

```php:source.php
<?php

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
	/* 本命のソース
	   ※見せたくないソースをここに記載します */
	echo "本命のソース！";

}else{
	/* 直接アクセスされた場合のダミー記述 */
	echo 'アクセス……拒否しますっ！！(>_<)';
}

?>
```

直接アクセスかどうかのチェックには、リクエストメソッドの状態を取得する`$_SERVER["REQUEST_METHOD"]`を使う。
この値は通常GETになっていて、後述するような特殊なアクセスを行った場合のみPOSTになる。
直接アクセスの場合はもちろんGET。

#Step1 隠したいJavascriptコードの用意

隠したいJsソースを用意する。

```javascript:javascript

function HideSource(){
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    var random;
    setInterval(function(){
        random = 'rgb(' + (~~(256 * Math.random())) + ', ' + (~~(256 * Math.random())) + ', ' + (~~(256 * Math.random())) + ')' ;
        block.style.background = random;
    }, 1000);
    document.body.appendChild(block);
};

```

このコードは後述の`XMLHttpRequest`で読み込み、そのあとに削除する。
そのため、ここに記載するコードはオブジェクト化しておく必要がある。
平たく言うと一つの`function`内にまとめておかなくてはならない。

で、これをStep1のPHPと組み合わせた結果が以下のとおり。

```php:source.php
<?php

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
	/* 本命のソース
	   ※見せたくないソースをここに記載します */
	echo "function HideSource(){
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    var random;
    setInterval(function(){
        random = 'rgb(' + (~~(256 * Math.random())) + ', ' + (~~(256 * Math.random())) + ', ' + (~~(256 * Math.random())) + ')' ;
        block.style.background = random;
    }, 1000);
    document.body.appendChild(block);
};";

}else{
	/* 直接アクセスされた場合のダミー記述 */
	echo 'アクセス……拒否しますっ！！(>_<)';
}

?>
```

これで、要件①はクリア。
直接URLからページを見てもコードは読めなくなった。


#Step3 XMLHttpRequestでの呼び出し

次に呼び出し処理を作成する。
呼び出しには、javascriptの`XMLHttpRequest`を用いる。

そのままコピペできる内容なので、とりあえずはコードを記載する。

```javascript:javascript

// 実行
ReadSource(document.body, './source.php');

function ReadSource(target, urls){

	// 宣言
	let request = new XMLHttpRequest();

	// リクエストURLを設定（リクエストタイプはPOSTにすること）
	request.open('POST', urls);

	// リクエスト先からレスポンス（返答）があった場合の処理
	request.onreadystatechange = function(){
		if ( (request.readyState == 4) && (request.status == 200) ){

			let scrpt = document.createElement("script");
			scrpt.type = 'text/javascript';
			scrpt.innerHTML = request.responseText;
			target.appendChild(scrpt);

			// 消去
			target.removeChild(scrpt);

			// 処理 
			HideSource();
		}
	}
	request.send();
}
```

`<script>`リンクでファイルを読むのではなく、
外部から読み込んだテキストをhtml上に貼り付け、それをJsとして解釈させているから検証メニューにも載ることはない。
代わりにHTMLソース上に関数`HideSource`全文が載ってしまうので、それは削除する必要がある。

挙動を見る限り、ソースコードを`appendChild`した時点で関数全体がどこかに登録される模様。
なので、一度appendChildでhtml上に追加してしまえば消しても大丈夫。

このコードでは`HideSource`の実行が行われるより先にソースコードを消してしまっているが、
その場合でも`ReadSource`内で`HideSource`を実行しておけば問題なく動作する。

で、上記のコードを実際にページに記述してみよう。

```html:index.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8"/>
	<title>たいとる</title>
</head>
<body>

<div id="point"></div>

<script type="text/javascript">
function ReadSource(target, urls) {
	// 宣言
	let request = new XMLHttpRequest();

	// リクエストURLを設定（リクエストタイプはPOSTにすること）
	request.open('POST', urls);

	// リクエスト先からレスポンス（返答）があった場合の処理
	request.onreadystatechange = function(){
		if ( (request.readyState == 4) && (request.status == 200) ){

			let scrpt = document.createElement("script");
			scrpt.type = 'text/javascript';
			scrpt.innerHTML = request.responseText;
			target.appendChild(scrpt);

			// 消去
			target.removeChild(scrpt);

			// 処理 
			HideSource();
		}
	}
	request.send();
}
// 実行
ReadSource(document.getElementById('point'), './source.php');
</script>
</body>
</html>
```

要件②もクリア。
これで、ソースである`source.php`を見せないようにしつつ、秘匿したJsを実行できる。やったね。


※完成形のコードは[Github](https://github.com/Yomogenium/SourceHide-lite)に置いています。
「Clone or download」→「Download ZIP」でダウンロードできます。
PHPを含むので、PHPが動く実行環境が必要というのは言わずもがな。



##おまけ `XMLHttpRequest`とCORS

残るは要件③「あらかじめ決めておいたサイト以外からは呼び出せないように設定する」だが、
これ実は自動的にクリアしている。

今回使った`XMLHttpRequest`だが、こいつには**CORS**というルールで制限がかけられている。
詳しい説明は[こちらの記事](https://qiita.com/att55/items/2154a8aad8bf1409db2b#%E6%A6%82%E8%A6%81)に譲るが……すごーーく雑な説明をすると、

「**呼び出し元と呼び出し先のサイトが別々だったときは呼び出される側に『このサイトはオレを呼び出していいぞ！』という許可証をつけておいてくださいね。許可証なしで他人のサイトからファイル持ってこようとするなら接続させませんよ**」[^e1]

ということ。
今回は呼び出し元（index.html）と呼び出し先（source.php）を同じウェブサイトに同梱しているのでこの制限にはあたらない。

が、もし仮に別サイトから呼び出すことを考慮する[^e2]場合、`source.php`の文頭に`header()`を追記して以下のように変更しよう。

```php:source.php
<?php

header('Access-Control-Allow-Origin: https://example.com'); // この部分を追記。 https://example.comのところは呼び出し元のドメインを指定。

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
	/* 本命のソース
	   ※見せたくないソースをここに記載します */
	echo "function HideSource(){
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';
    var random;
    setInterval(function(){
        random = 'rgb(' + (~~(256 * Math.random())) + ', ' + (~~(256 * Math.random())) + ', ' + (~~(256 * Math.random())) + ')' ;
        block.style.background = random;
    }, 1000);
    document.body.appendChild(block);
};";

}else{
	/* 直接アクセスされた場合のダミー記述 */
	echo 'アクセス……拒否しますっ！！(>_<)';
}

?>
```


#実用的なカスタマイズ

JsはそのままJsに書いていたい！という場合、
サーバー自体のアクセス制限を操作して、当該Jsへのアクセスを防ぐ手法が必用になる。
今回用いているのはApacheだが、この辺はつかっているサーバーによって多少変化するだろう。

<details>
<summary>コード全文</summary><div>

```
ファイル階層構造

(ページを置く階層)/
  ┣━index.html  // ←表示するページ
  ┗━js
  　 ┣━.htaccess
  　 ┣━call.php
  　 ┗━source.js // ←隠したいJavascriptファイル

```


```html:index.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8"/>
	<title>たいとる</title>
</head>
<body>

<div id="point"></div>

<script type="text/javascript">
function ReadSource(target, filename) {
	// 宣言
	let request = new XMLHttpRequest();

	// リクエストURLを設定（リクエストタイプはPOSTにすること）
	request.open('POST', './js/call.php');

	request.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );

	// リクエスト先からレスポンス（返答）があった場合の処理
	request.onreadystatechange = function(){
		if ( (request.readyState == 4) && (request.status == 200) ){

			let scrpt = document.createElement("script");
			scrpt.type = 'text/javascript';
			scrpt.innerHTML = request.responseText;

			target.appendChild(scrpt);

			// 消去
			target.removeChild(scrpt);
		}
	}
	request.send('flname=' + filename);
}
// 実行
ReadSource(document.getElementById('point'), 'source.js');
</script>
</body>
</html>
```


```php:call.php
<?php
header('Access-Control-Allow-Origin: https://example.com'); // この部分を追記。 https://example.comのところは呼び出し元のドメインを指定。

/* 直接アクセス禁止設定 */
if($_SERVER["REQUEST_METHOD"] === 'POST'){
    /* 本命のソース
       ※見せたくないソースをここに記載します */

    $target_name = $_POST['flname'];
    $pass = dirname(__FILE__).'/'; // ←ファイルまでのパスを指定
    print(file_get_contents($pass.$target_name));

}else{
    /* 直接アクセスされた場合のダミー記述 */
    echo 'ERROR';
} ?>
```

```javascript:source.js
HideSource();

function HideSource(){

    /* ここから処理を記載する */
    let block = document.createElement('div');
    block.style.height = '40px';
    block.style.width = '40px';
    block.style.background = '#00b5ad';
    block.style.borderRadius = '5px';
    block.style.position = 'fixed';
    block.style.top = '50%';
    block.style.left = '50%';

    var random;
    setInterval(function(){
        random = "rgb(" + (~~(256 * Math.random())) + ", " + (~~(256 * Math.random())) + ", " + (~~(256 * Math.random())) + ")" ;
        block.style.background = random;
    }, 1000);

    document.body.appendChild(block);

    /* ここまで */
};
```

```htaccess:.htaccess
<Files ~ "\.js$">
deny from all
</Files>
```


</div></details>



###閲覧方法

<details>
<summary>ネタバレ防止の為折り畳み</summary><div>

※Chromeの場合

- F12でデベロッパーツールを立ち上げる
- 上のタブの中にある「ネットワーク」タブに移動する
- 通信の一覧がリストになって載っているので、その中で該当のアクセスを行っているものをクリック（今回でいうところのsource.php）

</div></details>



#参考文献
http://pc110.club/php/%EF%BD%8Aavascript-hidden
http://labs.vividworks.jp/javascript-%E3%81%AE%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%AF%E9%9A%A0%E8%94%BD%E3%81%99%E3%82%8B%E3%81%B9%E3%81%8D%E3%81%8B%EF%BC%9F-vol-4/
https://tenderfeel.xsrv.jp/mootools/705/



[^e1]: 正確には「オリジン」というやつをを比較する。わからなければ、ドメインと同じものだと思っておけばいい。
[^e2]: 同じサーバーであっても、別のサブドメインから呼び出したいときには必要だ