GoWindGoWind
Home
Framework
GoWind Toolkit
  • Open Source

    • GoWind Admin (Admin Platform)
    • GoWind CMS (Content Management)
    • GoWind UBA (Behavior Analytics)
    • GoWind Shop (E-commerce)
    • GoWind Kanban (Kanban Boards)
    • GoWind Ledger (Personal Finance)
    • GoWind ShortPlay (Short Drama)
    • GoWind Ops (Operations Platform)
    • RushWind (Rust Ecosystem)
  • Open Core (Community + Enterprise)

    • GoWind OA (Office Collaboration)
    • GoWind ERP (Procure-to-Pay)
    • GoWind WMS (Warehousing)
  • Commercial

    • GoWind IM (Instant Messaging)
    • GoWind IoT (IoT Platform)
    • GoWind Edu (AI Education)
    • GoWind MES (Manufacturing Execution)
    • GoWind CDC (Data Pipeline)
    • GoWind SDP (Zero-Trust Gateway)
    • GoWind Quant (Quant Research)
    • GoWind Trade Radar (Export Sourcing)
    • GoWind Game Radar (Game Greenlight)
  • 简体中文
  • English
GitHub
Home
Framework
GoWind Toolkit
  • Open Source

    • GoWind Admin (Admin Platform)
    • GoWind CMS (Content Management)
    • GoWind UBA (Behavior Analytics)
    • GoWind Shop (E-commerce)
    • GoWind Kanban (Kanban Boards)
    • GoWind Ledger (Personal Finance)
    • GoWind ShortPlay (Short Drama)
    • GoWind Ops (Operations Platform)
    • RushWind (Rust Ecosystem)
  • Open Core (Community + Enterprise)

    • GoWind OA (Office Collaboration)
    • GoWind ERP (Procure-to-Pay)
    • GoWind WMS (Warehousing)
  • Commercial

    • GoWind IM (Instant Messaging)
    • GoWind IoT (IoT Platform)
    • GoWind Edu (AI Education)
    • GoWind MES (Manufacturing Execution)
    • GoWind CDC (Data Pipeline)
    • GoWind SDP (Zero-Trust Gateway)
    • GoWind Quant (Quant Research)
    • GoWind Trade Radar (Export Sourcing)
    • GoWind Game Radar (Game Greenlight)
  • 简体中文
  • English
GitHub
  • Introduction

    • GoWind Admin Product Introduction
    • GoWind Admin Installation Guide
    • Release Notes
  • Backend

    • Backend Architecture Overview
    • Backend Core Modules in Depth
    • Backend API & Protobuf Definitions
    • Backend Configuration & Deployment
    • Backend Extension Development
  • Frontend

    • Frontend Architecture Overview
    • Frontend Core Features in Depth
  • Secondary-Dev Tutorials

    • Backend Tutorial: Adding a New Business Module
    • Frontend Tutorial: Adding a New Business Page
    • Full-Stack Integration Tutorial
  • Advanced Tutorials

    • Lua Script Extension Tutorial
    • Permission System in Depth
    • Multi-Tenancy Architecture Tutorial
    • Task Scheduling and Asynchronous Processing
    • File Upload and Object Storage
    • Event Bus and Decoupled Architecture
    • Frontend Theming and Internationalization
    • Performance Optimization and Monitoring
    • SSE Real-Time Push
    • Login Policies and Security Hardening
    • Encryption Toolkit in Practice

Permission System in Depth

The permission system is one of the core capabilities of GoWind Admin. It supports three authorization engines — Casbin, OPA, and Zanzibar — and provides fine-grained access control. This tutorial explains the permission models, configuration methods, and practical scenarios in depth.

1. Permission Model Overview

1.1 Comparison of the Three Authorization Engines

FeatureCasbinOPA (Open Policy Agent)Zanzibar
TypeACL/RBAC/ABACPolicy engine (Rego language)Relationship-based authorization
Complexity⭐⭐ Simple⭐⭐⭐⭐ Complex⭐⭐⭐ Moderate
PerformanceHigh (in-memory evaluation)Medium (policies must be compiled)High (graph database)
Learning curveLowHighMedium
Use casesTraditional RBACComplex dynamic policiesSocial networks, collaboration platforms
Backend implementationpkg/authorizer/casbinpkg/authorizer/opapkg/authorizer/zanzibar

1.2 Permission Dimensions

The permission system of GoWind Admin covers three dimensions:

DimensionDescriptionExample
Menu permissionsControl which menus a user can seeThe "Article Management" menu is hidden from ordinary employees
API permissionsControl which APIs a user can callOnly administrators can delete users
Data permissionsControl which data ranges a user can accessA sales manager can only see data of their own department

2. The RBAC Model in Detail

2.1 Core Concepts

User → Role → Permission → Resource
  • User: a consumer of the system
  • Role: a collection of permissions (e.g. "Super Administrator", "Editor", "Visitor")
  • Permission: an operation grant on a resource (e.g. "article:create", "user:delete")
  • Resource: a protected object (e.g. a menu, an API endpoint, a data row)

2.2 Database Table Structure

-- User table
users (id, username, ...)

-- Role table
roles (id, name, code, ...)

-- Permission table
permissions (id, name, code, resource_type, resource_id, ...)

-- User-role association
user_roles (user_id, role_id)

-- Role-permission association
role_permissions (role_id, permission_id)

-- Menu table
menus (id, name, path, component, permission_code, ...)

2.3 Permission Code Conventions

Permission codes follow the resource:action naming convention:

Permission codeDescription
article:listView the article list
article:createCreate an article
article:updateUpdate an article
article:deleteDelete an article
user:viewView user information
user:editEdit a user
system:configSystem configuration

3. The Casbin Engine in Practice

Casbin is an open-source access control library that supports ACL/RBAC/ABAC and other models. It is used by Intel, IBM, Tencent Cloud, VMware, and other companies. Its core consists of three concepts:

  • Request: a (subject, object, action) triple, e.g. (bob, /users, GET)
  • Model: the decision rules defining how requests match policies
  • Policy: the mapping between users and roles, resources, and actions

3.1 Configure Casbin

Enable Casbin in auth.yaml:

authz:
  type: "casbin"
  
  casbin:
    model: |
      [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) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)

3.2 Policy Definitions

Casbin policies are stored in the database (the casbin_rule table):

p, admin, /admin/v1/article/*, (GET|POST|PUT|DELETE)
p, editor, /admin/v1/article/*, (GET|POST|PUT)
p, viewer, /admin/v1/article/*, GET

g, alice, admin
g, bob, editor
g, charlie, viewer

Explanation:

  • p lines define permission rules: subject, resource, action
  • g lines define role inheritance: which role a user belongs to

3.3 Using It in Code

import "github.com/casbin/casbin/v2"

// Check a permission
func CheckPermission(enforcer *casbin.Enforcer, user string, resource string, action string) bool {
    allowed, err := enforcer.Enforce(user, resource, action)
    if err != nil {
        log.Errorf("check permission failed: %v", err)
        return false
    }
    return allowed
}

// Usage example
if !CheckPermission(enforcer, "alice", "/admin/v1/article/123", "DELETE") {
    return errors.New("permission denied")
}

3.4 Loading Policies Dynamically

When permissions change, reload the policies:

// Add a policy
enforcer.AddPolicy("editor", "/admin/v1/article/*", "DELETE")

// Remove a policy
enforcer.RemovePolicy("viewer", "/admin/v1/article/*", "POST")

// Reload all policies
enforcer.LoadPolicy()

4. The OPA Engine in Practice

4.1 Configure OPA

Enable OPA in auth.yaml:

authz:
  type: "opa"
  
  opa:
    policy_path: "./policies"
    query: "data.authz.allow"

4.2 The Rego Policy Language

Create the policy file policies/authz.rego:

package authz

# Deny by default
default allow = false

# Administrators have all permissions
allow {
    input.user.roles[_] == "admin"
}

# Editors can create and update articles
allow {
    input.user.roles[_] == "editor"
    input.resource.type == "article"
    input.action in ["create", "update"]
}

# Visitors can only view
allow {
    input.user.roles[_] == "viewer"
    input.resource.type == "article"
    input.action == "view"
}

# Data permission: users can only view their own articles
allow {
    input.user.roles[_] == "author"
    input.resource.type == "article"
    input.action == "view"
    input.resource.author_id == input.user.id
}

4.3 Request Context

The input data structure OPA receives:

{
  "user": {
    "id": 123,
    "username": "alice",
    "roles": ["editor"]
  },
  "resource": {
    "type": "article",
    "id": 456,
    "author_id": 123
  },
  "action": "update"
}

4.4 Using It in Code

import "github.com/open-policy-agent/opa/rego"

// Create the query
query, err := rego.New(
    rego.Query("data.authz.allow"),
    rego.Input(input),
    rego.Load([]string{"./policies"}, nil),
).PrepareForEval(ctx)

// Evaluate
results, err := query.Eval(ctx)
if err != nil {
    return false, err
}

// Check the result
allowed := len(results) > 0 && len(results[0].Expressions) > 0 && 
           results[0].Expressions[0].Value == true

5. The Zanzibar Engine in Practice

5.1 Configure Zanzibar

Enable Zanzibar in auth.yaml (using Keto as an example):

authz:
  type: "zanzibar"
  
  zanzibar:
    type: "keto"
    
    keto:
      write_url: "http://keto:4466"
      read_url: "http://keto:4466"
      use_grpc: true

5.2 Relationship Tuples

Zanzibar is based on relationship tuples:

(user:Alice, is_member_of, group:Admins)
(group:Admins, can_access, resource:Dashboard)
(article:123, owned_by, user:Bob)

5.3 Keto Configuration

Create the Keto namespace configuration namespaces.yaml:

namespaces:
  - name: user
    relations:
      member: {}
  
  - name: article
    relations:
      owner:
        types:
          - this
      viewer:
        types:
          - this
          - computedUserset:
              relation: owner
  
  - name: organization
    relations:
      member:
        types:
          - this
      admin:
        types:
          - this

5.4 Using It in Code

import "github.com/ory/keto-client-go"

// Check an access permission
func CheckAccess(ketoClient *keto.Client, subject string, relation string, object string) bool {
    resp, err := ketoClient.Check(context.Background(), &keto.CheckRequest{
        Namespace: "article",
        Object: object,
        Relation: relation,
        Subject: &keto.Subject{
            Id: subject,
        },
    })
    
    if err != nil {
        log.Errorf("check access failed: %v", err)
        return false
    }
    
    return resp.Allowed
}

// Usage example
if !CheckAccess(ketoClient, "Alice", "viewer", "article:123") {
    return errors.New("access denied")
}

6. Data Permission Implementation

6.1 Data Permission Scopes

GoWind Admin supports five data permission scopes:

ScopeConstantDescription
Self onlySELFCan only view data created by the user themselves
Own departmentUNIT_ONLYCan only view data of the user's own department
Own department and sub-departmentsUNIT_AND_CHILDView data of the user's department and all its sub-departments
Selected departmentsSELECTED_UNITSView data of specified departments
AllALLView all data

6.2 How It Works

Data permissions are implemented through the Viewer mechanism of the Ent ORM:

// Create a user-level Viewer
viewer := entgo.NewUserViewerContext(ctx, userID, dataScope)

// The filter is applied automatically at query time
articles, err := client.Article.Query().
    Where(article.HasAuthorWith(user.ID(userID))).
    All(viewer)

6.3 Configuring Data Permissions for a Role

In the admin management system:

  1. Go to Permission Management → Role Management
  2. Select a role and click "Set Data Permissions"
  3. Choose the data scope:
    • If "Selected departments" is chosen, the specific departments must be checked
  4. Save

6.4 Obtaining Data Permissions in Code

// Get the current user's data permission scope from the Context
dataScope := auth.GetDataScopeFromContext(ctx)

switch dataScope {
case identityV1.DataScope_SELF:
    query = query.Where(article.AuthorID(userID))
    
case identityV1.DataScope_UNIT_ONLY:
    query = query.Where(article.HasAuthorWith(
        user.HasOrgUnitsWith(orgunit.ID(orgUnitID)),
    ))
    
case identityV1.DataScope_ALL:
    // No filtering
    
// ...
}

7. Frontend Permission Control

7.1 Route-Level Permissions

Register routes dynamically according to user permissions:

// router/access.ts
import { useAccessStore } from '@vben/stores';

export function generateAccessRoutes(menus: Menu[]) {
  const routes: RouteRecordRaw[] = [];
  
  menus.forEach(menu => {
    if (menu.component) {
      routes.push({
        path: menu.path,
        component: () => import(`@/views/${menu.component}.vue`),
        meta: {
          title: menu.title,
          permission: menu.permission_code,
        },
      });
    }
  });
  
  return routes;
}

7.2 Menu-Level Permissions

Filter the menu display according to permissions:

// stores/modules/permission.ts
export const usePermissionStore = defineStore('permission', () => {
  const accessibleMenus = ref<Menu[]>([]);
  
  function setAccessibleMenus(menus: Menu[]) {
    // Filter out menus without permission
    accessibleMenus.value = filterMenusByPermission(menus);
  }
  
  return { accessibleMenus, setAccessibleMenus };
});

7.3 Component-Level Permissions

Control button visibility via a directive or a component:

<script setup>
import { useAccess } from '@vben/access';

const { hasAccessByCodes } = useAccess();
</script>

<template>
  <!-- Method 1: the v-if directive -->
  <Button v-if="hasAccessByCodes(['article:create'])" type="primary">
    新建
  </Button>
  
  <!-- Method 2: the permission component -->
  <AccessControl codes="['article:delete']">
    <Button danger>删除</Button>
  </AccessControl>
</template>

7.4 Permission Composable

// composables/usePermission.ts
export function usePermission() {
  const accessStore = useAccessStore();
  
  function hasPermission(code: string): boolean {
    return accessStore.accessCodes.includes(code);
  }
  
  function hasAnyPermission(codes: string[]): boolean {
    return codes.some(code => hasPermission(code));
  }
  
  function hasAllPermissions(codes: string[]): boolean {
    return codes.every(code => hasPermission(code));
  }
  
  return { hasPermission, hasAnyPermission, hasAllPermissions };
}

8. Permission Audit Logs

8.1 Recording Permission Changes

An audit log is recorded for every permission change:

// Record a log when adding role permissions
func (s *RoleService) AddPermissions(ctx context.Context, req *AddPermissionsRequest) error {
    // Perform the addition
    err := s.repo.AddPermissions(ctx, req.RoleId, req.PermissionIds)
    if err != nil {
        return err
    }
    
    // Record the audit log
    auditLogRepo.Create(ctx, &permissionV1.PermissionAuditLog{
        Action:      "add_permissions",
        RoleId:      req.RoleId,
        PermissionIds: req.PermissionIds,
        OperatorId:  auth.GetUserID(ctx),
        CreatedAt:   time.Now(),
    })
    
    return nil
}

8.2 Viewing Permission Audit Logs

In the admin management system:

  1. Go to Log Management → Permission Audit Logs
  2. You can see all permission change records:
    • Operator
    • Operation time
    • Operation type (add/remove permission)
    • Involved roles and permissions

9. Performance Optimization

9.1 Caching Permission Data

Cache user permissions in Redis:

// Get user permissions (with caching)
func GetUserPermissions(ctx context.Context, userID uint32) ([]string, error) {
    cacheKey := fmt.Sprintf("user:%d:permissions", userID)
    
    // Try to get it from the cache
    cached, err := redis.Get(ctx, cacheKey).Result()
    if err == nil {
        var permissions []string
        json.Unmarshal([]byte(cached), &permissions)
        return permissions, nil
    }
    
    // Query the database
    permissions, err := queryPermissionsFromDB(userID)
    if err != nil {
        return nil, err
    }
    
    // Write to the cache (TTL: 1 hour)
    data, _ := json.Marshal(permissions)
    redis.Set(ctx, cacheKey, data, time.Hour)
    
    return permissions, nil
}

9.2 Batch Permission Checks

Avoid calling the permission check API multiple times:

// ❌ Wrong: checking in a loop
for _, resource := range resources {
    if !CheckPermission(user, resource, "view") {
        return errors.New("permission denied")
    }
}

// ✅ Correct: get all permissions at once
permissions := GetUserPermissions(user)
permissionSet := make(map[string]bool)
for _, p := range permissions {
    permissionSet[p] = true
}

for _, resource := range resources {
    if !permissionSet[resource] {
        return errors.New("permission denied")
    }
}

10. Common Issues

Q1: How do I choose the right authorization engine?

  • Casbin: suitable for traditional RBAC scenarios; simple to configure with good performance
  • OPA: suitable for complex dynamic policies, such as permission decisions based on time, location, device, and other dimensions
  • Zanzibar: suitable for scenarios requiring complex relationship graphs, such as social networks and collaboration platforms

Q2: When do permission changes take effect?

  • Menu permissions: take effect after the user logs in again (or when the frontend refreshes permissions proactively)
  • API permissions: take effect immediately (Casbin policies are updated in real time)
  • Data permissions: take effect on the next query

Q3: How do I implement permission inheritance?

Through role inheritance:

Super Administrator → Administrator → Editor → Visitor

In Casbin:

g, admin, super_admin
g, editor, admin
g, viewer, editor

Q4: How do I handle cross-tenant permissions?

In multi-tenancy mode, permission isolation is achieved in the following ways:

  1. Tenant-level roles: each tenant has its own roles and permissions
  2. Data isolation: a tenant_id filter is added automatically at query time
  3. Cross-tenant access: requires a special permission code (e.g. cross_tenant:view)

11. Related Documentation

  • Backend Core Modules in Detail
  • Full-Stack Integration Tutorial
  • Lua Script Extension Tutorial
Edit this page
Last Updated:: 11/3/26, 2:14 PM
Contributors: Bobo
Prev
Lua Script Extension Tutorial
Next
Multi-Tenancy Architecture Tutorial