Webアプリケヌションのアクセス暩を分離するシステム

この蚘事では、アむデア、デヌタベヌスの蚭蚈、PHPコヌドの䜜成、および最終的な最適化のアむデアから完党なサむクルを説明したす。 できるだけ簡単にすべおに぀いお話そうずしたす。 䟋ずしおPHPずMysqlを䜿甚したす。 同時に私は新人を蚓緎したす:)。

この蚘事では、質問に觊れたす。
1. ACLのアむデア
2.デヌタベヌス蚭蚈
3.デヌタベヌスの正芏化
4.コヌドのリファクタリング
5.䜜業コヌドの最適化

この蚘事は、CMSのアクセス暩のバむナリ配垃に察する応答です。 著者が実甚的な郚分を曞いおいる間、私はかなり長い間䜿甚しおきた私のバヌゞョンを提䟛したいず思いたす。
これから説明する内容は、 ACLのように芋えたす。

アむデアの簡単な説明


アクセス暩は、適甚する必芁があるすべおのオブゞェクトに属したす。
簡単なニュヌスペヌゞの䟋ここで説明したすを怜蚎する堎合、アクセス暩は次のようになりたす。
1メむンニュヌスペヌゞ-グロヌバルアクセス暩。「新しいニュヌスの䜜成」、「䞭皋床のニュヌス」、「ペヌゞ自䜓の衚瀺」を意味したす。
2各ニュヌス-「ニュヌスの著者を線集する」たたは「コメントを残さない」機胜。

アクセス暩システムは次のもので構成されたす。
{グルヌプ} + {アクション}たたは{グルヌプ}-{アクション}

グルヌプは、次の名前のセットです。
1特定のナヌザヌの暩利たずえば、「User1」、「User2」...。 たずえば、このナヌザヌがアクセスできるプラむベヌトメッセヌゞや、サむトで自分のメッセヌゞのみを線集できるようにするために䜿甚されたす。
2特定のアクションに暩限を付䞎する必芁があるプラむベヌトペヌゞのグルヌプたたはナヌザヌグルヌプ。 䟋管理者、スヌパヌモデレヌタヌなど
3远加のプロパティ。 たずえば、フラグはモヌド切り替えです

アクション -既存の{Group}を持぀ナヌザヌが実行できる䞀連のアクション。 圓瀟のニュヌスシステムでは、次を䜿甚できたす。
N-新しいトピックを远加
D-トピックを削陀
E-トピックを線集
V-トピックを参照
C-コメントを残す
B-コメントを削陀

±ナヌザヌにそのような暩利を䞎えるか、アクションぞのアクセスを優先䞎えないこずを意味したす。 䟋ナヌザヌ+ VC、ナヌザヌ-C =ナヌザヌ+V。

次に、簡単なニュヌスサむトのアクセス暩の䟋を考えおみたしょう。
MainNewsPageオブゞェクト
ナヌザヌ+ VC、モデレヌタヌ+ NEDB、管理者+ NEDB
NewsMessageオブゞェクト
User1 + ED原則ずしお、モデレヌタヌのみが远加できる堎合は䞍芁です
Users-Cコメントを残したくない堎合に䜿甚できたす
NewsCommentオブゞェクト
User2 + Bここでは、すべおのナヌザヌがコメントを残すこずができたすが、誰でもコメントを削陀できるわけではないため、必芁です

コンピュヌタヌのアむデアを理解するためのシステムを簡玠化する


たず、オブゞェクトの暩利を扱うためのデヌタベヌスを定矩したす。

いく぀かの暩利のリストを取埗しおいるため、このようなデヌタベヌスから始めるこずができたす。
RightsID-暩利のリストの識別子。
グルヌプ - グルヌプの名前。
サむン -グルヌプサむン。
アクション - アクションの名前。

䟋1MainNewsPageのアクセス蚱可
IDラむシッド団䜓サむンアクション
1100ナヌザヌ+V
2100ナヌザヌ+C
3100モデレヌタヌ+N
4100モデレヌタヌ+E
5100モデレヌタヌ+D
6100モデレヌタヌ+B
7100管理者+N
8100管理者+E
9100管理者+D
10100管理者+B

䟋2NewsMessageに察する暩利
IDラむシッド団䜓サむンアクション
11101User1+D
12101User1+E
13101ナヌザヌ-C

SELECT * FROM `rights_action` WHERE` RightsID` = 100を芁求するず、必芁なオブゞェクトに属するすべおの暩限を取埗したす。

テヌブルの正芏化。 ナヌザヌ暩限を远加したす。


ペヌゞを衚瀺するナヌザヌは、所有する暩利を持っおいる必芁がありたす。 それらに基づいお、ナヌザヌにアクションの暩利があるかどうかを知るこずができたす。
䟋User2、Users、Moderator。

これを行うには、暩利テヌブルを定矩したす。
RightsID-ナヌザヌ暩利のリストの識別子。
グルヌプ -ナヌザヌがメンバヌになっおいるグルヌプの名前。

䟋
IDラむシッド団䜓
110User1
210ナヌザヌ
310モデレヌタヌ
次に、䞡方のテヌブルを通垞の圢匏にしたす。  wiki 

その結果、IDキヌが削陀され、3぀のテヌブルが取埗されたす。
rights_action-オブゞェクトの暩利
RightsID敎数pk -暩利リストの識別子。
GroupID敎数pk -グルヌプの名前。
サむンtinyint1 -グルヌプのサむン。
アクション列挙pk -アクションの名前。
rights_group-ナヌザヌ暩限
RightsID敎数pk -ナヌザヌ暩利のリストの識別子。
GroupID敎数pk -ナヌザヌがメンバヌになっおいるグルヌプの識別子。
rights_names-グルヌプ名
GroupID敎数pk -グルヌプ識別子。
name-グルヌプの名前。

䞻キヌ「ID」を他のキヌに眮き換えたした。堎合によっおは、テヌブル内のいく぀かのフィヌルドで構成されおいたす。
グルヌプの笊号は、0+たたは1-になりたした。これにより、グルヌプぞのアクセスが容易になるためです。
GroupID識別子は、rights_namesの名前を盎接指したす。
実際、 rights_namesテヌブルは、必芁なアクションに察する暩利を識別するために䜿甚されない付録です。 このテヌブルは、結果を「人間化」するためだけに圹立ちたす。

私たちが埗たものの䟋
rights_name
Groupidお名前
10ナヌザヌ
11モデレヌタヌ
12管理者
1001User1
1002User2
1003User3
rights_group
ラむシッドGroupid
11001
110
111
rights_action
ラむシッドGroupidサむンアクション
100100message_view
100100comment_create
100110message_create
100110message_edit
100110message_delete
100110comment_delete
100120message_create
100120message_edit
100120message_delete
100120comment_delete
10110010message_edit
10110010message_delete
101101comment_create
それはあたり明癜ではありたせん-人にずっお。 数倀で動䜜するコンピュヌタヌは、テヌブルの凊理がはるかに簡単になりたした。
これで、任意のアクションのテヌブル内の任意のオブゞェクトに暩限を远加できたす。 アクションはENUMフィヌルド「アクション」の圢匏でテヌブルに曞き蟌たれるようになりたした。これにより、プロゞェクトの理解ず開発が容易になりたす。 アクション自䜓は文字列のようなもので、奜きなように呌び出すこずができたす。

`rights_group`はナヌザヌに結び付けられるべきであり、ナヌザヌが所有する暩利に぀いお話したす。
`rights_action`はオブゞェクトにバむンドし、ナヌザヌが実行できる暩限、アクションを指定する必芁がありたす。

䟋ニュヌスサむトの堎合
news_pageメむンニュヌスペヌゞのパラメヌタヌ
PageIDラむシッドお名前
1100ニュヌスペヌゞ
news_messageニュヌスペヌゞに投皿
MsgidPageIDラむシッドヘッダヌメッセヌゞ
11101やばい、私たちはメむンです!!!しかし、これはほんの始たりに過ぎたせん。そのため、Habrの管理に近づくず...
21101先週のニュヌスメむンに沿っお敎然ず行進しおいるにもかかわらず、蚈画が途切れたようです...

アクセス暩ラむブラリの開発


そしお、これらのテヌブルをたずめるために必芁なものを確認し、必芁な結果を遞択する必芁がありたす。

アクションの可胜性をチェックするずきのアクションのアルゎリズム
1デヌタベヌスから、必芁なオブゞェクトの暩利の遞択を取埗したす。 100ナヌザヌ+ VC、モデレヌタヌ+ NEDB、管理者+ NEDB
2必芁なアクションアクションを遞択したす。 Vナヌザヌ+ V
3ナヌザヌのアクセス暩ず遞択したものを比范したす。 ナヌザヌ、ナヌザヌ1 <=>ナヌザヌ+
4結果がない堎合は、falseを返したす。
5結果がマむナスで構成されおいる堎合、falseを返したす。 それ以倖の堎合は、trueを返したす。

泚目すべきもう1぀のポむントは、芪ニュヌスペヌゞから子この堎合はメッセヌゞぞのアクセス蚱可です。 ぀たり、ペヌゞ+ 'message_view'で指定するず、すべおのメッセヌゞは自動的にそのような暩利読み取りになりたす。 アルゎリズムのパラグラフ1でこの状況を䜿甚しお怜蚌したす。

実装に移りたしょう
実際、正しい遞択をするためにPHPは必芁ありたせん。 mysqlですべおを行いたす。
アむテム1
この堎合、耇数のオブゞェクトから「rightsID」デヌタを読み取り、テヌブルからそれらを遞択する必芁がありたす。
ペヌゞに察する暩限ずペヌゞ䞊の個々の投皿に察する暩限があるため、いく぀かのオブゞェクト。 䞡方の暩利は互いに補完したす。 子パディング芪
たずえば、ペヌゞ䞊のメッセヌゞの暩利
SELECT * FROM `rights_action` WHERE` RightsID` = 100 たたは ` RightsID` = 101 、ここで
100-ペヌゞ暩限のID
101-ペヌゞ䞊のメッセヌゞの暩利のID
PHPでの通信を容易にするために、構文を少し最適化したす。
SELECT * FROM `rights_action` WHERE` RightsID` IN 100、101

アむテム2
必芁なアクションを遞択するず、すべおが簡単になりたす。
SELECT * FROM `rights_action` WHERE` action` = 'message_view'

アむテム3
そしお、ここでいく぀かのSELECTを組み合わせる必芁がありたす。 たず、ナヌザヌのアクセス暩を遞択しおから、必芁なものず比范したす。 これらのアクションを1぀にたずめるず、以䞋が埗られたす。
SELECT * FROM `rights_action` WHERE` GroupID` IN SELECT` GroupID` FROM` rights_group` WHERE `RightsID` = 1

アむテム1-3。
1぀の耇雑なク゚リですべお䞀緒になりたした
SELECT * FROM `rights_action` WHERE` RightsID` IN 100、101AND` action` = 'message_view' AND` GroupID` IN  SELECT `GroupID` FROM` rights_group` WHERE` RightsID` = 1
この䟋では、ナヌザヌ暩利1、オブゞェクト100および101の暩利、アクション 'メッセヌゞの衚瀺'message_viewを取埗し、結果笊号+および-を生成したす。

項目1-5。
これをPHP実装に貌り付け、同時にチェックを远加したす。
function check( /*array(int,int,...)*/ $obj_rights, /*integer*/ $user_rightsID, /*string*/ $action){
$result = mysql_query( "SELECT * FROM `rights_action` WHERE `RightsID` IN (" . implode( "," ,$obj_rights) . ") AND `action`= '$action' AND `GroupID` IN (SELECT `GroupID` FROM `rights_group` WHERE `RightsID` = $user_rightsID)" );

if (!$result)
return false ;

$tmp=array();
while ($t = mysql_fetch_assoc($result)){
// (Users, User1, Moderator)
// + (0) - (1) ( ).
if (!isset($tmp[$t[ 'groupID' ]]))
$tmp[$t[ 'groupID' ]] = $t[ 'sign' ];
else
$tmp[$t[ 'groupID' ]] |= $t[ 'sign' ];
}
mysql_free_result($result);

if ($tmp)
// + , true. false.
return (array_search(0, $tmp) !== FALSE);

// $tmp == false
return false ;
}

* This source code was highlighted with Source Code Highlighter .
これはほんの始たりであり、最初のステップです。

アクセス暩クラスを䜜成する


䜕が必芁ですか
クラスで䜜業する䟋を開発したす。
1最初に、䜿甚する暩利を持぀ナヌザヌの暩利を指定する必芁がありたす。 たた、クラス自䜓を特定のナヌザヌにバむンドする必芁がありたす。
2プロパティを远加しお、子の暩利のクラスにオブゞェクトを远加したす。
3さたざたなアクションアクションのアクセスを確認したす。

意芋ず考え
__constructでクラスを䜜成するずきにナヌザヌ暩限を指定できたす。
クラスのプロパティが倱われないように新しいプロパティを远加する堎合、新しいクラスを䜜成する必芁がありたすプロパティを远加しお叀いクラスを耇補したす。

これをすべお実装しおみたしょう。
class Rights{
private $usrID; //User rights ID

function __construct($user_rightsID){
$ this ->usrID=$user_rightsID;
}
}
これで、コンストラクトを䜿甚できたす。
$UserRights = new Rights($CurrentUser->rightsID);
さらに別のプログラムで$ UserRightsを䜿甚したす。

確認するために適切なオブゞェクトを远加するこずを怜蚎しおください。
class Rights{
private $group=array(); //

function include_right($grp){
$clone=clone $ this ; // ,
$clone->group[]=$grp; //
return $clone;
}

//... constructor
}
異なるメッセヌゞ子からの暩利芪を远加するため、オブゞェクトを台無しにする必芁はないこずを思い出させおください。

次に、 チェック関数を曞き盎しおクラスに導入し、䜕が起こったかを確認したす。
class Rights{
private $usrID; //User rights ID
private $group=array(); //

function __construct($user_rightsID){
$ this ->usrID=$user_rightsID;
}

function include_right($grp){
$clone=clone $ this ; // ,
$clone->group[]=$grp; //
return $clone;
}

function check($action){
$result = mysql_query( "SELECT * FROM `rights_action` WHERE `RightsID` IN (" . implode( "," ,$ this ->group) . ") AND `action`= '$action' AND `GroupID` IN (SELECT `GroupID` FROM `rights_group` WHERE `RightsID` = " . $ this ->usrID . ")" );

if (!$result)
return false ;

$tmp=array();
while ($t = mysql_fetch_assoc($result)){
// (Users, User1, Moderator)
// + (0) - (1) ( ).
if (!isset($tmp[$t[ 'groupID' ]]))
$tmp[$t[ 'groupID' ]] = $t[ 'sign' ];
else
$tmp[$t[ 'groupID' ]] |= $t[ 'sign' ];
}
mysql_free_result($result);

if ($tmp)
// + , true. false.
return (array_search(0, $tmp) !== FALSE);

// $tmp == false
return false ;
}
}


* This source code was highlighted with Source Code Highlighter .
珟圚行っおいるこず関数をクラスに再远加し、より䜿いやすく、より汎甚的にするこずは、 リファクタリングず呌ばれたす。  wiki 

これで、このクラスを次のように䜿甚できたす。
//
$UserRights = new Rights($CurrentUser->rightsID);

// , .
$PageRights = $UserRights->include_right($MainPage->rightsID);

//, ?
if ($PageRights->check( 'messages_view' )){
//, . ?

//
foreach ($MainPage->Messages as $msg){
// (parent), (child)
$MsgRights = $PageRights->include_right($msg->rightsID);

//
if ($MsgRights->check( 'messages_view' )){
// , ?
if ($MsgRights->check( 'messages_edit' ))
$msg->editable_flag = 1;
// ?
if ($MsgRights->check( 'messages_delete' ))
$msg->delete_flag = 1;

DrawMessage($msg);
}
}
}
$ CurrentUserは、ペヌゞを衚瀺しおいるナヌザヌの構造です。
$ MainPage-ナヌザヌが衚瀺しおいるペヌゞ構造。
$ MainPage-> Messages-ペヌゞに衚瀺されるメッセヌゞの配列。
以前は、構造はデヌタベヌスから読み蟌たれおいたした。

最適化


ラむブラリの品質ず機胜には満足しおいたすが、生産性の問題が生じたす。

最初に目を匕くのは、新しい「アクション」を䜿甚したチェックごずに、実行䞍可胜なSQLク゚リが発生するこずです。 修正しおみたしょう。

たず、リク゚ストごずに倉化しないものを芋おみたしょう。これを最適化したす。
SELECT * FROM `rights_action` WHERE` RightsID` IN .implode"、 "、$ this-> group AND` action` = '$ action' AND `GroupID` IN SELECT` GroupID` FROM` rights_group` WHERE `RightsID` =。$ This-> usrID

たず、 SELECTク゚リ`GroupID` FROM` rights_group` WHERE` RightsID` = =のたびに 。 それから始めたしょう。
UserRightsを宣蚀するずき、このク゚リを1回実行するず、結果が既にSQLク゚リに挿入されたす。
function __construct($grp){
$result=mysql_query( "SELECT `group_rights`.groupID FROM `group_rights` WHERE `group_rights`.rightsID=$grp" );

$ this ->usrID=array();
while ($t=mysql_fetch_assoc($result)){
$ this ->usrID[]=$t[ 'groupID' ];
}
mysql_free_result($result);

$ this ->usrID=implode( "," ,$ this ->usrID);
}
これで$ this-> usrIDには、SELECTa党䜓ではなく、ク゚リに盎接挿入できる既補の文字列がありたす。

すでに簡単になっおいたすが、それでも各リク゚ストに察しおデヌタベヌス党䜓で怜玢が行われたす。 どうすればこれを取り陀くこずができたすか ほずんどの堎合、アクションのみに䟝存する予備的な結果を䜜成したす-「RightsID」ず「GroupID」の遞択は倉曎されないためです。

オブゞェクトのグルヌプが远加されるず、デヌタベヌスからすべおの結果が配列に読み蟌たれたす。配列は「アクション」の倀のみに䟝存したす。
SELECT * FROM `rights_action` WHERE `RightsID` IN (...) AND `GroupID` IN (...)

次に、配列内の各「アクション」を既に゜ヌトしお、必芁な芁玠を探しおいたす。 同時に、デヌタベヌスにはこれ以䞊のク゚リはありたせん-新しい暩限を持぀次のオブゞェクトたで。

最適化の結果、クラスは次のようになりたす。
class Rights{
private $group= "" ;
private $usrID=array();
private $temptable= "" ;

function include_right($grp){
$clone=clone $ this ;
$clone->group[]=$grp;

$result=mysql_query( "SELECT * FROM `action_rights` WHERE `action_rights`.groupID IN ({$this->usrID}) AND `action_rights`.rightsID IN (" .implode( "," ,$clone->group). ")" );
$tmp=array();
while ($t=mysql_fetch_assoc($result)){
$tmp[]=$t;
}
mysql_free_result($result);

$clone->temptable=$tmp;

return $clone;
}

function check($action){
$tmp=array();
foreach ($ this ->temptable as $t){
if ($t[ 'action' ]==$action){
if (!isset($tmp[$t[ 'groupID' ]]))
$tmp[$t[ 'groupID' ]]=$t[ 'sign' ];
else
$tmp[$t[ 'groupID' ]]|=$t[ 'sign' ];
}
}

if ($tmp){
return (array_search(0,$tmp)!==FALSE);
}
return false ;
}

function __construct($grp){
$result=mysql_query( "SELECT `group_rights`.groupID FROM `group_rights` WHERE `group_rights`.rightsID=$grp" );

$ this ->usrID=array();
while ($t=mysql_fetch_assoc($result)){
$ this ->usrID[]=$t[ 'groupID' ];
}
mysql_free_result($result);

$ this ->usrID=implode( "," ,$ this ->usrID);
}
}


* This source code was highlighted with Source Code Highlighter .

さらに高速ですか


はい、できたす。
1たずえば、メッセヌゞ子の空の暩限グルヌプを考慮する堎合、既に䜿甚されおいる䞀時テヌブルは倉曎されたせん。 この堎合、新たに䜜成するこずなく䜿甚できたす。 怜蚌のために、もう1぀だけSELECTカりント*FROM `action_rights` WHERE` GroupID` = ...を远加する必芁がありたす。これはむンデックスを通過しお結果を返したす。

2テヌブル「action_rights」ず「group_rights」のむンデックスを正しく配眮したす。
ここではわかりたせん。 専門家は、圌らが私を修正するこずを願っおいたす。 個人的に䜜成されたPK-「rightsID」、「action」、「groupID」、INDEX-「groupID」、「rightsID」

3䞀時テヌブルを䜜成した埌、 'action'でむンデックスを远加したす ALTER TABLE `{$ this-> temptable}` ADD INDEX  `action` 。
確かに、この方法が効果的かどうかはわかりたせん。 専門家、献身しおください。 :)

4キャッシュを䜿甚したす。 しかし、それは別の話です:)

実斜䟋


今日はこれで十分なコヌドだず思いたす。 仕組みは次のずおりです。
䜜業䟋 -明確さの欠劂をおIび申し䞊げたす。
test.php動䜜䟋-SQLデヌタベヌスで動䜜する私のラむブラリがここで䜿甚されおいたすが、驚かないでください。 きっずご理解いただけるず思いたす。
rights.phpは私たちのラむブラリです。

拡匵性


プロゞェクトで䜿甚する新しいアクションはすべお、「アクション」ENUMに远加されたす。

特定のアクションに関連付けおリアルタむムで远加したくない堎合は、「アクション」ENUMを敎数に眮き換え、action_nameで別のactionID察応衚を䜜成する必芁がありたす。 グルヌプの名前で行ったように

曎新継続がありたした Webアプリケヌションのアクセス暩の分離システムの最適化

Source: https://habr.com/ru/post/J51327/


All Articles