このユーザーはこの機能が使える、みたいなあれをどう実現するか
このユーザーはこの機能が使える、みたいなあれをどう実現するか
新しい記事公開しました!
結論
DBで実現するかアプリケーション側でライブラリで実現する。casbinが強そうだぞ。
はじめに
ユーザーテーブルにisAdmin
というカラムを使って、アドミンかそうでないかで、アクセスコントロールを分けていたが、サービスが大きくなり、もう少し細かくコントロールする必要になった。そこで、情報収集してえた見識をまとめておく。
アクセスコントロールという考え方を知る
だれがこのファイルを操作できるのかみたいなやつ。おおきく4つのパターンがあるみたい。
- DAC
- RBAC
- MAC
- ABAC
DACとRBACとMAC
この3つはまとめて図で理解したほうが覚えやすいと思う。
DAC(任意アクセス制御)はユーザー側が任意にこの機能を誰が使えるか設定する。例えばgoogleでファイル共有するときに誰までが閲覧可能かどうか設定する、あれのこと。ちなみに、Linuxのファイルシステムなどに利用される。反対にMAC(強制アクセス制御)は完全にシステムからレベルごとに使える機能を限定されている。軍隊などでよく利用される。例えば、めちゃくちゃ下っ端だとここまでの情報しか取得できないみたいなやつ。DACはユーザーに自由度を与え過ぎるのが欠点。MACは、純粋にレベルがあがると利用できる機能が増えていくモデルなので、例えば作成はできるけど閲覧はできないみたいな表現ができないのが欠点。
RBAC(ロールベースアクセス制御)は2つに比べたら比較的新し目。ロールという中間層を設ける。ユーザー側からしたら割り振られたロールが可能な機能しか使えない。システム側からしたらユーザーに自由度は与えずに、かつ機能のわりふりが比較的表現度たかく行える。
ABAC
ちょっとトリッキー。最終進化系かもしれない。こちらは条件を複雑にしたもの。例えば、
- 本店勤務である、店長クラスであれば削除ができる(閲覧と作成と更新もできる)
- 店長クラスであれば閲覧と作成と更新ができる
- 支店勤務であれば閲覧ができる
みたいに、その人の属性によって細かく権限を変える考え方。めっちゃ複雑。もう少しシンプルにしてRBACでも対応できると思う。 - 本店の店長ロールは全権限がある
- 支店店長ロールは閲覧と作成と更新ができる。
- 支店バイトロールは閲覧しかできない。
みたいな感じ。
DACとRBACとMACをアプリの権限管理に活かして考えてみる
上記はどちらかというとシステムレベルでコンテンツにアクセスするさいのふるい分けでよく言われる話で、webサービスの権限的な部分での話とは少し違う気がする(ユーザーが自由に権限管理できたら権限の意味がない)。ただ上記の図は権限管理に活かせそうな気がする。ここは上記の図をヒントに、少し違った考えをしてみる。
下記はDBを利用しているが、動的に変化することがなければテキストファイル(yml的なものとか)でも良いと思う。
ユーザーidに直接できることを紐付けるパターン
DBで次のように権限テーブルを作成する。
userId | view | create | edit | delete |
---|---|---|---|---|
1 | true | true | true | true |
2 | true | false | false | false |
アプリケーション側ではユーザー取得に権限テーブルをjoinし、各操作の真偽値を保持しておく。
const create = () => {
// さすがにuser.createという取得は嫌なので
if(!user.create){
throw new Error('create is not permitted.');
}
// create something.
}
ロールにできることを紐付けるパターン
DBではuserテーブルにroleカラムができ、権限テーブルにroleIdと各操作がひも付く。
userテーブル
userId | name | roleId |
---|---|---|
1 | bob | 1 |
2 | alice | 2 |
権限テーブル
roleId | name | view | create | edit | delete |
---|---|---|---|---|---|
1 | ADMIN | true | true | true | true |
2 | GUEST | true | false | false | false |
アプリケーション側のコード
const create = () => {
if(!user.role.create){
throw new Error('create is not permitted.');
}
// create something.
}
もしくは、userテーブルにroleカラムだけおいておき、権限テーブルは作らないという選択もある。その場合はアプリケーション側で分岐する。
const create = () => {
// 対応ロールが増えるとここの分岐が増える。
// const permissions = ['ADMIN', 'LEADER']として、if(permissions.includes(user.role)) とするのもあり。
if(user.role !== 'ADMIN){
throw new Error(`role need admin. you role: ${user.role}`);
}
// create something.
}
もしくはRBACを実現できるライブラリを導入して、権限テーブルの部分をアプリケーションがわのどこかであらかじめ宣言しておくという方法もある。
ユーザーにレベルをつけるパターン
ユーザーテーブルにレベルカラムをつけて、アプリケーション側でレベルが閾値以上かチェックする。どのレベル権限でどの操作ができるのかという情報はドキュメントにちゃんとまとめておかないと、あとで地獄をみそう。
userテーブル
userId | name | level |
---|---|---|
1 | bob | 2 |
2 | alice | 1 |
アプリケーション側のコード
const create = () => {
if(!user.level <= 1){
throw new Error('create is not permitted.');
}
// create something.
}
specification パターン
デザインパターンのひとつにspecificationパターンというものがある。DDD本のどこかででてくる。複雑な条件のビジネスロジックを扱うパターン。これを利用する手もあるかなと思った。
おわりに
この分野初心者なので、もし詳しい方いましたら教えていただけるとありがたいです^^
Discussion
僕なら『ユーザーにレベルをつけるパターン』にします!
『ユーザーidに直接できることを紐付けるパターン』だと、機能が増えるたびにDBをいじる必要がある や この権限変えた方がいいよねというときに既存のレコードを書き直すDB処理をする必要がある などの辛さが出てきます。
『ユーザーにレベルをつけるパターン』にしておき、ユーザーのクラスなどで権限を管理するのは一つの手です。user.can_view? や user.can_delete? などのメソッドを用意しておいて、レベルの値を意識することなく処理できるようにするなど
こうしておけばドキュメントを作らなくてもそのクラスを見れば、どのレベルがどの操作を許されているかが分かるような、、、
あくまでも一意見ですが、、
ネココニャンさん、コメントありがとうございます^^
ここ確かにそうですね😳 特にどんどん機能を追加していくフェーズだと、度重なるスキーマ変更が開発の邪魔になりそうです。
ユーザーサイドでめちゃくちゃ細かい権限の動的変更が求められない限り、DBで管理するよりも、DBには識別(レベルやロールなど)だけ置いておき、アプリケーション側で対応するほうがよさげです😁
また、ユーザークラスにメソッドごとの使用許可メソッドを作っておき、そのメソッドを呼び出すまえに、必ず使用許可メソッドを契約的に呼び出して権限チェックをする方式、いいですね! こうすれば権限チェックコードがあちこちに散らばらずにコード自体をドキュメントとして使えそうです。
その方針でいこうかなと思います。ありがとうございます😊
の下のuserテーブルの表記が崩れているかもです!
下のように書き直せばいけそうですー!
プルリクエストが出せないようなので、コメントにて失礼しますー
ありがとうございます!
修正しますね!