- 投稿日:2020-05-30T23:35:16+09:00
予測変換を非表示にする
- 投稿日:2020-05-30T20:25:46+09:00
Djangoを用いてhtmlからPythonファイルを実行する
ここで書くこと
この記事では「Djangoを用いてWebページからサーバ上に用意したPythonファイルを実行し、htmlから渡したデータをcsvファイルに出力する方法」について書いています。
また、執筆環境は
OS:macOS Catalina バージョン 10.15.4
Python:3.7.6
Django:3.0.3
となっています。Djangoについて
前回の記事にてDjangoのインストール→htmlの表示までの流れについて書かせていただきましたので、Djangoの導入等についてはそちらをご参考ください。
Pythonファイルの準備
Pythonファイルの作成
まず、実行したいPythonファイルをDjangoのサーバ上に用意します。説明のために今回は以下のようなPythonファイルを用意します。
write_data.py# coding:utf-8 import os import csv # htmlからのデータをcsvファイルに記録 def write_csv(data): datas = [data] with open(os.getcwd()+'/myapp/application/'+'data.csv','a') as f: writer = csv.writer(f, lineterminator='\n') writer.writerow(datas)これにより、write_csv()の引数にデータを渡して呼び出すことでdata.csvにそのデータが書き込まれます。なおここでは
<アプリ名>/application
フォルダ内にPythonファイルを配置&csvファイルを出力することを想定しているためos.getcwd()+'/myapp/application/'+'data.csv'
としていますが、この部分は環境に応じて適宜読み替えてください。Pythonファイルの配置
用意したPythonファイルをDjangoのサーバ上に配置します。Pythonファイルを
<アプリ名>/application
フォルダ内に置く場合、アプリ内のディレクトリは以下のようになるかと思います。<プロジェクト名> - db.sqlite3 - manage.py - <プロジェクト名> - __init__.py - asgi.py - settings.py - urls.py - wsgi.py - __pycashe__ - (.pycファイルが複数) - <アプリ名> - __init__.py - admin.py - apps.py - models.py - tests.py - urls.py - views.py - migrations - __init__.py - application # 作成したフォルダ - write_data.py # 用意したPythonファイル - templates - staticもちろん、ここに置かないといけないという訳ではないのでファイルの配置場所は任意の場所で大丈夫です。
htmlからPythonファイルを実行できるようにする
ファイルの用意と配置が完了しましたら、実際にhtmlからそのPythonファイルを実行させてcsvファイルを作成してみます。その前に、データの流れがどのようになっているかを把握してから実装した方が作業しやすいと思いますので、今回想定するデータの流れを書いてみたいと思います。
パワポで作ってみました、ガサツな図で申し訳ありません、、
簡略化の為にプログラムと異なる部分も多々ありますが、csvファイルへの書き込みまでのデータの流れとしてはこんな感じです。何となくでもイメージが伝われば嬉しいです。html(index.html)
→myapp/views.pyのcall_write_data()にデータを送信
→call_write_data()内でapplication/write_data.pyのwrite_csv()メソッドを実行
→それにより渡したデータがcsvファイルに書き込まれる言葉で表すとこんな感じです。これを踏まえ、次から実際にhtmlからPythonファイルを実行できるよう各ファイルを編集していきます。
html:ajaxを用いてviews.pyにデータを送信する
views.py内のcall_write_data()メソッドにデータを渡す為に、html上でajaxを用いてデータを送信してみたいと思います。ここに関しては色々な方法があるかと思いますので、アプリケーションに合う方法を用いていただければと思います。
index.html{% load static %} <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>HTML</title> <link rel='stylesheet' type='text/css' href="{% static 'style.css' %}"/> <script type="text/javascript" src="{% static 'script.js' %}"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> </head> <body> <span>文字を入力した後にボタンを押してください</span> <br> <input type="text" size="50" id="input_form"> <button type="button" onclick="clickBtn()">送信</button> <script> function clickBtn() { var txt = document.getElementById("input_form").value; $.ajax({ url: "{% url 'myapp:call_write_data' %}", method: 'GET', data: {"input_data": txt}, dataType: "text", contentType: "application/json", beforeSend: function(xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrf_token); } }, error: function(xhr, status, error) { console.log("error") } }) .done(function(data) { console.log("Success"); }); // csrf_tokenの取得に使う function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } // ヘッダにcsrf_tokenを付与する関数 function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); }; } </script> </body> </html>送信ボタンを押すとclickBtn()が実行され、ajaxによりデータが送信されるようになっています。urlの部分は
myapp: call_write_data
となっており、これによりviews.pyに記述したcall_write_data()というメソッドにデータが送信されます。データ部分は
data: {"input_data": txt}
となっており、データを受け取る側では"input_data"と指定することで目的のデータを取得することができます。ここでは一つだけにしていますが、data: {"data1": txt1, "data2": txt2}
のようにデータの個数や型などデータの形式については自由に設定できます。myapp/views.py:実行したいPythonファイルとメソッドを指定して実行する
myapp/views.pyfrom django.shortcuts import render from django.http import HttpResponse # application/write_data.pyをインポートする from .application import write_data # Create your views here. def index(req): return render(req, 'index.html') # ajaxでurl指定したメソッド def call_write_data(req): if req.method == 'GET': # write_data.pyのwrite_csv()メソッドを呼び出す。 # ajaxで送信したデータのうち"input_data"を指定して取得する。 write_data.write_csv(req.GET.get("input_data")) return HttpResponse()ここではajaxで送信されたデータを取得し、実行したいPythonファイルのメソッドにそのデータを渡して呼び出しています。
myapp/urls.py:htmlからviews.pyのcall_write_data()にデータを送信できるようにする
myapp/urls.pyfrom django.urls import path from . import views app_name = 'myapp' urlpatterns = [ path(r'', views.index, name='index'), # 以下を追記(views.pyのcall_write_data()にデータを送信できるようにする) path("ajax/", views.call_write_data, name="call_write_data"), ]パスを通すことで、html上からajax通信を用いてviews.pyの指定したメソッドにデータを送信できるようになります。
実際にcsvファイルに書き込まれるかを確認する
以上で必要な編集は完了です。
$ python manage.py runserverでサーバを立ち上げ、表示されたアドレスにアクセス(htmlを表示)し入力フォームに適当な文字を入力してから送信ボタンを押してみてください。
- <アプリ名> - __init__.py ... - application - write_data.py - data.csv # 生成されたcsvファイルこのようにアプリ内に作成したapplicationフォルダの中にcsvファイルが生成され、入力した文字列がファイルに記録されていれば問題なくデータが送信されている&Pythonファイルが実行されています。
補足
今回はhtmlからPythonファイルを実行し、送信されたデータをcsvファイルに書き込む方法についてご説明しましたがその逆も可能です。
説明の簡略化の為に、”write_data.pyから渡されたデータをviews.pyで取得しそれをhtmlに渡して表示する”ことを行ってみたいと思います。変更箇所のあるファイルだけ以下に載せていきます。
myapp/application/write_data.py
return_text()というメソッドを追記する。
myapp/application/write_data.py# coding:utf-8 import os import csv # htmlからのデータをcsvファイルに記録 def write_csv(data): datas = [data] with open(os.getcwd()+'/myapp/application/'+'data.csv','a') as f: writer = csv.writer(f, lineterminator='\n') writer.writerow(datas) # 以下を追記(return_text()を呼び出すと"Hello!!"が返される) + def return_text(): + return "Hello!!"
myapp/views.py
write_data.pyにて追記したreturn_text()を呼び出し、返ってきた文字列を取得する(dataに格納する)。そのデータを
HttpResponse()
を用いてhtmlに渡す。myapp/views.pyfrom django.shortcuts import render from django.http import HttpResponse # application/write_data.pyをインポートする from .application import write_data # Create your views here. def index(req): return render(req, 'index.html') # ajaxでurl指定したメソッド def call_write_data(req): if req.method == 'GET': # write_data.pyのwrite_csvメソッドを呼び出す。 # ajaxで送信したデータのうち"input_data"を指定して取得する。 write_data.write_csv(req.GET.get("input_data")) # write_data.pyの中に新たに記述したメソッド(return_text())を呼び出す。 + data = write_data.return_text() # 受け取ったデータをhtmlに渡す。 + return HttpResponse(data)index.html
ajax通信が成功するとHttpResponse()で渡した引数が
.done(function(data) {
の部分に渡されるので、そのデータをページに表示する。index.html{% load static %} <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>HTML</title> <link rel='stylesheet' type='text/css' href="{% static 'style.css' %}"/> <script type="text/javascript" src="{% static 'script.js' %}"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> </head> <body> <span>文字を入力した後にボタンを押してください</span> <br> <input type="text" size="50" id="input_form"> <button type="button" onclick="clickBtn()">送信</button> <!-- views.pyから渡された文字列を表示する。 --> + <br> + <span id="text"></span> <script> function clickBtn() { var txt = document.getElementById("input_form").value; $.ajax({ url: "{% url 'myapp:call_write_data' %}", method: 'GET', data: {"input_data": txt}, dataType: "text", contentType: "application/json", beforeSend: function(xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrf_token); } }, error: function(xhr, status, error) { console.log("error") } }) .done(function(data) { // views.pyのcall_write_data()にてreturnしたHttpResponse(data)のデータはここで取得できる。 // フォームの下部に追記したspan部分の内容を書き換える。 + document.getElementById("text").textContent = data; console.log("Success"); }); // csrf_tokenの取得に使う function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } // ヘッダにcsrf_tokenを付与する関数 function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); }; } </script> </body> </html>これにより、送信ボタンを押して正しくPythonファイルが実行されると"Hello!!"という文字列がhtmlに渡され、入力フォームの下の部分に渡されたHello!!という文字列が表示されると思います。
これを応用することで、サーバ上のPythonファイルを実行しサーバ上のファイルのデータを読み書きしたり、そのデータをhtml上に反映させることが可能になります。
- 投稿日:2020-05-30T18:07:41+09:00
PHPと Vue.jsで簡易掲示板を作ろう〜その1(PHP編)〜
初めに
3つのパートに分けて
1.PHPでとりあえず動く掲示板を作る←今回はここ
2.SCSSで簡単なデザインを作る
3.Vue.jsで少しリッチな動きをつける目次
・誰向けですか?
・このパートでの完成イメージとデモサイト
・PHPの記述と解説
・HTMLの記述と解説
・*の解説
・まとめ誰向けですか?
・PHP初心者
・Vue.js初心者
・PHPで特に作りたいものがない人このパートでの完成イメージとデモサイト
デモサイト
簡易掲示板へようこそ(XSS対策済み)〜完成イメージ〜
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
このようにクロスサイトスクリプティング(XSS)の対策をしています。PHPの記述と解説
index.php<?php // htmlspecialchars()でHTMLで本来直接書くことのできない<>などの記号を書けるようにする // メッセージ欄にscriptを記述する輩への対策(XSSの防止)*1 function h($v) { return htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); } $FILE = 'all_data.text'; // DBを使わずファイルに保存する $user_id = uniqid(rand().'_'); // IDを作る(ランダムな数値_13文字の文字列)が出力される*2 date_default_timezone_set('Asia/Tokyo'); // ↓date()でどの地域の時間を使用するか決める $date = date('Y年/m月/d日/H:i'); // date(Y,m,d,H,i)に↑で設定した時間が入る*3 $input_text = ''; // 入力するテキストを変数として代入 $post_data = []; // 一回の投稿のデータを入れる,($user_id, $date, $input_text)を入れる $all_data = []; // 全ての投稿のデータを入れる // file_exists(ファイル名)でこのファイルが存在するか調べる if(file_exists($FILE)) { //json_decode()でJSON形式のデータを配列に戻す //file_get_contents()で$FILEを読み込む $all_data = json_decode(file_get_contents($FILE)); } // $_SERVERにはHTTPサーバやHTTP通信に関わる情報が格納されている // $_SERVER['REQUEST_METHOD']でブラウザからのリクエストがGETかPOSTか判別する*4 // もしPOSTのリクエストがあったら if($_SERVER['REQUEST_METHOD'] === 'POST') { // リクエストを受け取った時textが空ではなかったら(送信ボタンが押された時) if(!empty($_POST['text'])) { // $post_textに投稿されたテキストを代入 $input_text = $_POST['text']; // 新しく投稿されたデータ(データを配列にして入れる)HTMLで表示する時に使う $post_data = [$user_id, $date, $input_text]; // 投稿をファイルに保存する $all_data[] = $post_data; // ファイルに保存する file_put_contents($FILE, json_encode($all_data)); } //header() //$_SERVER['SCRIPT_NAME']は現在のスクリプトのパス(簡単に言うと今いる場所のURL) header('Location:' . $_SERVER['SCRIPT_NAME']); //プログラム終了(exitなかったら後に書かれた処理も実行してしまうから) exit; } ?>HTMLの記述と解説
HTMLの記述は同じファイルでも分けても好みでOK(今回は続けて同じファイルに記述します)
index.php<div class="container"> <h1>簡易掲示板へようこそ(XSS対策済み)</h1> <!-- 投稿ボタン --> <form method="post"><!-- 中の投稿ボタンが押されたらPOSTのリクエストを送る --> <input type="text" name="text"> <input type="submit" value="投稿する"> </form> <table> <?php foreach((array)$all_data as $post_data) : ?> <tr> <form method="post"> <td> <!-- $post_data[2]は$post_text(テキストデータ) --> <?php echo h($post_data[2]); ?> </td> <td> <!-- $post_data[1]は$date(日付) --> <?php echo $post_data[1]; ?> </td> </form> </tr> <?php endforeach; ?> </table> </div>~*の解説~
<!-- *1ENT_QUOTES:["],['],[<],[>]が普通の文字として出力される *2uniqid()だけでは同じタイミングで実行すると同じ値が生成されるのでrand()を入れることで回避 ↑マイクロ秒で同じでないとかぶらないので念のためである *3公式マニュアルにdate()の日付文字列の書式が解説されている(分かりやすい)https://www.php.net/manual/ja/function.date.php *4 GET:何か情報を検索したり取得するために使うためのメソッド POST:登録処理や更新処理などの、書き込みがありリソースが更新される可能性のある処理に対して使うメソッド もっと詳しく分かりやすい解説記事:https://qiita.com/kanataxa/items/522efb74421255f0e0a1 -->
まとめ
今回のパートではとりあえず動く掲示板を作成しました!
PHP学習してまだ日が浅いので間違いがあるかもしれません。その時はお教えください?♂️
メソッドやプロパティを一つ一つ調べながら学習するといいことが今回分かりました!
この記事を見てくださったあなたの成長を応援します!!
ーーーー
次のパート:PHPと Vue.jsで簡易掲示板を作ろう〜その2( SCSS編)〜この記事もいかがですか?
初心者に捧げるハンバーガーメニューの作り方
初心者に捧げるヘッダーの作り方
初心者に捧げる〜PHPを使って九九の表を作ろう〜
- 投稿日:2020-05-30T17:10:41+09:00
マークアップ共通化:アクション毎にurlパスの変更
概要
前回の記事で、マークアップの共通化を掲載しました
コチラ↓↓
https://qiita.com/kazuko___3o3___/items/019174474f4f258ba19b実は、この記事がメインでございましたっ
newアクション、editアクション毎に指定したいURLが異なり、最初はif文でコードを書くもエラー続きでした
その解消法を備忘録として残します!事象
以前、【form_forが自動的に生成してくれるパスは複数形のみ】という記事を書かせていただきました(https://qiita.com/kazuko___3o3___/items/cf8e6966772d629d5927 )
そのため、HTMLは下記のように記載されています。
_form.html.haml#省略 .new_display = form_for @task, url: group_tasks_path do |f| #省略【group_tasks_path】はnewアクションの時のみに有効で、editアクションではエラーになってしまいます
解決方法
共通ビューに記載されているform_forの箇所を別ファイルに記載します
before
new.html.haml/edit.html.haml= render "form"after
new.html.haml= form_for @task, url: group_tasks_path do |f| = render partial: "form", locals: {f: f} |= render "new_main"edit.html.haml= form_for @task, url: group_task_path do |f| = render partial: "form", locals: {f: f} |= render "new_main"上記内容に変更することで、newアクション時には新規登録画面、editアクション時には編集画面(入力されている内容も反映)されるようになり、create、updateもバッチリでした
参考
- 投稿日:2020-05-30T16:51:21+09:00
マークアップの共通化【new,edit】
概要
新規登録画面(newアクション)や編集画面(editアクション)は登録項目は同じなので画面共通にしちゃた方が楽ということで備忘録のため残します。
変更点
①viewフォルダ内に _form.html.hamlファイルを作成し、表示内容を記載する
②viewフォルダ内にある edit.html.hamlと new.html.hamlに下記のコードを記載するnew.html.haml/edit.html.haml= render "form"感想
あら!簡単
何ということでしょう!いとも簡単に画面の表示ができました!しかし、、、
私が実装しているアプリには問題が発生。。。
form_forにurlを指定していたためエラーが発生してしまいました。。。。
こちらの解決法は下記URLをご覧くださいませURL
https://qiita.com/kazuko___3o3___/items/37579db477c5f398c472
- 投稿日:2020-05-30T10:41:39+09:00
Javascriptソースコードを隠蔽してみた。
はじめに
javascriptはいやおうなしに見られてしまうので、
APIキーとかソースとか隠せないかな、と考えて色々調べてみた結果、ここに行き着いた。現状のままではPHPにJsソースを書くことになる欠点がある……ので、これの対策verもそのうち記事にします。
どうやって実装するの?
まずもって、ユーザーがJavascriptのソースコードをのぞき見する主な手段は以下の2パターンある。
- URLバーに該当するJsのURLを直接打ち込んで、ページとして見る
- 右クリック→検証→左メニューの「Sources」から閲覧する
逆に言えば、今回やるべきはこの2つの手段を両方とも潰すことである。
それを踏まえて、本システムの要件は以下のとおりとする。要件
- ① 直接アクセスした場合にはエラーorニセのコードを出力し、ソースコードを見られないようにすること
- ② 右クリック→検証→左メニューの「Sources」を使ってもソースコードが見られないようにすること
- ③ あらかじめ決めておいたサイト以外からは呼び出せないように設定する(場合によっては必要になる。理由は後述)
Step1 直接アクセスの阻止
まず直接URLを指定してページを覗かれた場合だが、これは単純なJavascriptだけでは阻止しきれない。
なぜならJavascriptがクライアントサイドで動く言語だからだ。この図はウェブサイトが開かれ、見られるようになるまでの一連の流れ。
javascriptのようなクライアントサイド言語はまずコード全体が閲覧者の端末に送信され、閲覧者の端末の中(図の⑤)で処理が実行される。見せたい見せたくないに関わらずページを開いた瞬間にコードを渡してしまうので、そのあとから隠蔽するのって難しいのだ。
ならばどうするかというと、「サーバーサイド言語」というやつを経由させる。
こいつは図で言うと③で実行される言語で、サーバーマシンの中で処理を行ってからその結果だけを返すようになっている。
ということは、このサーバーサイド言語で直接アクセスかどうかを判定してやればよいわけだ。今回はPHPを利用してこの処理を実装する。
サーバー内で処理できればそれでいいので、処理自体はPythonでもRubyでもNode.jsでも実装できる……はず。とりあえず、以下のようにソースを書いてみた。
source.php<?php /* 直接アクセス禁止設定 */ if($_SERVER["REQUEST_METHOD"] === 'POST'){ /* 本命のソース ※見せたくないソースをここに記載します */ echo "本命のソース!"; }else{ /* 直接アクセスされた場合のダミー記述 */ echo 'アクセス……拒否しますっ!!(>_<)'; } ?>直接アクセスかどうかのチェックには、リクエストメソッドの状態を取得する
$_SERVER["REQUEST_METHOD"]
を用いよう。
この値は通常GETになっていて、後述するような特殊なアクセスを行った場合のみPOSTになる。
直接アクセスの場合はもちろんGETになっているので見れないというわけだ。Step1 隠したいJavascriptコードの用意
隠したいJsソースを用意する。
javascriptfunction 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%'; document.body.appendChild(block); /* ここまで */ };このコードは後述の
XMLHttpRequest
で読み込み、そのあとに削除する。
そのため、ここに記載するコードはオブジェクト化しておく必要がある。
平たく言うと一つのfunction
内にまとめておかなくてはならない。で、これをStep1の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%'; document.body.appendChild(block); /* ここまで */ };"; }else{ /* 直接アクセスされた場合のダミー記述 */ echo 'アクセス……拒否しますっ!!(>_<)'; } ?>これで、要件①はクリア。
直接URLからページを見てもコードは読めなくなった。Step3 XMLHttpRequestでの呼び出し
次に呼び出し処理を作成する。
呼び出しには、javascriptのXMLHttpRequest
を用いる。そのままコピペできる内容なので、とりあえずはコードを記載する。
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
を実行しておけば問題なく動作する。で、上記のコードを実際にページに記述してみよう。
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に置いています。
「Clone or download」→「Download ZIP」でダウンロードできます。
PHPを含むので、PHPが動く実行環境が必要というのは言わずもがな。おまけ
XMLHttpRequest
とCORS残るは要件③「あらかじめ決めておいたサイト以外からは呼び出せないように設定する」だが、
これ実は自動的にクリアしている。今回使った
XMLHttpRequest
だが、こいつにはCORSというルールで制限がかけられている。
詳しい説明はこちらの記事に譲るが……すごーーく雑な説明をすると、
XMLHttpRequest
で別のファイルを呼び出すときに呼び出し元と呼び出し先のサイトが異なった場合、呼び出される側に『このサイトはオレを呼び出していいぞ!』という許可証をつけておけよ?でなけりゃ呼び出しを拒否するぞ1ということ。
今回は呼び出し元(index.html)と呼び出し先(source.php)を同じ階層に同梱しているのでこの制限にはあたらない。が、もし仮に別サイトから呼び出すことを考慮したい場合、
source.php
の文頭にheader()
を追記して以下のように変更しよう。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%'; document.body.appendChild(block); /* ここまで */ };"; }else{ /* 直接アクセスされた場合のダミー記述 */ echo 'アクセス……拒否しますっ!!(>_<)'; } ?>参考文献
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/
正確には「オリジン」というやつをを比較する。わからなければ、ドメインと同じものだと思っておけばいい。 ↩
- 投稿日:2020-05-30T10:41:39+09:00
Javascriptを隠蔽化してみた。
はじめに
javascriptはいやおうなしに見られてしまうので、
APIキーとかソースとか隠せないかな、と考えて色々調べてみた結果、ここに行き着いた。現状のままではPHPにJsソースを書くことになる欠点がある……ので、これの対策verもそのうち記事にします。
どうやって実装するの?
まずもって、ユーザーがJavascriptのソースコードをのぞき見する主な手段は以下の2パターンある。
- URLバーに該当するJsのURLを直接打ち込んで、ページとして見る
- 右クリック→検証→左メニューの「Sources」から閲覧する
逆に言えば、今回やるべきはこの2つの手段を両方とも潰すことである。
それを踏まえて、本システムの要件は以下のとおりとする。要件
- ① 直接アクセスした場合にはエラーorニセのコードを出力し、ソースコードを見られないようにすること
- ② 右クリック→検証→左メニューの「Sources」を使ってもソースコードが見られないようにすること
- ③ あらかじめ決めておいたサイト以外からは呼び出せないように設定する(場合によっては必要になる。理由は後述)
Step1 直接アクセスの阻止
まず直接URLを指定してページを覗かれた場合だが、これは単純なJavascriptだけでは阻止しきれない。
なぜならJavascriptがクライアントサイドで動く言語だからだ。この図はウェブサイトが開かれ、見られるようになるまでの一連の流れ。
javascriptのようなクライアントサイド言語はまずコード全体が閲覧者の端末に送信され、閲覧者の端末の中(図の⑤)で処理が実行される。見せたい見せたくないに関わらずページを開いた瞬間にコードを渡してしまうので、そのあとから隠蔽するのって難しいのだ。
ならばどうするかというと、「サーバーサイド言語」というやつを経由させる。
こいつは図で言うと③で実行される言語で、サーバーマシンの中で処理を行ってからその結果だけを返すようになっている。
ということは、このサーバーサイド言語で直接アクセスかどうかを判定してやればよいわけだ。今回はPHPを利用してこの処理を実装する。
サーバー内で処理できればそれでいいので、処理自体はPythonでもRubyでもNode.jsでも実装できる……はず。とりあえず、以下のようにソースを書いてみた。
source.php<?php /* 直接アクセス禁止設定 */ if($_SERVER["REQUEST_METHOD"] === 'POST'){ /* 本命のソース ※見せたくないソースをここに記載します */ echo "本命のソース!"; }else{ /* 直接アクセスされた場合のダミー記述 */ echo 'アクセス……拒否しますっ!!(>_<)'; } ?>直接アクセスかどうかのチェックには、リクエストメソッドの状態を取得する
$_SERVER["REQUEST_METHOD"]
を使う。
この値は通常GETになっていて、後述するような特殊なアクセスを行った場合のみPOSTになる。
直接アクセスの場合はもちろんGET。Step1 隠したいJavascriptコードの用意
隠したいJsソースを用意する。
javascriptfunction 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%'; document.body.appendChild(block); /* ここまで */ };このコードは後述の
XMLHttpRequest
で読み込み、そのあとに削除する。
そのため、ここに記載するコードはオブジェクト化しておく必要がある。
平たく言うと一つのfunction
内にまとめておかなくてはならない。で、これをStep1の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%'; document.body.appendChild(block); /* ここまで */ };"; }else{ /* 直接アクセスされた場合のダミー記述 */ echo 'アクセス……拒否しますっ!!(>_<)'; } ?>これで、要件①はクリア。
直接URLからページを見てもコードは読めなくなった。Step3 XMLHttpRequestでの呼び出し
次に呼び出し処理を作成する。
呼び出しには、javascriptのXMLHttpRequest
を用いる。そのままコピペできる内容なので、とりあえずはコードを記載する。
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
を実行しておけば問題なく動作する。で、上記のコードを実際にページに記述してみよう。
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に置いています。
「Clone or download」→「Download ZIP」でダウンロードできます。
PHPを含むので、PHPが動く実行環境が必要というのは言わずもがな。おまけ
XMLHttpRequest
とCORS残るは要件③「あらかじめ決めておいたサイト以外からは呼び出せないように設定する」だが、
これ実は自動的にクリアしている。今回使った
XMLHttpRequest
だが、こいつにはCORSというルールで制限がかけられている。
詳しい説明はこちらの記事に譲るが……すごーーく雑な説明をすると、
XMLHttpRequest
で別のファイルを呼び出すときに呼び出し元と呼び出し先のサイトが異なった場合、呼び出される側に『このサイトはオレを呼び出していいぞ!』という許可証をつけておけよ?でなけりゃ呼び出しを拒否するぞ1ということ。
今回は呼び出し元(index.html)と呼び出し先(source.php)を同じ階層に同梱しているのでこの制限にはあたらない。が、もし仮に別サイトから呼び出すことを考慮したい場合、
source.php
の文頭にheader()
を追記して以下のように変更しよう。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%'; document.body.appendChild(block); /* ここまで */ };"; }else{ /* 直接アクセスされた場合のダミー記述 */ echo 'アクセス……拒否しますっ!!(>_<)'; } ?>参考文献
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/
正確には「オリジン」というやつをを比較する。わからなければ、ドメインと同じものだと思っておけばいい。 ↩
- 投稿日:2020-05-30T10:41:39+09:00
Javascriptをユーザーから見られないようにしてみた。
はじめに
javascriptはいやおうなしに見られてしまうので、
APIキーとかソースとか隠せないかな、と考えて色々調べてみた結果、ここに行き着いた。現状のままではPHPにJsソースを書くことになる欠点がある……ので、これの対策verもそのうち記事にします。
※完成形のコードはGithubに置いています。
「Clone or download」→「Download ZIP」でダウンロードできます。
PHPを含むので、PHPが動く実行環境が必要というのは言わずもがな。どうやって実装するの?
まずもって、ユーザーがJavascriptのソースコードをのぞき見する主な手段は以下の2パターンある。
- URLバーに該当するJsのURLを直接打ち込んで、ページとして見る
- 右クリック→検証→左メニューの「Sources」から閲覧する
逆に言えば、今回やるべきはこの2つの手段を両方とも潰すことである。
それを踏まえて、本システムの要件は以下のとおりとする。要件
- ① 直接アクセスした場合にはエラーorニセのコードを出力し、ソースコードを見られないようにすること
- ② 右クリック→検証→左メニューの「Sources」を使ってもソースコードが見られないようにすること
- ③ あらかじめ決めておいたサイト以外からは呼び出せないように設定する(場合によっては必要になる。理由は後述)
Step1 直接アクセスの阻止
まず直接URLを指定してページを覗かれた場合だが、これは単純なJavascriptだけでは阻止しきれない。
なぜならJavascriptがクライアントサイドで動く言語だからだ。この図はウェブサイトが開かれ、見られるようになるまでの一連の流れ。
javascriptのようなクライアントサイド言語はまずコード全体が閲覧者の端末に送信され、閲覧者の端末の中(図の⑤)で処理が実行される。見せたい見せたくないに関わらずページを開いた瞬間にコードを渡してしまうので、そのあとから隠蔽するのって難しいのだ。
ならばどうするかというと、「サーバーサイド言語」というやつを経由させる。
こいつは図で言うと③で実行される言語で、サーバーマシンの中で処理を行ってからその結果だけを返すようになっている。
ということは、このサーバーサイド言語で直接アクセスかどうかを判定してやればよいわけだ。今回はPHPを利用してこの処理を実装する。
サーバー内で処理できればそれでいいので、処理自体はPythonでもRubyでもNode.jsでも実装できる……はず。とりあえず、以下のようにソースを書いてみた。
source.php<?php /* 直接アクセス禁止設定 */ if($_SERVER["REQUEST_METHOD"] === 'POST'){ /* 本命のソース ※見せたくないソースをここに記載します */ echo "本命のソース!"; }else{ /* 直接アクセスされた場合のダミー記述 */ echo 'アクセス……拒否しますっ!!(>_<)'; } ?>直接アクセスかどうかのチェックには、リクエストメソッドの状態を取得する
$_SERVER["REQUEST_METHOD"]
を使う。
この値は通常GETになっていて、後述するような特殊なアクセスを行った場合のみPOSTになる。
直接アクセスの場合はもちろんGET。Step1 隠したいJavascriptコードの用意
隠したいJsソースを用意する。
javascriptfunction 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%'; document.body.appendChild(block); /* ここまで */ };このコードは後述の
XMLHttpRequest
で読み込み、そのあとに削除する。
そのため、ここに記載するコードはオブジェクト化しておく必要がある。
平たく言うと一つのfunction
内にまとめておかなくてはならない。で、これをStep1の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%'; document.body.appendChild(block); /* ここまで */ };"; }else{ /* 直接アクセスされた場合のダミー記述 */ echo 'アクセス……拒否しますっ!!(>_<)'; } ?>これで、要件①はクリア。
直接URLからページを見てもコードは読めなくなった。Step3 XMLHttpRequestでの呼び出し
次に呼び出し処理を作成する。
呼び出しには、javascriptのXMLHttpRequest
を用いる。そのままコピペできる内容なので、とりあえずはコードを記載する。
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
を実行しておけば問題なく動作する。で、上記のコードを実際にページに記述してみよう。
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に置いています。
「Clone or download」→「Download ZIP」でダウンロードできます。
PHPを含むので、PHPが動く実行環境が必要というのは言わずもがな。おまけ
XMLHttpRequest
とCORS残るは要件③「あらかじめ決めておいたサイト以外からは呼び出せないように設定する」だが、
これ実は自動的にクリアしている。今回使った
XMLHttpRequest
だが、こいつにはCORSというルールで制限がかけられている。
詳しい説明はこちらの記事に譲るが……すごーーく雑な説明をすると、「呼び出し元と呼び出し先のサイトが別々だったときは呼び出される側に『このサイトはオレを呼び出していいぞ!』という許可証をつけておいてくださいね。許可証なしで他人のサイトからファイル持ってこようとするなら接続させませんよ」1
ということ。
今回は呼び出し元(index.html)と呼び出し先(source.php)を同じウェブサイトに同梱しているのでこの制限にはあたらない。が、もし仮に別サイトから呼び出すことを考慮する2場合、
source.php
の文頭にheader()
を追記して以下のように変更しよう。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%'; document.body.appendChild(block); /* ここまで */ };"; }else{ /* 直接アクセスされた場合のダミー記述 */ echo 'アクセス……拒否しますっ!!(>_<)'; } ?>参考文献
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/
- 投稿日:2020-05-30T05:15:38+09:00
初心者によるプログラミング学習ログ 330日目
100日チャレンジの330日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。
100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
330日目は、
おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) May 29, 2020
330日目 2h
・ポートフォリオ作成2h:制作物一覧部分作成#早起きチャレンジ#駆け出しエンジニアと繋がりたい#100DaysOfCode