授权,也被称作访问控制,是管理访问资源的过程。也就是说,在一个应用中控制谁能访问什么。

授权检查的例子有:这个用户是否被允许查看这个页面,编辑这条数据,看到这个按钮,或者打印到打印机?这些都是关于一个用户是否有权限访问的决定。

Elements of Authorization 授权的要素

授权有三个核心要素,在Shiro中用的很多:permissions,roles,users。

Permissions 权限

Permissions在Apache Shiro中是安全策略的最小原子元素。它们是对行为描述的最基本语句,明确地表示了在一个应用中能做什么。一个格式良好的permission语句本质上描述了资源和当一个主体和这些资源进行交互时可能采取什么样的动作。

permission语句的一些例子:

大部分资源都支持经典的CRUD(增查改删)动作,但是只要是对于一个具体的资源类型有意义的动作都是可以的。最基本的想法就是,permission语句至少是基于资源和动作的。

当查看权限时,可能要认识到的最重要的一点就是,permission语句并不表示谁可以执行某种行为,只是描述了在应用中什么事情可以做。

权限只代表了行为

权限只反映了行为(行为就是关联了某种资源类型的动作)。它们不反应谁能执行这些行为。

定义谁(用户)被允许做什么(权限),基本上可以看做是分配权限给用户的操作。这经常有应用的数据模型来处理,在不同的应用中可能有很大的不同。

例如,权限可以被归组到角色,角色又可以关联到一个或多个用户对象。或者有些用户可以有用户组,用户组可以关联角色,通过传递关联,将角色中的所有权限间接地授权给了用户组中的所有用户。

将权限授权给用户有很多不同的方法——应用将根据自身需求来决定怎么建模。

等下我们将介绍Shiro怎么决定一个主体是否被允许做某事。

Permission Granularity 权限粒度

上面的几个权限例子全都指定了在某个资源类型(门,文件,用户,等等)上采取什么动作(打开,查看,删除,等等)。在某些情况下,它们甚至指定非常细粒度(实例水平)的行为——例如,“删除”(动作)一个叫“jsmith”(实例标识)的“用户”(资源类型)。在Shiro中,你有能力去准确定义任何细粒度的权限语句。

我们将在Shiro的 Permissions Documentation 中介绍关于权限粒度和权限语句水平的更多详细信息。

Roles 角色

角色是一个被特定命名的、代表了一个行为或责任集合的实体。这些行为最终被转化成在一个软件应用中可以做或不可以做的事。角色通常被分配给用户账户,由此关联,用户能“做 ”某事就归因于他是否拥有各种各样的角色。

角色有两种类型,Shiro都支持:
     
潜在的脆弱的安全

虽然是更简单和最通用的做法,隐式角色可能会强加需要软件维护和管理的问题。

例如,如果你只是想增加或移除一个角色,或者想在后面重新定义一个角色的行为时会怎样?你必须回去找你的源代码,并修改所有的角色检查代码来应对你的安全模型的变化,而且每次变化都要这么搞!更不提这将招致额外的运营成本(重新测试,通过QA,关闭应用,因为新的角色检查升级软件,重启应用,等等)。

这在简单应用中没多大问题(比如只有一个“admin”角色和“everyone else”角色的话),但是对于更多的复杂的或可配置的应用来说,在你的应用的生命周期内这将是一个非常非常重大的问题,并且将造成一笔非常大的维护费用。


Shiro团队提倡使用权限和显式角色来代替隐式角色这种更老的办法。你将会得到好得多的控制你的应用安全的体验。

基于资源的访问控制

请阅读Les Hazlewood的文章, The New RBAC: Resource-Based Access Control ,它深入地探讨了使用权限和显式角色(及其对源代码所带来的积极影响 )来代替更老的隐式角色的办法所带来的好处。

Users 用户

用户本质上是指使用应用的“谁”。正如前面所提到的,主体(Subject)就是Shiro中的“用户”的概念。

通过关联角色或直接关联权限,用户在应用中被允许执行某些动作。你应用的数据模型精确地定义了一个主体是怎么被允许做某事或不允许做某事的。

例如,在你的数据模型中,你也许有一个实际的用户类,并且你直接分配权限给用户实例。或者你可能只直接给角色分配权限,然后再把角色分配给用户,通过联系,用户间接地拥有了分配给角色的那些权限。或许你可以用“组”的概念来表示这些东西,这随你——使用对你的应用程序有意义的模型就行了。

你的数据模型完全决定了授权将怎样起作用,Shiro依赖一个 Realm 实现把你的数据模型关联详细翻译成Shiro可以理解的格式。我们稍后将介绍Realm是怎么做的。

最终,你的Realm实现是和你的数据源(RDBMS, LDAP, 等等)进行通信,所以你的Realm就是告诉Shiro存不存在某角色或权限的东西。你可以完全控制你的授权模型的结构和定义。

Authorizing Subjects 授权中的主体

在Shiro里面执行授权有3种办法:


Programmatic Authorization 硬编码授权

也许执行授权最简单和最通用的方法就是通过直接编码的方式和当前主体实例进行交互。

Role-Based Authorization 基于角色授权

如果你想基于更简单的/更传统的隐式角色名来进行访问控制,你可以执行角色检查:

Role Checks

如果你只是想简单地检查当前用户是否拥有某个角色,你可以调用Subject实例的hasRole*系列方法。

例如,看某个主体是否有某个特定(单个)的角色,你可以调用 subject. hasRole(roleName)  方法,并且做出相应反应:

Subject currentUser = SecurityUtils.getSubject();

if (currentUser.hasRole("administrator")) {
    //show the admin button
} else {
    //don't show the button?  Grey it out?
}

Subject类有一些跟角色相关的方法供你使用,你可以根据需要选择:

Subject方法
描述
如果Subject实例被分配了这个指定的role,就返回true,否则返回false
返回一个boolean数组,分别对应每个角色的检查结果。当需要检查多个角色的时候是一个很有用的增强性能的操作(例如,当自定义了一个复杂的视图)
如果Subject实例被分配了所有集合中指定的role,返回true,否则返回false

Role Assertions 角色断言

还有一个检查主体是否拥有某个角色的并返回boolean值的方法,在逻辑执行之前,你可以先简单地断言他们是否拥有一个预期的角色。如果主体没有这个角色,该方法将抛出一个  AuthorizationException  异常。如果有这个预期的角色,断言将会静默地执行通过,后面的逻辑代码也将预期执行。

例如:

Subject currentUser = SecurityUtils.getSubject();

//guarantee that the current user is a bank teller and
//therefore allowed to open the account:
currentUser.checkRole("bankTeller");
openBankAccount();

相对于使用hasRole*方法,用这个方法可以使代码更加整洁,因为在当前主体不符合预期的条件下你不必自己创建AuthorizationExceptions异常(如果你不想创建的话)。

Subject类有一些跟角色相关的断言方法供你使用,你可以根据需要选择:

Subject方法
描述
如果Subject实例被分配了指定的role,就静默地返回,否则抛出一个AuthorizationException异常
如果Subject实例被分配了所有集合中指定的role,就静默地返回,否则抛出一个AuthorizationException异常
效果跟上面的方法一样,但是允许使用Java5的可变参数个数风格

Permission-Based Authorization 基于权限授权

正如我们上面对角色的概述一样,基于权限的授权来执行访问控制通常是一种更好的方式。基于权限授权,因为它与你的应用的原始功能(基于应用核心资源之上的行为)密切相关,当你的功能发生变化,基于权限授权的源代码才会改变,而不会因为一次安全策略的改变而改变。这意味着相比于基于角色授权代码来说,代码受冲击的频率要大大减小。

Permission Checks 权限检查

如果你想检查一个主体是否被允许做某事,你可以调用isPermitted*方法的任何变体。检查权限有两个主要的方法——基于对象的权限实例或者用代表权限的字符串。

Object-based Permission Checks 基于对象的权限检查

执行权限检查的一种可能方式就是,初始化一个Shiro的  org.apache.shiro.authz.Permission  接口实例,并且将它传入接受权限实例的isPermitted*方法。

例如,考虑以下情况:办公室有一个打印机,这个打印机有一个独特标识laserjet4400n,在允许他们按“print”按钮之前,我们的软件需要检查当前用户是否被允许在那台打印机上打印文档。权限检查有可能看起来是这种形式的:

Permission printPermission = new PrinterPermission("laserjet4400n", "print");

Subject currentUser = SecurityUtils.getSubject();

if (currentUser.isPermitted(printPermission)) {
    //show the Print button
} else {
    //don't show the button?  Grey it out?
}

在上面的例子中,我们也看到了一个非常强大的在实例级别进行访问控制的例子——在个别数据实例上约束行为的能力。

在下面的几种情形中基于对象的权限非常有用:

Subject类有一些跟权限相关的方法供你使用,你可以根据需要选择:

Subject方法
描述
如果通过指定的权限实例的综述,主体被允许执行某个动作或访问某个资源,则返回true,否则返回false
返回一个boolean数组,分别对应每个权限的检查结果。当需要检查多个权限的时候是一个很有用的增强性能的操作(例如,当自定义了一个复杂的视图)
如果Subject实例被许可了集合中所有指定的权限,返回true,否则返回false

String-based permission checks 基于字符串的权限检查

虽然基于对象的权限十分有用(编译期类型安全,确保行为正确,自定义包含逻辑,等等),但是对于很多应用来说有时显得很“笨重”。还有一种办法就是使用普通字符串来表示权限实例。

例如,沿用上面的打印权限示例,我们可以用基于字符串的权限检查重新设计一个相同效果的检查:

Subject currentUser = SecurityUtils.getSubject();

if (currentUser.isPermitted("printer:print:laserjet4400n")) {
    //show the Print button
} else {
    //don't show the button?  Grey it out?
}

这个例子仍然展示了实例级别的权限检查,但是权限的重要部分——打印机(资源类型),打印(action)和laserjet4400n(实例id)——都由字符串来表示。

这个具体的例子展示了由Shiro的默认 org.apache.shiro.authz.permission.WildcardPermission 实现定义的一种由冒号分隔的特殊格式,大多数人都会觉得这种格式用起来很恰当。

也就是说,上面的代码块就是(很大程度上可以这么认为)下面这个代码块的简短写法:

Subject currentUser = SecurityUtils.getSubject();

Permission p = new WildcardPermission("printer:print:laserjet4400n");

if (currentUser.isPermitted(p) {
    //show the Print button
} else {
    //don't show the button?  Grey it out?
}

WildcardPermission的令牌格式和生成选项在Shiro的 Permission documentation 文档里面有讨论。

虽然上面的字符串的格式都默认为WildcardPermission格式,但是实际上你可以创造自己的字符串格式,并且随你喜欢用哪种。我们将在下面的Realm授权章节讨论该怎么定义自己的字符串格式。

基于字符串的权限是有益的,它不强制要求你实现一个接口,并且简单的字符串易于阅读。不利的是,你没有类型安全,如果你需要更复杂的行为,而这种行为超出了字符串所能表示的范围,这时你就会想基于权限接口实现自己的权限对象。在实践中,大多数Shiro的最终用户选择更简单的基于字符串的方法,但最终还是由你的应用的需求来决定那种方法更好。

像基于对象的权限检查方法一样,主体也有基于字符串权限检查的方法变体:

Subject方法
描述
如果通过指定的字符串权限的综述,主体被允许执行某个动作或访问某个资源,则返回true,否则返回false
返回一个boolean数组,分别对应每个权限的检查结果。当需要检查多个权限的时候是一个很有用的增强性能的操作(例如,当自定义了一个复杂的视图)
如果Subject实例被许可了参数中所有指定的权限,返回true,否则返回false

Permission Assertions 权限断言

还有一个检查主体是否被允许做某事并返回boolean值的方法,在逻辑执行之前,你可以先简单地断言他们是否拥有一个预期的权限。如果主体没有这个权限,就抛出一个AuthorizationException异常。如果有这个预期的权限,断言将会静默地执行通过,后面的逻辑代码也将预期执行。

例如:

Subject currentUser = SecurityUtils.getSubject();

//guarantee that the current user is permitted
//to open a bank account:
Permission p = new AccountPermission("open");
currentUser.checkPermission(p);
openBankAccount();

或者,同样的检查,用基于字符串权限:

Subject currentUser = SecurityUtils.getSubject();

//guarantee that the current user is permitted
//to open a bank account:
currentUser.checkPermission("account:open");
openBankAccount();

相对于使用isPermitted*方法,用这个方法可以使代码更加整洁,因为在当前主体不符合预期的条件下你不必自己创建AuthorizationExceptions异常(如果你不想创建的话)。

Subject类有一些跟权限相关的断言方法供你使用,你可以根据需要选择:

Subject方法
描述
如果通过指定的权限实例的综述,主体被允许执行某个动作或访问某个资源,则静默返回,否则抛出一个AuthorizationException异常
如果通过指定的字符串权限的综述,主体被允许执行某个动作或访问某个资源,则静默返回,否则抛出一个AuthorizationException异常
如果Subject实例被许可了集合中所有指定的权限,则静默返回,否则抛出一个AuthorizationException异常
跟上面的方法效果一样,但是使用的是基于字符串的权限

Annotation-based Authorization 基于注解的授权

除了Subject的API调用,Shiro还提供了Java5+里面的注解的集合,如果你喜欢基于元数据进行授权控制的话。


Configuration 配置

在你使用Java注解之前,你必须让你的应用支持AOP。这里有许多不同的AOP框架,所以很不幸,这里并没有一种标准的方法使你的应用拥有AOP功能。

对于AspectJ,你可以回顾一下我们的 AspectJ sample application文档。

对于Spring应用,你可以查看我们的 Spring Integration文档。

对于Guice应用,你可以查看我们的 Guice Integration文档。

The RequiresAuthentication annotation

RequiresAuthentication 注解要求当前的主体在当前会话期间并且在被标注的类/实例/方法被访问或调用时被认证

例如:

@RequiresAuthentication
public void updateAccount(Account userAccount) {
    //this method will only be invoked by a
    //Subject that is guaranteed authenticated
    ...
}

这大概相当于下面这段基于Subject API的逻辑代码:

public void updateAccount(Account userAccount) {
    if (!SecurityUtils.getSubject().isAuthenticated()) {
        throw new AuthorizationException(...);
    }
   
    //Subject is guaranteed authenticated here
    ...
}

The RequiresGuest annotation

RequiresGuest注解要求当前主体是一个“宾客”。也就是说,当被标注的类/实例/方法被访问或调用时,他们(这些主体)没有被认证过或被先前的会话记住过。

例如:

@RequiresGuest
public void signUp(User newUser) {
    //this method will only be invoked by a
    //Subject that is unknown/anonymous
    ...
}

这大概相当于下面这段基于Subject API的逻辑代码:

public void signUp(User newUser) {
    Subject currentUser = SecurityUtils.getSubject();
    PrincipalCollection principals = currentUser.getPrincipals();
    if (principals != null && !principals.isEmpty()) {
        //known identity - not a guest:
        throw new AuthorizationException(...);
    }
   
    //Subject is guaranteed to be a 'guest' here
    ...
}

The RequiresPermissions annotation

RequiresPermissions注解要求当前的主体被许可了一个或多个权限后,才可以执行被标注的方法。

例如:

@RequiresPermissions("account:create")
public void createAccount(Account account) {
    //this method will only be invoked by a Subject
    //that is permitted to create an account
    ...
}

这大概相当于下面这段基于Subject API的逻辑代码:

public void createAccount(Account account) {
    Subject currentUser = SecurityUtils.getSubject();
    if (!subject.isPermitted("account:create")) {
        throw new AuthorizationException(...);
    }
   
    //Subject is guaranteed to be permitted here
    ...
}

The RequiresRoles annotation

RequiresRoles  注解要求当前主体拥有所有指定的角色。如果他们没有这些角色,方法将不会执行并且抛出一个AuthorizationException异常。

例如:

@RequiresRoles("administrator")
public void deleteUser(User user) {
    //this method will only be invoked by an administrator
    ...
}

这大概相当于下面这段基于Subject API的逻辑代码:

public void deleteUser(User user) {
    Subject currentUser = SecurityUtils.getSubject();
    if (!subject.hasRole("administrator")) {
        throw new AuthorizationException(...);
    }
   
    //Subject is guaranteed to be an 'administrator' here
    ...
}

The RequiresUser annotation

RequiresUser* 注解要求在被注解的类/实例/方法被访问或调用时,当前主体是一个应用用户。一个“应用用户”被定义成是一个已知标识身份的主体,不管是因为当前会话期间被认证过而已知,还是因为在先前会话中被“记住我”服务记住而已知。

@RequiresUser
public void updateAccount(Account account) {
    //this method will only be invoked by a 'user'
    //i.e. a Subject with a known identity
    ...
}

这大概相当于下面这段基于Subject API的逻辑代码:

public void updateAccount(Account account) {
    Subject currentUser = SecurityUtils.getSubject();
    PrincipalCollection principals = currentUser.getPrincipals();
    if (principals == null || principals.isEmpty()) {
        //no identity - they're anonymous, not allowed:
        throw new AuthorizationException(...);
    }
   
    //Subject is guaranteed to have a known identity here
    ...
}

JSP授权标签库

Shiro提供了一套标签库,用来控制JSP/GSP页面根据Subject的状态来输出。这将在Web章节的JSP/GSP Tag Library部分介绍。

Authorization Sequence 授权步骤

现在我们必须看看怎么根据当前主体执行授权,让我们看看当一个授权调用发生的时候Shiro内部发生了什么。

我们还是使用前面 Architecture架构章节的架构图,并且只让跟授权相关的组件高亮。每个数字代表了授权期间的一个步骤:




Step 1:应用或框架代码调用Subject的hasRole*, checkRole*, isPermitted*, 或 checkPermission*方法变体,然后传入要求的任何权限或角色的表述作为参数。

Step 2:主体的实例,通常是一个 DelegatingSubject (或者它的子类)实例,它通过调用securityManager的几乎相同的hasRole*, checkRole*, isPermitted*, 或checkPermission*方法变体(securityManager 实现了 org.apache.shiro.authz.Authorizer 接口,这个接口定义了所有关于主体授权的方法),来委托给应用的SecurityManager。

Step 3:SecurityManager, 作为一个“保护伞”组件,通过调用authorizer的各自对应的hasRole*, checkRole*, isPermitted*, or checkPermission*方法,委托给它内部的 org.apache.shiro.authz.Authorizer 实例。authorizer实例默认是一个 ModularRealmAuthorizer  实例,它在任何授权操作期间,都支持协调一个或多个Realm实例。

Step 4:每个被配置的Realm都要被检查是否实现了相同的 Authorizer 接口。如果是,这个Realm自己对应的hasRole*,checkRole*, isPermitted*, 或checkPermission*方法就要被调用。

ModularRealmAuthorizer

前面一点提到过,Shiro的SecurityManager实现默认使用 ModularRealmAuthorizer 实例。ModularRealmAuthorizer在处理应用支持多个Realm时和处理应用支持一个Realm时效果一样。

对于任何授权操作,ModularRealmAuthorizer都将迭代它内部的Realm集合,并且按顺序逐一和它们交互。每个Realm交互步骤如下:

  1.     如果Realm自己实现了 Authorizer 接口,就说明Authorizer方法(hasRole*, checkRole*, isPermitted*, 或checkPermission*)被调用了。
    1. 如果Realm的方法返回一个异常结果,这个异常传播一个AuthorizationException给主体的调用者。这直接把授权进程短路了,在这个授权操作中剩余的Realm都不会被请求。
    2. 如果Realm的hasRole*或isPermitted*方法返回一个boolean值并且值是true,true值被立即返回并且剩余的Realm被短路。这个行为存在一个性能增强的点,通常如果被一个Realm许可了,就暗示着这个主题被完全许可了。这个最受欢迎的安全策略,在默认情况下任何东西都是被禁止的,被允许的东西都是明确被允许的,这是安全策略中最常用的安全类型。
  2. 如果这个Realm没有实现Authorizer接口,它将被忽略。

Realm Authorization Order :Realm的授权顺序

十分值得指出的是,跟认证完全一样,ModularRealmAuthorizer将使用迭代顺序和Realm实例交互。

ModularRealmAuthorizer必须访问配置在SecurityManager上面的Realm实例。当执行一个授权操作,它将迭代那个集合,并且只针对实现了Authorizer接口的那些Realm,调用Realm中各自对应的Authorizer方法(例如: hasRole*checkRole*isPermitted*, 或 checkPermission*)。

配置一个全局的 PermissionResolver

当执行一个基于字符串的权限检查的时候,大多数Shiro的默认Realm实现都在执行权限的包含逻辑之前首先将这条字符串转换成一个真实的 Permission  实例。

这是因为权限都是基于包含逻辑进行求值的,而不是直接用一个等式来检查(参看 Permission文档,得到更多关于包含逻辑和等式逻辑的信息)。在代码中包含逻辑比通过字符串比较更容易表达意思。因此,大多数Realm需要转换,或者把已提交的权限字符串解析成对应的权限实例。

为了这个转换,Shiro支持 PermissionResolver 这一概念。大多数Shiro的Realm实现都使用一个PermissionResolver来支持它们的实现——实现Authorizer接口中那些基于字符串权限检查的方法,它将使用PermissionResolver将字符串转换成权限对象,然后通过权限对象执行检查。

所有Shiro的Realm实现使用内部组件 WildcardPermissionResolver ,假定使用Shiro的 WildcardPermission  字符串格式。

如果你想创建自己的PermissionResolver实现,或许是为了支持你自己的权限字符串语法,并且你希望所有被配置的Realm实例都支持这种语法,你可以将你的PermissionResolver设置为全局变量给所有的Realm使用,在一个应用中只要配置一次就行了。

例如,在shiro.ini中:

shiro.ini

globalPermissionResolver = com.foo.bar.authz.MyPermissionResolver
...
securityManager.authorizer.permissionResolver = $globalPermissionResolver
...

PermissionResolverAware

如果你想配置一个全局的PermissionResolver,每一个配置在Realm中的PermissionResolver都必须实现 PermisionResolverAware 接口。这保证了配置的实例可以传递到每一个支持这种配置的Realm。

如果你不想使用一个全局的PermissionResolver,或者你不想那么麻烦使用PermissionResolverAware接口,你可以显式地给每一个Realm配置一个PermissionResolver实例(假设有一个JavaBeans兼容的setPermissionResolver方法):

permissionResolver = com.foo.bar.authz.MyPermissionResolver

realm = com.foo.bar.realm.MyCustomRealm
realm.permissionResolver = $permissionResolver
...


配置一个全局的 RolePermissionResolver

类似于PermissionResolver的概念,一个 RolePermissionResolver有能力去表示Realm所需要的权限实例,进而执行权限检查。

RolePermissionResolver的关键不同之处在于,输入的字符串是一个角色的名称,而不是一个权限字符串。

当需要将一个角色名称转换成一个具体的权限实例集合的时候,RolePermissionResolver将被Realm内部调用。

这是一个特别有用的特性,用于支持那些古老的不灵活的数据源,它们可能没有权限的概念。

例如,很多LDAP目录存储了角色名称(或者用户组名称),但是不支持将角色名称关联到具体的权限,因为它们根本没有“权限”的概念。基于Shiro的应用可以使用存储在LDAP中的角色名称,但是另外实现一个RolePermissionResolver,用来将角色名称转换成一个显式权限的集合,进而执行首选的显式访问控制。权限关联存储在另一个数据存储中,可能是一个本地数据库。

因为将角色名称转换成权限的概念在应用中有非常大的差异,所以Shiro的默认Realm实现不实现它。

然而,如果你想创建自己的RolePermissionResolver实现,并且你想把它配置给一个以上的Realm实现,你可以将你的RolePermissionResolver设置为全局变量,这样可以只配置一次。

shiro.ini

globalRolePermissionResolver = com.foo.bar.authz.MyPermissionResolver
...
securityManager.authorizer.rolePermissionResolver = $globalRolePermissionResolver
...

RolePermissionResolverAware

如果你想配置一个全局的RolePermissionResolver,每一个配置在Realm中的RolePermissionResolver都必须实现 RolePermisionResolverAware接口。这保证了配置的实例可以传递到每一个支持这种配置的Realm。

如果你不想使用一个全局的RolePermissionResolver,或者你不想那么麻烦使用RolePermissionResolverAware接口,你可以显式地给每一个Realm配置一个RolePermissionResolver实例(假设有一个JavaBeans兼容的setRolePermissionResolver方法):

rolePermissionResolver = com.foo.bar.authz.MyRolePermissionResolver

realm = com.foo.bar.realm.MyCustomRealm
realm.rolePermissionResolver = $rolePermissionResolver
...

自定义Authorizer

如果你的应用使用多个Realm执行授权,并且ModularRealmAuthorizer默认只是简单地迭代,短路授权行为不符合你的需求,这时你就可能会想要创建一个自定义的Authorizer,并且相应地配置给SecurityManager 。

例如,在shiro.ini:

[main]
...
authorizer = com.foo.bar.authz.CustomAuthorizer

securityManager.authorizer = $authorizer




原文地址