Casbin
認証ライブラリ
Casbin
概要
CasbinはACL、RBAC、ABACなどの様々なアクセス制御モデルをサポートするマルチ言語対応の認可ライブラリです。柔軟なポリシー定義とモデル設定により、複雑な権限管理要件に対応できます。Go、Python、Java、Node.js、Rust等20以上の言語をサポートし、Webアプリケーション、マイクロサービス、クラウドサービスの認可システムとして広く採用されています。2025年現在も活発に開発が続けられています。
詳細
Casbinは様々なアクセス制御モデルを統一的に扱える認可ライブラリです。以下の主な特徴があります:
- 複数のアクセス制御モデル: ACL、RBAC、ABAC、RESTful、拒否/許可優先などをサポート
- 柔軟なポリシー管理: CSV、データベース、クラウドストレージでのポリシー保存
- モデル設定: INI形式でのアクセス制御ルールとマッチャーの定義
- 多言語サポート: 20以上のプログラミング言語での実装
- Webフレームワーク統合: Express、Flask、Django、Spring Boot等との連携
- 分散システム対応: マイクロサービスやクラウド環境での認可管理
メリット・デメリット
メリット
- 複数のアクセス制御モデルを統一的に扱える柔軟性
- 豊富な言語サポートによりマルチ言語環境で一貫した認可システム
- モデル駆動設計による分かりやすいポリシー管理
- Webフレームワークとの豊富な統合オプション
- 大規模システムでの実績と信頼性
- アクティブなコミュニティと継続的な開発
デメリット
- 初期学習コストが高く、設定が複雑になる場合がある
- 高度な機能を使う場合はCasbinの概念の理解が必要
- 小規模アプリケーションには機能過多の場合がある
- パフォーマンスを重視する場合は最適化が必要
- モデル設計を間違えると運用が困難になる
参考ページ
書き方の例
基本的なACLモデルの設定
# model.conf - ACLモデル定義
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
# policy.csv - ACLポリシー
p, alice, data1, read
p, bob, data2, write
p, charlie, data1, read
p, charlie, data1, write
Go言語でのCasbin使用
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
)
func main() {
// Enforcerの初期化
e, err := casbin.NewEnforcer("model.conf", "policy.csv")
if err != nil {
panic(err)
}
// 権限チェック
res, err := e.Enforce("alice", "data1", "read")
if err != nil {
panic(err)
}
fmt.Printf("Alice can read data1: %t\n", res) // true
res, err = e.Enforce("alice", "data2", "read")
if err != nil {
panic(err)
}
fmt.Printf("Alice can read data2: %t\n", res) // false
// 動的ポリシー追加
e.AddPolicy("alice", "data2", "read")
res, err = e.Enforce("alice", "data2", "read")
if err != nil {
panic(err)
}
fmt.Printf("Alice can read data2 after adding policy: %t\n", res) // true
// ポリシー削除
e.RemovePolicy("alice", "data2", "read")
}
RBACモデルの実装
# rbac_model.conf - RBACモデル定義
[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
# rbac_policy.csv - RBACポリシー
p, admin, data1, read
p, admin, data1, write
p, admin, data2, read
p, admin, data2, write
p, user, data1, read
g, alice, admin
g, bob, user
g, charlie, user
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
)
func main() {
// RBACモデルでEnforcerを初期化
e, err := casbin.NewEnforcer("rbac_model.conf", "rbac_policy.csv")
if err != nil {
panic(err)
}
// ロールベースの権限チェック
res, _ := e.Enforce("alice", "data1", "write")
fmt.Printf("Alice (admin) can write data1: %t\n", res) // true
res, _ = e.Enforce("bob", "data1", "write")
fmt.Printf("Bob (user) can write data1: %t\n", res) // false
// ロール管理API
roles, _ := e.GetRolesForUser("alice")
fmt.Printf("Alice's roles: %v\n", roles) // [admin]
users, _ := e.GetUsersForRole("admin")
fmt.Printf("Admin users: %v\n", users) // [alice]
// 動的ロール割り当て
e.AddRoleForUser("charlie", "admin")
res, _ = e.Enforce("charlie", "data2", "write")
fmt.Printf("Charlie (now admin) can write data2: %t\n", res) // true
}
ABACモデルでの属性ベースアクセス制御
# abac_model.conf - ABACモデル定義
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub.Department == r.obj.Owner
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
)
type Subject struct {
Name string
Department string
}
type Object struct {
Name string
Owner string
}
func main() {
// ABACモデルでEnforcerを初期化
e, err := casbin.NewEnforcer("abac_model.conf")
if err != nil {
panic(err)
}
// 属性ベースの権限チェック
sub := Subject{Name: "alice", Department: "engineering"}
obj := Object{Name: "project1", Owner: "engineering"}
res, _ := e.Enforce(sub, obj, "read")
fmt.Printf("Alice (engineering) can access engineering project: %t\n", res) // true
obj2 := Object{Name: "project2", Owner: "marketing"}
res, _ = e.Enforce(sub, obj2, "read")
fmt.Printf("Alice (engineering) can access marketing project: %t\n", res) // false
}
Webアプリケーションでのミドルウェア統合
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/casbin/casbin/v2"
)
func NewCasbinMiddleware(enforcer *casbin.Enforcer) gin.HandlerFunc {
return func(c *gin.Context) {
// ユーザー情報を取得(JWTトークンやセッションから)
user := getUserFromToken(c.GetHeader("Authorization"))
// リソースとアクションを特定
resource := c.Request.URL.Path
action := c.Request.Method
// 権限チェック
allowed, err := enforcer.Enforce(user, resource, action)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Authorization check failed"})
c.Abort()
return
}
if !allowed {
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
c.Abort()
return
}
c.Next()
}
}
func main() {
// Casbinの初期化
enforcer, _ := casbin.NewEnforcer("model.conf", "policy.csv")
r := gin.Default()
// 認可ミドルウェアを適用
authorized := r.Group("/api", NewCasbinMiddleware(enforcer))
{
authorized.GET("/users", getUsers)
authorized.POST("/users", createUser)
authorized.DELETE("/users/:id", deleteUser)
}
r.Run(":8080")
}
func getUserFromToken(token string) string {
// JWTトークンからユーザー情報を抽出
// 実装詳細は省略
return "alice"
}
func getUsers(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"users": []string{"alice", "bob"}})
}
func createUser(c *gin.Context) {
c.JSON(http.StatusCreated, gin.H{"message": "User created"})
}
func deleteUser(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "User deleted"})
}
ドメイン・テナント分離のRBACモデル
# rbac_with_domains_model.conf
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
# rbac_with_domains_policy.csv
p, admin, tenant1, data1, read
p, admin, tenant1, data1, write
p, admin, tenant2, data2, read
p, user, tenant1, data1, read
g, alice, admin, tenant1
g, bob, user, tenant1
g, charlie, admin, tenant2
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
)
func main() {
// ドメイン対応RBACモデルでEnforcer初期化
e, err := casbin.NewEnforcer("rbac_with_domains_model.conf", "rbac_with_domains_policy.csv")
if err != nil {
panic(err)
}
// ドメイン別権限チェック
res, _ := e.Enforce("alice", "tenant1", "data1", "write")
fmt.Printf("Alice can write data1 in tenant1: %t\n", res) // true
res, _ = e.Enforce("alice", "tenant2", "data2", "read")
fmt.Printf("Alice can read data2 in tenant2: %t\n", res) // false
// ドメイン別ロール管理
roles, _ := e.GetRolesForUserInDomain("alice", "tenant1")
fmt.Printf("Alice's roles in tenant1: %v\n", roles) // [admin]
// クロステナントアクセスの防止を確認
res, _ = e.Enforce("bob", "tenant2", "data2", "read")
fmt.Printf("Bob (tenant1 user) can read tenant2 data: %t\n", res) // false
}
データベースを使ったポリシー管理
package main
import (
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v3"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func main() {
// データベース接続
db, err := gorm.Open(postgres.Open("postgres://user:password@localhost/casbin_db"), &gorm.Config{})
if err != nil {
panic(err)
}
// Gorm Adapterを使用
adapter, err := gormadapter.NewAdapterByDB(db)
if err != nil {
panic(err)
}
// Enforcerをデータベースアダプターで初期化
e, err := casbin.NewEnforcer("model.conf", adapter)
if err != nil {
panic(err)
}
// ポリシーをデータベースにロード
e.LoadPolicy()
// 動的ポリシー管理(データベースに永続化)
e.AddPolicy("alice", "data1", "read")
e.AddPolicy("bob", "data2", "write")
// ポリシーを保存
e.SavePolicy()
// 権限チェック
res, _ := e.Enforce("alice", "data1", "read")
fmt.Printf("Alice can read data1: %t\n", res) // true
}