关于 Shiro 的权限匹配器和过滤器

项目源码:https://github.com/weimingge14/Shiro-project
演示地址:http://liweiblog.duapp.com/Shiro-project/loginjava

关于 Shiro 的权限匹配器和过滤器git

上一节,咱们实现了自定义的 Realm,方式是继承 AuthorizingRealm这个抽象类,分别实现认证的方法和受权的方法。程序员

这一节实现的代码的执行顺序: 
一、Shiro定义的过滤器和自定义的过滤器,在自定义的过滤器中执行 Subject对象的判断是否具备某项权限的方法 isPermitted(),传入某一个跟当前登陆对象相关的特征值(这里是登陆对象正在访问的 url 链接) 
二、程序走到自定义的 Realm 中的受权方法中,根据已经认证过的主体查询该主体具备的角色和权限字符串,一般状况下是一个角色的集合和一个权限的集合。github

此时咱们第 1 步有一个字符串,第 2 步有一个字符串的集合(权限的集合)。 
程序要帮咱们作的就是看第 1 步的字符串在不在第 2 步的字符串集合中。那么这件事情是如何实现的呢?数据库

三、此时程序检测到配置文件中有声明一个实现了 PermissionResolver 的类,这个时候程序就会到这个类中去查找所采用的权限匹配策略。 
四、到上一步返回的实现了 Permission 的类的对象中的 implies()方法中去进行判断。若是第 2 步的权限字符串数量多于 1 个,这个 implies()就会执行屡次,直到该方法返回 true 为止,第 1 步的 isPermitted() 才会返回 true。浏览器

下面咱们来关注一下 [urls]这个节点下面的部分。markdown

[urls]
# 配置 url 与使用的过滤器之间的关系 /admin/**=authc,resourceCheckFilter /login=anon

其中app

/admin/**=authc,resourceCheckFilter

表示,当请求 /admin/** 的时候,会依次通过 (1)authc和 (2)resourceCheckFilter 这两个过滤器。ide

过滤器在有些地方也叫拦截器,他们的意思是同样的。this

(1)authc这个过滤器是 Shiro 自定义的认证过滤器,即到自定义 Realm 的认证方法里面去按照指定的规则进行用户名和密码的匹配。 
DefaultFilter这个枚举类里面定义了多个自定义的过滤器,能够直接使用。

(2)resourceCheckFilter 是一个自定义的过滤器,咱们来看看它的声明:

[filters]
# 声明一个自定义的过滤器 resourceCheckFilter = com.liwei.shiro.filter.ResourceCheckFilter # 为上面声明的自定义过滤器注入属性值 resourceCheckFilter.errorUrl=/unAuthorization

 

实现:

public class ResourceCheckFilter extends AccessControlFilter { private String errorUrl; public String getErrorUrl() { return errorUrl; } public void setErrorUrl(String errorUrl) { this.errorUrl = errorUrl; } private static final Logger logger = LoggerFactory.getLogger(ResourceCheckFilter.class); /** * 表示是否容许访问 ,若是容许访问返回true,不然false; * @param servletRequest * @param servletResponse * @param o 表示写在拦截器中括号里面的字符串 mappedValue 就是 [urls] 配置中拦截器参数部分 * @return * @throws Exception */ @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { Subject subject = getSubject(servletRequest,servletResponse); String url = getPathWithinApplication(servletRequest); logger.debug("当前用户正在访问的 url => " + url); return subject.isPermitted(url); } /** * onAccessDenied:表示当访问拒绝时是否已经处理了;若是返回 true 表示须要继续处理;若是返回 false 表示该拦截器实例已经处理了,将直接返回便可。 * @param servletRequest * @param servletResponse * @return * @throws Exception */ @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { logger.debug("当 isAccessAllowed 返回 false 的时候,才会执行 method onAccessDenied "); HttpServletRequest request =(HttpServletRequest) servletRequest; HttpServletResponse response =(HttpServletResponse) servletResponse; response.sendRedirect(request.getContextPath() + this.errorUrl); // 返回 false 表示已经处理,例如页面跳转啥的,表示不在走如下的拦截器了(若是还有配置的话) return false; } }

 

注意:咱们首先要关注 isAccessAllowed()方法,在这个方法中,若是返回 true,则表示“经过”,走到下一个过滤器。若是没有下一个过滤器的话,表示具备了访问某个资源的权限。若是返回 false,则会调用 onAccessDenied 方法,去实现相应的当过滤不经过的时候执行的操做,例如跳转到某一个指定的登陆页面,去引导用户输入另外一个具备更大权限的用户名和密码进行登陆。

isAccessAllowed()方法的最后一个参数 o,能够得到咱们自定义的过滤器后面中括号中所带的参数。

咱们再跳回到 isAccessAllowed()中:subject.isPermitted(url)。说明经过继承 AccessControlFilter咱们能够获得认证主体 Subject和当前请求的 url 连接,它们的 API 分别是:

得到认证主体:

Subject subject = getSubject(servletRequest,servletResponse);

 

与 
得到当前请求的 url

String url = getPathWithinApplication(servletRequest);

 

而后,咱们调用了 subject.isPermitted(url)方法,将 url 这个字符串对象传入。

此时咱们的流程应该走到 Realm的受权方法中,经过查询(通过了认证的)用户信息去查询该用户具备的权限信息。此时的代码走到了这里。 
在受权方法中,咱们看到 SimpleAuthorizationInfo的角色信息和权限信息都是经过字符串来解析的。 
角色信息和权限信息都是集合。

@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("--- MyRealm doGetAuthorizationInfo ---"); // 得到通过认证的主体信息 User user = (User)principalCollection.getPrimaryPrincipal(); // // 此处为节约篇幅,突出重点省略 // SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(new HashSet<>(roleSnList)); info.setStringPermissions(new HashSet<>(resStrList)); // 以上完成了动态地对用户受权 logger.debug("role => " + roleSnList); logger.debug("permission => " + resStrList); return info; }

 

在这个 Realm 的受权方法中,完成了对该认证后的主体所具备的角色和权限的查询,而后放入 SimpleAuthorizationInfo对象中。

接下来就要进行 subject.isPermitted(url)中的 url 和 自定义 Realm 中的受权方法中的 info.setStringPermissions(new HashSet<>(resStrList));权限字符串集合的匹配操做了。

权限信息: 
这里写图片描述
它们都是从数据库中查询出来的。

那么如何实现匹配呢?比较简单的一个思路就是比较字符串,可是这件简单的比较的事情被 Shiro 定义为一个 PermissionResolver,经过实现 PermissionResolver,咱们能够为完成自定义的权限匹配操做,能够是简单的字符串匹配,也能够稍有灵活性的通配符匹配,这都取决于咱们程序员本身。

public class UrlPermissionResolver implements PermissionResolver { private static final Logger logger = LoggerFactory.getLogger(UrlPermissionResolver.class); /** * 通过调试发现 * subject.isPermitted(url) 中传入的字符串 * 和自定义 Realm 中传入的权限字符串集合都要通过这个 resolver * @param s * @return */ @Override public Permission resolvePermission(String s) { logger.debug("s => " + s); if(s.startsWith("/")){ return new UrlPermission(s); } return new WildcardPermission(s); } }

 

能够看到,权限信息是经过字符串:“/admin/**”等来进行匹配的。这时就不能使用 Shiro 默认的权限匹配器 WildcardPermission了。

而 UrlPermission 是一个实现了 Permission接口的类,它的 implies 方法的实现决定了权限是否匹配,因此 implies 这个方法的实现是很重要的。

public class UrlPermission implements Permission { private static final Logger logger = LoggerFactory.getLogger(UrlPermission.class); // 在 Realm 的受权方法中,由数据库查询出来的权限字符串 private String url; public UrlPermission(String url){ this.url = url; } /** * 一个很重要的方法,用户判断 Realm 中设置的权限和从数据库或者配置文件中传递进来的权限信息是否匹配 * 若是 Realm 的受权方法中,一个认证主体有多个权限,会进行遍历,直到匹配成功为止 * this.url 是在遍历状态中变化的 * * urlPermission.url 是从 subject.isPermitted(url) * 传递到 UrlPermissionResolver 中传递过来的,就一个固定值 * * @param permission * @return */ @Override public boolean implies(Permission permission) { if(!(permission instanceof UrlPermission)){ return false; } // UrlPermission urlPermission = (UrlPermission)permission; PatternMatcher patternMatcher = new AntPathMatcher(); logger.debug("this.url(来自数据库中存放的通配符数据),在 Realm 的受权方法中注入的 => " + this.url); logger.debug("urlPermission.url(来自浏览器正在访问的连接) => " + urlPermission.url); boolean matches = patternMatcher.matches(this.url,urlPermission.url); logger.debug("matches => " + matches); return matches; } }

 

重点说明:若是在自定义的 Realm 中的受权方法中传入的受权信息中的权限信息是一个集合,那么这里的 implies 就会进行遍历,直到这个方法返回 true 为止,若是遍历的过程所有返回 false,就说明该认证主体不具备访问某个资源的权限。

相关文章
相关标签/搜索