「スパゲッティ認可」という名の悪夢
最初のGoプロジェクトは、if-elseによる権限チェックの山で危うく失敗しそうになりました。最初は簡単でした。ユーザーが管理者(admin)なら記事の編集を許可するだけです。しかし、システムが10,000ユーザーに達した頃、クライアントから「厄介な」要求が届き始めました。
エディター、モデレーター、さらにはゲストといったロールの追加です。最も厄介だったのは、「エディターは自分の記事のみ編集可能」や「モデレーターは午前8時から午後5時までの間のみコメントを削除できる」といった条件でした。わずか2週間で、コードは権限チェックのロジックで溢れかえりました。小さなルール変更のたびに、数十のファイルを探し回り、コードを修正して再ビルドしなければなりませんでした。これこそが、多くの開発者が陥りがちな「ハードコードされた認可(Hardcoded Authorization)」の罠です。
なぜ自作の権限コードはスケール時に限界が来るのか?
1週間かけてリファクタリングした結果、自前で構築したシステムがすぐに限界を迎える3つの理由が見えてきました:
- ロジックの密結合(Tight Coupling): 権限管理がビジネスロジックの中に散在してしまいます。ロール構造を変更したい場合、至る所のコードを修正しなければなりません。
- ABACの実装が困難: RBAC(ロールベースアクセス制御)は比較的簡単です。しかし、ABAC(属性ベースアクセス制御 – 例:記事の所有者のみが編集可能)が必要になると、
if-elseの山はまさに迷宮と化します。 - セキュリティリスク: チームの各メンバーが独自のやり方で権限チェックを行うと、一瞬の油断がハッカーに悪用される脆弱性を生むことになります。
最適なソリューションを求めて
私は「正解」に辿り着く前に、3つの選択肢を検討しました:
- 自前でDBテーブルを作成:
rolesやpermissionsテーブルを作成する方法。権限の継承(Role Hierarchy)の処理が非常に難しく、メンテナンスコストも高くなります。 - シンプルなミドルウェア: ルーターレベルでのブロックは可能ですが、リソースごとの詳細な(fine-grained)権限管理には対応できません。
- Casbinの利用: PERMモデル(Policy, Effect, Request, Matchers)をサポートする特化型ライブラリです。これが最もプロフェッショナルなアプローチです。
Casbin – 「ルール」と「データ」の分離
50以上のAPIエンドポイントを持つECサイトのプロダクション環境でCasbinを6ヶ月間運用した結果、これが正しい決断だったと確信しています。Casbinの最大の利点は、メカニズム(Mechanism)とポリシー(Policy)を分離できることです。
PERMモデル:Casbinの背骨
model.confファイルを一度定義するだけで済みます。これは以下の4つのコンポーネントを通じてシステムの動作を規定します:
- Request (r): 誰が(sub)どのリソース(obj)に対して何(act)をしようとしているか?
- Policy (p): 許可ルールのリスト。
- Matchers (m): リクエストとポリシーを照合するための数式。
- Effect (e): 最終的な決定(例:1つでも一致するルールがあれば許可する)。
Goでの実践的な実装
ライブラリのインストールは非常に簡単です:
go get github.com/casbin/casbin/v2
以下は、権限の継承をサポートするRBACシステムのmodel.confファイルです:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
Goのコードでアクセス権限をチェックする方法:
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
)
func main() {
e, _ := casbin.NewEnforcer("model.conf", "policy.csv")
sub := "alice" // ユーザー
obj := "data1" // リソース
act := "read" // アクション
ok, err := e.Enforce(sub, obj, act)
if err != nil {
fmt.Println("システムエラー")
} else if ok {
fmt.Println("アクセス成功!")
} else {
fmt.Println("この権限がありません!")
}
}
複雑なポリシー構造を扱う際、私はよく toolcraft.app を使ってJSONの整形やデータのクイックテストを行っています。これにより、VS Codeが重くなるような拡張機能を入れすぎるのを防げます。
ABACへのアップグレード:真のパワー
「記事の所有者のみが編集可能」というルールにしたい場合でも、Goのコードを修正する必要はありません。設定ファイルのMatcherを更新するだけです:
[matchers]
m = r.sub == r.obj.Owner || r.sub == "admin"
このカスタマイズ性により、ビジネスロジックを汚すことなく、クライアントからのどんな「特殊な」要求にも対応できます。
Casbinを使う際の血の滲むような教訓
半年間の運用の末、いくつかの実戦的な経験を得ました:
- プロダクション環境でCSVは使わない: Gorm Adapterを使用して、ポリシーをPostgreSQLやMySQLに保存しましょう。CSVファイルはクイックテストやデモにのみ適しています。
- キャッシュは不可欠: 権限チェックはすべてのリクエストで行われます。ポリシーが数千行に達する場合は、Redisを使用して結果をキャッシュしてください。レスポンス速度は1ms未満を目指すべきです。
- コメントを惜しまない:
model.confファイルには詳細な注釈を入れましょう。信じてください、コメントがないと3ヶ月後に戻ってきたとき、自分が書いたMatcherが何を意味しているのか分からなくなります。
終わりに
Casbinはすべての問題に対する万能薬ではありませんが、Goで権限管理を処理するための最も強力なツールです。セキュリティロジックを完全に分離することで、開発チームと運用チームがコードを読まずとも相互理解を深めることができます。プロジェクトが複雑になり始めたら、ぜひ今日からCasbinを試してみてください。

