データの繰り返しの送信を避けます。ソースをチェックして、実行するアクションと一致する外部送信かどうかを確認します (追加、削除など、同じページに複数のロジックが実装されている場合)。を変更して、ファイル内の 1 つの PHP 操作にまとめます) ここで説明するトークンは、ページが表示されるときに FORM に書き込まれる非表示のフォーム項目 (type=hidden) です。
PHP トークン (トークン) の設計
設計目標:
トークンを平文にすることはできません。平文だと危険すぎるため、暗号文は可逆的でなければなりません。そのため、既成の方法を使用しました。インターネット。
目標を達成する方法:
重複した提出を避けるにはどうすればよいですか?
配列は SESSION に保存される必要があります。この配列には、バックグラウンド処理中に、トークンがこの配列に存在するかどうかが最初に確認されます。それは、繰り返し送信されたことを意味します。
ソースを確認するには?
オプションで、このトークンが生成されると、現在の session_id が追加されます。他の人があなたの HTML (トークンのコピー) をコピーした場合、理論的にはトークンに含まれる session_id が現在の session_id と等しくないものと判断されます。この提出物は外部提出物です。
実行するアクションをどのように一致させるか?
トークンを使用する場合、トークンのアクション名をトークンに書き込む必要があります。これにより、処理中に比較のためにアクションを抽出できます。
以前書いた GToken は上記 2 点を満たせなかったので、今日修正して機能 2 を追加しました。個人的には大丈夫だと思います。
コードを見て、何か無理があると思われた場合は、アドバイスをお願いします。
オンラインで暗号化の方法を見つけて、いくつかの変更を加えました。
GEncrypt.inc.php:
クラス GEncrypt は GSuperclass を拡張します {
保護された静的関数 keyED($txt,$encrypt_key){
$encrypt_key = md5($encrypt_key);
$ctr=0;
$tmp = "";
for ($i=0;$i
if ($ctr==strlen($encrypt_key)) $ctr=0;
$tmp.= substr($txt,$i,1) ^ substr($encrypt_key,$ctr,1);
$ctr++;
}
return $tmp;
}
パブリック静的関数 encrypt($txt,$key){
//$encrypt_key = md5(rand(0,32000));
$encrypt_key = md5(((float) date("YmdHis") + rand(10000000000000000,9999999999999999)).rand(100000,999999));
$ctr=0;
$tmp = "";
for ($i=0;$i
if ($ctr==strlen($encrypt_key)) $ctr=0;
$tmp.= substr($encrypt_key,$ctr,1) . (substr($txt,$i,1) ^ substr($encrypt_key,$ctr,1));
$ctr++;
}
returnbase64_encode(self::keyED($tmp,$key));
}
パブリック静的関数 decrypt($txt,$key){
$txt = self::keyED(base64_decode($txt),$key);
$tmp = "";
for ($i=0;$i
$md5 = substr($txt,$i,1);
$i++;
$tmp.= (substr($txt,$i,1) ^ $md5);
}
return $tmp;
}
}
?>
GToken.inc.php
方法:
a、granteToken パラメータ: formName、アクション名、key は暗号化/復号化キーです。
暗号化された (formName:session_id) という形式で文字列を返します
b、isToken パラメータ: トークンは、grantToken、formName、アクション名によって生成された結果、fromCheck がオリジンをチェックするかどうか、true の場合、トークン内の session_id が現在の session_id と同じかどうかを判断する必要もあります。 .
c、dropToken、アクションが正常に実行された後、この関数を呼び出してトークンをセッションに記録します。
/**
* 原則: トークンの割り当てをリクエストするときは、一意のトークン、base64(time + rand + action) を割り当てる方法を見つけてください
* 送信される場合は、このトークンを記録して、このトークンが以前に使用されていることを示し、繰り返しの送信を避けるために使用できます。
*
*/
クラス GToken {
/**
* 現在のトークンをすべて取得します
*
* @return array
*/
パブリック静的関数 getTokens(){
$tokens = $_SESSION[GConfig::SESSION_KEY_TOKEN ];
if (empty($tokens) && !is_array($tokens)) {
$tokens = array();
}
$tokens を返す;
}
/**
* 新しいトークンを生成します
*
* @param string $formName
* @param 暗号化キー $key
* @戻り文字列
*/
パブリック静的関数 GranteToken($formName,$key = GConfig::ENCRYPT_KEY ){
$token = GEncrypt::encrypt($formName.":".session_id(),$key);
$token を返す;
}
/**
* トークンを削除すると、実際にはセッション内の配列に要素が追加され、データの繰り返しの送信を避けるためにそのトークンが以前に使用されたことを示します。
*
* @param string $token
*/
パブリック静的関数dropToken($token){
$tokens = self::getTokens();
$tokens[] = $token;
GSession::set(GConfig::SESSION_KEY_TOKEN ,$tokens);
}
/**
* 指定されたトークンであるかどうかを確認します
*
* @param string $token チェックするトークンの値
* @param string $formName
* @param boolean $fromCheck ソースをチェックするかどうか。true の場合、トークンに付加されている session_id が現在の session_id と同じかどうかを判断します。
* @param string $key 暗号化キー
* @return boolean
*/
パブリック静的関数 isToken($token,$formName,$fromCheck = false,$key = GConfig::ENCRYPT_KEY){
$tokens = self::getTokens();
if (in_array($token,$tokens)) //存在する場合、それは使用済みトークンであることを意味します
false を返す;
$source = split(":", GEncrypt::decrypt($token,$key));
if($fromCheck)
return $source[1] == session_id() && $source[0] == $formName;
その他
return $source[0] == $formName;
}
}
?>
例:
まず$_POSTからトークンを取り出し、isTokenを使って判定します。
include("../common.inc.php");
$token = $_POST["トークン"];
if (GToken::isToken($token,"adminLogin",true)) {
$vCode = $_POST["vCode"];
if (strtoupper($vCode) != strtoupper($_SESSION[GConfig::SESSION_KEY_VALIDATE_CODE ])) {
throw new Exception("検証コードが間違っています!");
}
$vo = 新しい VO_Admin();
$vo->setNickName($_POST["name"]);
$vo->setPwd($_POST["pwd"]);
$mo = new MO_Admin();
$mo->setVO($vo);
$f = $mo->login();
if(!$f){
throw new Exception("ユーザー名またはパスワードが間違っています!");
}その他{
GToken::dropToken($token);
//header("location:".GDir::getRelativePath("/admin/index.php"));
echo "here"; // 外部から送信された場合、この文は印刷されません!
}
}
$sFile = GDir::getAbsPath(GConfig::DIR_SERIALIZE ,"admin/login");
$tpl = GSerialize::load($sFile);
if ($tpl === false) {
$tpl = new GTpl(GConfig::DIR_SKIN ,GConfig::DEBUG_TPL_FILE );
$tpl->load(array(
"header" => "admin/header.html",
"フッター" => "admin/footer.html",
"admLogin" => "admin/login.html",
"admLoginJs"=>"admin/loginJs.html"
));
GSerialize::save($tpl,$sFile);
}
$tpl->assign("タイトル","管理者ログイン");
$tpl->assign("path",GDir::getRelativePath(SITE_DIR));
$tpl->assign("vImg",GDir::getRelativePath("/vImg.php"));
if (MO_Admin::isLogined()) {
$tpl->parseBlock("blk_logined");
}else {
$tpl->assign("token",GToken::granteToken("adminLogin"));
$tpl->parseBlock("blk_loadScripts","cond_notLogin");
$tpl->parseBlock("blk_notLogin");
}
echo $tpl->parse("header");
echo $tpl->parse("admLogin");
echo $tpl->parse("footer");
echo $tpl->parse("admLoginJs");
?>すべて問題ないようです。
一致するアクションであるかどうかを判断したい場合は、isToken の formName を変更して実行できます。一致しないことを証明してください。
ロジックが単純すぎるため、重複送信を回避できるかどうかは検証していません。
残りは、ソースチェックが正常に機能しているかどうかを判断することです。
上記の例で生成された HTML をローカル Web ページにコピーし (さまざまなドメインの目的を達成するため)、それを実行し、ソースが不明であることを確認し、アクションは実行されません (isToken の 3 番目のパラメーターはtrue に設定) .
isToken の 3 番目のパラメータを false に設定し、送信すると、指定されたアクションが実行されます。
さて、今のところはこれで終わりです。長期的に使用するには、まだバグがあるかどうかはわかりません。