Role authentication
Casbin is a powerful and efficient open source access control framework, and its rights management mechanism supports multiple access control models.
Official website document: https://casbin.org/
What is Casbin
Casbin can:
- supports custom request formats. the default request format is
{subject, object, action}
. - It has two core concepts: access control model model and policy policy.
- Support multi-layer role inheritance in RBAC, not only the main body can have roles, resources can also have roles.
- Supports built-in superusers such as
root
oradministrator
. Super users can perform any operation without an explicit permission declaration. - Supports a variety of built-in operators, such as
keyMatch
, to facilitate the management of path-based resources, such as/foo/bar
, which can be mapped to/foo *
Casbin cannot:
- For authentication authentication (that is, to verify the user's user name and password),Casbin is only responsible for access control. There should be other specialized components responsible for identity authentication, and then Casbin will perform access control. The two are in a coordinated relationship.
- Manage user lists or role lists. Casbin believes that it is more appropriate to manage the list of users and roles by the project itself. Users usually have their passwords, but Casbin's design idea is not to use it as a container for storing passwords. Instead, it stores the mapping relationship between users and roles in the RBAC scheme.
Note:
- Available after Midway v3.6.0
- Midway only encapsulates the Casbin API and provides simple support. For more information about how to write policy rules, see Official documentation.
- Casbin does not provide login, but only provides authentication for existing users. It needs to be used with components such as passport to obtain user information.
Related information:
Description | |
---|---|
Can be used for standard projects | ✅ |
Can be used for Serverless | ✅ |
Can be used for integration | ✅ |
Contains independent main framework | ❌ |
Contains independent logs | ❌ |
Installation dependency
$ npm i @midwayjs/casbin@3 --save
Or reinstall the following dependencies in package.json
.
{
"dependencies": {
"@midwayjs/casbin": "^3.0.0",
// ...
},
}
Enable components
First, introduce components and import them in configuration.ts
:
import { Configuration } from '@midwayjs/core';
import * as casbin from '@midwayjs/casbin';
import { join } from 'path'
@Configuration({
imports: [
// ...
casbin
],
importConfigs: [
join(__dirname, 'config')
]
})
export class MainConfiguration {
}
Prepare models and strategies
Before using Casbin, you need to define the model and policy. The contents of these two files run through this article. It is recommended to go to the official website to learn about the relevant contents.
Let's take a basic model as an example, such:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _,_
g2 = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act || r.sub == "root"
Save it in the basic_model.conf
file in the project root directory.
and a policy file containing the following.
p, superuser, user, read:any
p, manager, user_roles, read:any
p, guest, user, read:own
g, alice, superuser
g, bob, guest
g, tom, manager
g2, users_list, user
g2, user_roles, user
g2, user_permissions, user
g2, roles_list, role
g2, role_permissions, role
Save it in the basic_policy.csv
file in the project root directory.
Configure models and policies
Here our strategy will be demonstrated in file form.
The configuration is as follows:
import { MidwayAppInfo } from '@midwayjs/core';
import { join } from 'path';
export default (appInfo: MidwayAppInfo) => {
return {
// ...
casbin: {
modelPath: join(appInfo.appDir, 'basic_model.conf')
policyAdapter: join(appInfo.appDir, 'basic_policy.csv')
}
};
}
Authentication by decorator
There are many forms to use Casbin, here with a decorator as an example.
Define resources
First, define resources, for example, put them in the src/resource.ts
file, corresponding to the resources in the policy file.
export enum Resource {
USERS_LIST = 'users_list',
USER_ROLES = 'user_roles',
USER_PERMISSIONS = 'user_permissions',
ROLES_LIST = 'roles_list',
ROLE_PERMISSIONS = 'role_permission',
}
Configure how to obtain users
When using decorator authentication, we need to configure a way to obtain users. For example, after passport components, we will obtain the user name from ctx.user
.
import { MidwayAppInfo } from '@midwayjs/core';
import { join } from 'path';
export default (appInfo: MidwayAppInfo) => {
return {
// ...
casbin: {
modelPath: join(appInfo.appDir, 'basic_model.conf')
policyAdapter: join(appInfo.appDir, 'basic_policy.csv')
usernameFromContext: (ctx) => {
return ctx.user;
}
}
};
}
Add guards
Decorator authentication depends on guards, which can be turned on globally or on some routes. Please refer to the guard section for global guards.
For example, we only enable authentication on the following findAllUsers
methods, AuthGuard
the guards provided by @midwayjs/casbin
, which can be used directly.
import { Controller, Get, UseGuard } from '@midwayjs/core';
import { AuthGuard } from '@midwayjs/casbin';
import { Resource } from './resouce';
@Controller('/')
export class HomeController {
@UseGuard(AuthGuard)
@Get('/users')
async findAllUsers() {
// ...
}
}
Define permissions
Use the UsePermission
decorator to define the permissions required for routing.
import { Controller, Get, UseGuard } from '@midwayjs/core';
import { AuthActionVerb, AuthGuard, AuthPossession, UsePermission } from '@midwayjs/casbin';
import { Resource } from './resouce';
@Controller('/')
export class HomeController {
@UseGuard(AuthGuard)
@UsePermission({
action: AuthActionVerb.READ
resource: Resource.USER_ROLES
possession: AuthPossession.ANY
})
@Get('/users')
async findAllUsers() {
// ...
}
}
Users who do not have permission to read USER_ROLES
cannot call findAllUsers methods and will return 403 status codes when requesting.
For example, the above bob
user access will return 403, while the tom
user access will return normally.
UsePermission
need to provide an object parameter, including action
, resource
, possession
, and an optional isOwn
object.
action
is aAuthActionVerb
enumeration that includes read and write operations.resource
resource stringpossession
is aAuthPossession
enumerationIsOwn
is a function that acceptsContext
(the parameter of the guardcanActivate
) as a unique parameter and returns a boolean value.AuthZGuard
use it to determine whether the user is the owner of the resource. If it is not defined, the default function that returnsfalse
is used.
Multiple permissions can be defined at the same time, but the route can only be accessed if all permissions are satisfied.
For example:
@UsePermissions({
action: AuthActionVerb.READ
resource: 'USER_ADDRESS',
possession: AuthPossession.ANY
}, {
action; AuthActionVerb.READ
resource: 'USER_ROLES
possession: AuthPossession.ANY
})
The route can only be accessed if the user is granted the read USER_ADDRESS
and USER_ROLES
permissions.
API authentication
Casbin itself provides some common API and permission-related functions.
We can use it by injecting CasbinEnforcerService
services directly.
For example, we can code in guards or middleware.
import { CasbinEnforcerService } from '@midwayjs/casbin';
import { Guard, IGuard } from '@midwayjs/core';
@Guard()
export class UserGuard extends IGuard {
@Inject()
casbinEnforcerService: CasbinEnforcerService;
async canActivate(ctx, clz, methodName) {
// If the user is logged in and is a specific method, check the permissions
if (ctx.user && methodName === 'findAllUsers') {
return await this.casbinEnforcerService.enforce(ctx.user, 'USER_ROLES', 'read');
}
// Unlogged users are not allowed to access
return false;
}
}
After the guard is enabled, the effect is the same as the decorator above.
In addition, CasbinEnforcerService
have more APIs, such as reloading policies.
await this.casbinEnforcerService.loadPolicy();
Distributed policy storage
In scenarios where multiple machines are deployed, policies need to be stored externally.
Currently implemented adapters are:
- Redis
- Typeorm
Redis Adapter
You need to rely on the @midwayjs/casbin-redis-adapter
package and Redis components.
$ npm i @midwayjs/casbin-redis-adapter @midwayjs/redis --save
enable the redis component.
import { Configuration } from '@midwayjs/core';
import * as redis from '@midwayjs/redis';
import * as casbin from '@midwayjs/casbin';
import { join } from 'path';
@Configuration({
imports: [
// ...
redis
casbin
],
importConfigs: [
join(__dirname, 'config')
]
})
export class MainConfiguration {
}
Configure the Redis connection and casbin adapter.
import { MidwayAppInfo } from '@midwayjs/core';
import { join } from 'path';
import { createAdapter } from '@midwayjs/casbin-redis-adapter';
export default (appInfo: MidwayAppInfo) => {
return {
// ...
redis: {
clients: {
// Defines a connection for casbin
node-casbin-official ': {
host: '127.0.0.1',
port: 6379
password: '',
db: '0',
}
}
},
casbin: {
policyAdapter: createAdapter({
// The connection name above is configured
clientName: 'node-casbin-official'
}),
// ...
},
};
}
TypeORM Adapter
You need to rely on @midwayjs/casbin-typeorm-adapter
packages and typeorm components.
$ npm i @midwayjs/casbin-typeorm-adapter @midwayjs/typeorm --save
Enable typeorm components.
import { Configuration } from '@midwayjs/core';
import * as typeorm from '@midwayjs/typeorm';
import * as casbin from '@midwayjs/casbin';
import { join } from 'path';
@Configuration({
imports: [
// ...
typeorm
casbin
],
importConfigs: [
join(__dirname, 'config')
]
})
export class MainConfiguration {
}
Configure the adapter. Take sqlite storage as an example. You can view the typeorm components for mysql configuration.
import { MidwayAppInfo } from '@midwayjs/core';
import { join } from 'path';
import { CasbinRule, createAdapter } from '@midwayjs/casbin-typeorm-adapter';
export default (appInfo: MidwayAppInfo) => {
return {
// ...
typeorm: {
dataSource: {
// Defines a connection for casbin
node-casbin-official ': {
type: 'sqlite',
synchronize: true
database: join(appInfo.appDir, 'casbin.sqlite')
// Note that Entity is explicitly introduced here.
entities: [CasbinRule]
}
}
},
casbin: {
policyAdapter: createAdapter({
// The connection name above is configured
dataSourceName: 'node-casbin-official'
}),
// ...
}
};
}
Monitor
Use a distributed messaging system such as etcd to maintain consistency across multiple Casbin executor instances. Therefore, our users can use multiple Casbin executors at the same time to handle a large number of permission checking requests.
Midway currently only provides one Redis update strategy. If you have other needs, you can submit an issue to us.
Redis Watcher
It needs to depend on @midwayjs/casbin-redis-adapter
package and redis component.
$ npm i @midwayjs/casbin-redis-adapter @midwayjs/redis --save
Enable the redis component.
import { Configuration } from '@midwayjs/core';
import * as redis from '@midwayjs/redis';
import * as casbin from '@midwayjs/casbin';
import { join } from 'path';
@Configuration({
imports: [
// ...
redis,
casbin,
],
// ...
})
export class MainConfiguration {
}
Example usage:
import { MidwayAppInfo } from '@midwayjs/core';
import { join } from 'path';
import { createAdapter, createWatcher } from '@midwayjs/casbin-redis-adapter';
export default (appInfo: MidwayAppInfo) => {
return {
// ...
redis: {
clients: {
'node-casbin-official': {
host: '127.0.0.1',
port: 6379,
db: '0',
},
'node-casbin-sub': {
host: '127.0.0.1',
port: 6379,
db: '0',
}
}
},
casbin: {
// ...
policyAdapter: createAdapter({
clientName: 'node-casbin-official'
}),
policyWatcher: createWatcher({
pubClientName: 'node-casbin-official',
subClientName: 'node-casbin-sub',
})
},
};
}
Note that pub/sub connections require different clients, the code above defines two clients.
The pub client can be reused with common Redis client connections, while the sub requires an independent client.