授权即访问控制,它将判断用户在应用程序中对资源是否拥有相应的访问权限。

判断一个用户是否有编辑的权限,有查看的权限

权限

权限是Apache Shiro安全机制最核心的元素。它在应用程序中明确声明了被允许的行为和表现。一个格式良好好的权限声明可以清晰表达出用户对该资源拥有的权限。

大多数的资源会支持典型的CRUD操作(create,read,update,delete),但是任何操作建立在特定的资源上才是有意义的。因此,权限声明的根本思想就是建立在资源以及操作上。

而我们通过权限声明仅仅能了解这个权限可以在应用程序中做些什么,而不能确定谁拥有此权限。

于是,我们就需要在应用程序中对用户和权限建立关联。

通常的做法就是将权限分配给某个角色,然后将这个角色关联一个或多个用户。这样的耦合性就比较低

权限声明及粒度

Shiro权限声明通常是使用以冒号分隔的表达式。就像前文所讲,一个权限表达式可以清晰的指定资源类型,允许的操作,可访问的数据。同时,Shiro权限表达式支持简单的通配符,可以更加灵活的进行权限设置。

下面以实例来说明权限表达式。

可查询用户数据

User:view

可查询或编辑用户数据

User:view,edit

可对用户数据进行所有操作

User:* 或 user

可编辑id为123的用户数据

User:edit:123

角色

Shiro支持两种角色模式:

1、传统角色:一个角色代表着一系列的操作,当需要对某一操作进行授权验证时,只需判断是否是该角色即可。这种角色权限相对简单、模糊,不利于扩展。

2、权限角色:一个角色拥有一个权限的集合。授权验证时,需要判断当前角色是否拥有该权限。这种角色权限可以对该角色进行详细的权限描述,适合更复杂的权限设计。

Shiro支持三种方式实现授权过程:编码实现 注解实现 JSP Taglig实现

笔者就想写点如何实现的过程~~,如何使用不是很重要了。

配置文件中,我们知道这个是采用默认的realm的实现方式,那么对于字符串设置的权限是如何进行判断的呢?勾引了笔者的好奇心,很多的博客都是简单的讲解使用的过程,其实学习细节方面的实现也是比较的有助于我们理解系统的实现的。

[users]
zhang=123,role1,role2
wang=123,role1

[roles]
#对资源user拥有create、update权限
role1=user:create,user:update
#对资源user拥有create、delete权限
role2=user:create,user:delete
#对资源user拥有create权限
role3=system:user:create

我记得之前我们提及的总管家的工作很多啊,我们再次看看这个继承图,Subject的验证的工作都是交给他来完成的。

验证当前的用户是否有这样的权限,这个是委托给管家的AuthorizingSecurityManager 来完成的,和我们验证身份的流程也是一样的处理的方式。

subject().isPermitted("user:create")

shiro授权_数据

AuthorizingSecurityManager 类中通过组合模式也可以说是代理吧,来完成这个过程

private Authorizer authorizer;   
public AuthorizingSecurityManager() {
super();
this.authorizer = new ModularRealmAuthorizer();
}

Modular Realm Authorizer (模块化 realm 授权)

下图就是验证是否有资源的权限,是否有角色等等,通过把权限的字符串和当前用户用户的权限的信息,和权限的处理realm集合带过来,通过默认的实现类进行处理,很少的耦合性吧!

shiro授权_shiro_02

ModularRealmAuthorizer 的继承结构图

shiro授权_数据_03

上面的这样的设计有啥子好处呢,个人感觉好处很多啊!比如Authorizer接口,我们在管家那里也注入的紧急是一个Authorizer的接口,然后代理这样的角度很好啊,还有个就是这种 PermissionResolverAware接口,这种类型的,在spring中很多吧,这样的注入依赖,可以很好的扩展,而且看起来也是比较的清晰,说不出的好处。加入要换掉默认的PermissionResolver,先判断是否实现这个接口然后在哈哈!然后再来说说PermissionResolver这个的作用吧,这个就是解决一个很简单的问题,将String的字符串的权限转换为Permission

public interface PermissionResolverAware {

public void setPermissionResolver(PermissionResolver pr);
}

ModularRealmAuthorizer 这里是模块化处理的一个转折点,都是需要发配给具体的Realm去处理的,就是说啊这里是个中转站。那么PermissionResolverAware 这个设置的接口放置在这里就是为了统一的设置下面的权限的解析,因为我们不管使用哪一种realm去处理,都会去解析这个字符串的意思到底是什么,那么如果笔者自定义了一种解析的方式,所以的reams都是需要重新设置PermissionResolver 的默认实现o,达到了一个统一处理的效果。集体看代码。

ModularRealmAuthorizer 中的函数:

public void setPermissionResolver(PermissionResolver permissionResolver) {
this.permissionResolver = permissionResolver;
applyPermissionResolverToRealms();
}

重新设置的时候啊,全部遍历realms看看是否实现了这个接口啊,实现了通通给我换掉。处理起来就好像门面一样的。看完了这里,我们在去看看Reaml的继承结构图

protected void applyPermissionResolverToRealms() {
PermissionResolver resolver = getPermissionResolver();
Collection<Realm> realms = getRealms();
if (resolver != null && realms != null && !realms.isEmpty()) {
for (Realm realm : realms) {
if (realm instanceof PermissionResolverAware) {
((PermissionResolverAware) realm).setPermissionResolver(resolver);
}
}
}
}

shiro授权_数据_04

如上图啊,我们一般处理的话,都是直接的继承了 AuthorizingRealm对吧哈哈,实现了很多的,比如验证的接口啊,这些的处理,是不是一个转发给一个的,架构就是厉害啊。一个代理给一个,但是整体的代码让人看起来就是比较的规范啦。

现在再次来看书写的接口,是不是特别的爽啦!

public abstract class AuthorizingRealm extends        AuthenticatingRealm
implements Authorizer,
Initializable,
PermissionResolverAware,
RolePermissionResolverAware

这里就是实现了默认的PermissionResolverAware,还有和缓存的沟通呢,模板模式使用的非常的广泛!感觉是不是非常的复杂啦,设计的好啊!

private static final String DEFAULT_AUTHORIZATION_CACHE_SUFFIX = ".authorizationCache";
private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger();
private boolean authorizationCachingEnabled;
private Cache<Object, AuthorizationInfo> authorizationCache;
private String authorizationCacheName;

private PermissionResolver permissionResolver;

private RolePermissionResolver permissionRoleResolver;
public AuthorizingRealm() {
this(null, null);
}

public AuthorizingRealm(CacheManager cacheManager) {
this(cacheManager, null);
}
public AuthorizingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
super();
if (cacheManager != null) setCacheManager(cacheManager);
if (matcher != null) setCredentialsMatcher(matcher);

this.authorizationCachingEnabled = true;
this.permissionResolver = new WildcardPermissionResolver();

int instanceNumber = INSTANCE_COUNT.getAndIncrement();
this.authorizationCacheName = getClass().getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
if (instanceNumber > 0) {
this.authorizationCacheName = this.authorizationCacheName + "." + instanceNumber;
}
}

这里逮住了 this.permissionResolver = new WildcardPermissionResolver();哈哈。继续我们之前的。在模块化分发的门面那里我们看到具体的检验权限的工作发给了reaml

(1)

subject().isPermitted("user:create")

(2)

DelegatingSubject->
securityManager.isPermitted(getPrincipals(), permission)

(3)

AuthorizingSecurityManager->
public boolean isPermitted(PrincipalCollection principals, String permissionString) {
return this.authorizer.isPermitted(principals, permissionString);
}

(4)

ModularRealmAuthorizer->门面这里哦
如果实现了权限检查的Realm才可以进行isPermitted的检查哦,之前我们自己做的那个是不是很low仅仅进行了登录的判断哦,这个才是正确的姿势,每一种都要实现对资源的判断哦,然后转发到具体的Realm中去,realm中又进行了继承的处理,有点管家的感觉了是不是!只有当前realm实现了Authorizer的才可以进行处理哦~~,所以进行了个转型!毕竟Realm是一个顶层的接口。然后就到了Reaml具体的咯!
public boolean isPermitted(PrincipalCollection principals, String permission) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
if (((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}

(5)

AuthorizingRealm->重Realm的继承图,我们可以看出来,这个是顶层的进行授权检查的哦!所以一般都是从父类开始,然后跑到子类!这里首先将String字符串使用这个权限转换器处理为一个Permission。然后进行之后的权限验证哦!哈哈~~先看看这个默认的实现吧!
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(principals, p);
}

PermissionResolver对于这个接口,如果我们想改变,如何去处理解析的权限字符串!如果让我们去定义,不能改变接口的性质,所以这里处理就比较的好,扩展性十足。你想定义其他的,还不是很简单?只要满足定义的格式,其他的代码都不用改变哦!这个就是接口的优势,面向接口编程~~

public interface PermissionResolver {

Permission resolvePermission(String permissionString);

}

WildcardPermissionResolver默认使用了这个来处理 Wildcard Permission Resolver 通配符 权限 处理,有点意思了,是不是!

public class WildcardPermissionResolver implements PermissionResolver {

public Permission resolvePermission(String permissionString) {
return new WildcardPermission(permissionString);
}
}

Permission 先别管WildcardPermission,先看看Permission这个接口,这个接口到底是什么,有何作用!

A Permission represents the ability to perform an action or access a resource. A Permission is the most granular, or atomic, unit in a system’s security policy and is the cornerstone upon which fine-grained security 。models are built.

权限代表执行动作或访问资源的能力。许可是最系统安全策略中的粒状或原子单元,是细粒度安全的基石。这句话是注解哦~~

RBCA新解哈哈:这个在这个接口上面说了,大多数应用程序通过将命名的角色与权限相关联(即一个角色)集合权限),然后关联用户与角色(即用户’有一个角色的集合),以便通过传递关联,用户“拥有”角色中的权限。这个主题有许多变化直接分配给用户、或分配给组的权限,以及用户依次添加到组和这些组中的权限有角色,等,等)。采用基于权限的安全模型,而不是一个角色,用户角色时,和组都可以创建,配置和/或在运行时删除。这使得一个非常强大的安全模型。所以需要对权限进行检查,将权限这种抽象的东西转换一下。知道了吧,这个就是唯一的哦。permission就是判断两个的权限够不够哦,但是我们得有将String->Permission 实例,然后在比较。这些都是Permission 的子类需要完成的任务。

public interface Permission {


/* If permission1 implies permission2 ;, i.e.permission1.implies(permission2)
* then any Subject granted permission1 would have ability greater than or
equal to that defined by permission。
*/
/**
这里的用法就是比较了,比如检查user:create的权限吧,
字符串user:create变化为一个permiss实例,当前的权限去将当前用户拥有的所有的
权限找出来,弄成一个Permission集合进行一一的比较哦,到底当前的够不够格
**/
boolean implies(Permission p);
}

WildcardPermission看看这个是怎么实现的。

因为我们的字符串是这样的 usr : create,update,edit : 1 使用:进行分割

private List<Set<String>> parts; 顺序放入,然后使用set保存,这个就是当前的保存

这里就是全部的实现了,然后就是 那个比较的函数判断是否有没有,写的思路的先排除所有的错误的可能,最后还剩下肯定有可能哦。implies 是当前的实例调用,自己和自己相同的对象比较。判断权限够不够~~哈哈。

public class WildcardPermission implements Permission, Serializable {

/*--------------------------------------------
| C O N S T A N T S |
============================================*/
protected static final String WILDCARD_TOKEN = "*";
protected static final String PART_DIVIDER_TOKEN = ":";
protected static final String SUBPART_DIVIDER_TOKEN = ",";
protected static final boolean DEFAULT_CASE_SENSITIVE = false;

/*--------------------------------------------
| I N S T A N C E V A R I A B L E S |
============================================*/
private List<Set<String>> parts;

/*--------------------------------------------
| C O N S T R U C T O R S |
============================================*/

protected WildcardPermission() {
}

public WildcardPermission(String wildcardString) {
this(wildcardString, DEFAULT_CASE_SENSITIVE);
}

public WildcardPermission(String wildcardString, boolean caseSensitive) {
setParts(wildcardString, caseSensitive);
}
protected void setParts(String wildcardString) {
setParts(wildcardString, DEFAULT_CASE_SENSITIVE);
}
protected void setParts(String wildcardString, boolean caseSensitive) {
if (wildcardString == null || wildcardString.trim().length() == 0) {
throw new IllegalArgumentException("Wildcard string cannot be null or empty");
}

wildcardString = wildcardString.trim();
List<String> parts = CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN));//:

this.parts = new ArrayList<Set<String>>();
for (String part : parts) {
Set<String> subparts = CollectionUtils.asSet(part.split(SUBPART_DIVIDER_TOKEN));//,
if (!caseSensitive) {//大小写敏感?转换一下
subparts = lowercase(subparts);
}
if (subparts.isEmpty()) {
throw new IllegalArgumentException("cannot contain parts with only dividers.");
}
this.parts.add(subparts);
}

if (this.parts.isEmpty()) {
throw new IllegalArgumentException("string cannot contain only dividers.");
}
}

private Set<String> lowercase(Set<String> subparts) {
Set<String> lowerCasedSubparts = new LinkedHashSet<String>(subparts.size());
for (String subpart : subparts) {
lowerCasedSubparts.add(subpart.toLowerCase());
}
return lowerCasedSubparts;
}

/*--------------------------------------------
| A C C E S S O R S / M O D I F I E R S |
============================================*/
protected List<Set<String>> getParts() {
return this.parts;
}

/*--------------------------------------------
| M E T H O D S |
============================================*/

public boolean implies(Permission p) {
// By default only supports comparisons with other WildcardPermissions
if (!(p instanceof WildcardPermission)) {
return false;
}

WildcardPermission wp = (WildcardPermission) p;

List<Set<String>> otherParts = wp.getParts();

int i = 0;
for (Set<String> otherPart : otherParts) {
// If this permission has less parts than the other permission, e
//verything after the number of parts contained
// in this permission is automatically implied, so return true
if (getParts().size() - 1 < i) {
return true;
} else {
Set<String> part = getParts().get(i);
if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
return false;
}
i++;
}
}

// If this permission has more parts than the other parts,
// only imply it if all of the other parts are wildcards
for (; i < getParts().size(); i++) {
Set<String> part = getParts().get(i);
if (!part.contains(WILDCARD_TOKEN)) {
return false;
}
}

return true;
}

public String toString() {
StringBuilder buffer = new StringBuilder();
for (Set<String> part : parts) {
if (buffer.length() > 0) {
buffer.append(":");
}
buffer.append(part);
}
return buffer.toString();
}

public boolean equals(Object o) {
if (o instanceof WildcardPermission) {
WildcardPermission wp = (WildcardPermission) o;
return parts.equals(wp.parts);
}
return false;
}

public int hashCode() {
return parts.hashCode();
}

}

(6)

刚刚说到了这里,然后得到了Permission

AuthorizingRealm->
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(principals, p);
}
AuthorizingRealm-> 通过这个PrincipalCollection 传过来的,拿到当前用户的权限信息的集合
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
AuthorizationInfo info = getAuthorizationInfo(principals);
return isPermitted(permission, info);
}
AuthorizingRealm->这里又得到了当前用户的所有的权限,比较就行了,有没有!完了~哈哈
private boolean isPermitted(Permission permission, AuthorizationInfo info) {
Collection<Permission> perms = getPermissions(info);
if (perms != null && !perms.isEmpty()) {
for (Permission perm : perms) {
if (perm.implies(permission)) {
return true;
}
}
}
return false;
}

//有点玩了哈~~ 明天继续