安全认证框架Shiro(三)- 源码角度解析shiro的权限验证
第一:前言
承接上篇文章安全认证框架Shiro (二)- shiro过滤器工作原理
权限验证,一直是个难点,看起来是个很高大尚的概念,其实我理解的权限其实就是字符串,把它当成字符串,只要和用户对应的字符串匹配成功,则验证通过。验证权限可以理解为字符串匹配即可通过授权,url对应的权限是个字符串数组集合,所以同一个请求可能配置成多个权限,比如老板和总经理都可以查询员工的信息。访问该url需要一组权限集,只要能从realm里取到你拥有的权限,只要某个匹配即可授权通过。
第二:走下去
权限验证和身份验证前半部分流程一样直到走到PathMatchingFilter.onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)这个方法都是一样的。
onPreHandler是处理权限验证的部分,不同的权限验证有一同的实现方式。
权限验证器包括如下:
其中较常用的是PermissionsAuthorizationFilter权限过滤器,如果自定义权限验证,一般继承自AuthorizationFilter。
前面流程这里不做介绍,不懂的可看安全认证框架Shiro (二)- shiro过滤器工作原理,
从AccessControlFilter.onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)开始,不同类型过滤器不同逻辑。
参数mappedValue是对应的权限字符串。
如下图的“user:admin”:
我们先看看PermissionsAuthorizationFilter和FormAuthenticationFilter的appliedPaths数据有什么不同?如下
FormAuthenticationFilter.appliedPaths:
{
/login=null,
/authenticated=null
}
PermissionsAuthorizationFilter.appliedPaths:
{
/index.jsp=[Ljava.lang.String;@5a24afbf,
/admin.jsp=[Ljava.lang.String;@54e35a8b
}
其中权限里key对应的value值可参考如下:
解释:
key即是对应的需要处理的url,重点是value值,权限验证的value是有值的,非null,而value值即是权限字符串。
收到一个请求,先找到url对应的过滤器链,然后按序执行过滤器,同时mappedValue值也会一同传下去,通过realm查找用户对应的权限与mappedValue值比对,比对成功则继承下个过滤器验证。
讲那么多废话,下面上代码:
PermissionsAuthorizationFilter.isAccessAllowed():
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
//取得subject
Subject subject = getSubject(request, response);
//取的访问该url需要的权限,比如:[user:admin],注意是字符串数组
String[] perms = (String[]) mappedValue;
boolean isPermitted = true;
if (perms != null && perms.length > 0) {
if (perms.length == 1) {
//单个权限验证,
if (!subject.isPermitted(perms[0])) {
isPermitted = false;
}
} else {
//多个权限验证
if (!subject.isPermittedAll(perms)) {
isPermitted = false;
}
}
}
return isPermitted;
}
subject.isPermitted()方法先验证是否登陆过,没登陆过的用户根本没必要权限验证,直接失败返回false。若登陆过,通过securityManager.isPermitted(getPrincipals(), permission);执行从数据库或缓存查询用户权限操作,和permission比对(注意这时候permission还是类似“user:admin”的字符串),继续往下走直到AuthorizingRealm.isPermitted()方法:
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
//获得验证,角色等操作,即数据提供源
AuthorizationInfo info = getAuthorizationInfo(principals);
return isPermitted(permission, info);
}
调用getAuthorizationInfo()拿到AuthorizationInfo对象:
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
return null;
}
AuthorizationInfo info = null;
if (log.isTraceEnabled()) {
log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
}
Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
if (cache != null) {
if (log.isTraceEnabled()) {
log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
}
Object key = getAuthorizationCacheKey(principals);
info = cache.get(key);
if (log.isTraceEnabled()) {
if (info == null) {
log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
} else {
log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
}
}
}
if (info == null) {
// Call template method if the info was not found in a cache
info = doGetAuthorizationInfo(principals);
// If the info is not null and the cache has been created, then cache the authorization info.
if (info != null && cache != null) {
if (log.isTraceEnabled()) {
log.trace("Caching authorization info for principals: [" + principals + "].");
}
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
return info;
}
getAuthorizationInfo先从缓存管理器里拿权限组,存在直接返回,如果没有则调用Realm实现子类的doGetAuthorizationInfo()方法,一般自定义的Reaml基本上都是继承AuthorizingRealm类,而且需要实现doGetAuthorizationInfo()方法,该方法从数据库查询数据组装到AuthorizationInfo子类对象里返回,且会保存到缓存里供下次调用。
查询出来的权限字符会和url需要的权限字符串比较,比较方法如下isPermitted():
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;
}
如果匹配成功则有权限访问该url,否则无权限。
shiro没配置权限验证的Url怎么处理
在shiro里,如果没配置url过滤器,默认url在shiro里不会做任何验证的,会走spring过虑器。
看看AbstractShiroFilter.getExecutionChain()查询Url对应的过滤器链方法。默认是origChain,如果在过滤器管理器里找到再重新赋值,否则是默认值
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
//默认过滤器,即ApplicationFilterChain
FilterChain chain = origChain;
FilterChainResolver resolver = getFilterChainResolver();
if (resolver == null) {
log.debug("No FilterChainResolver configured. Returning original FilterChain.");
return origChain;
}
FilterChain resolved = resolver.getChain(request, response, origChain);
if (resolved != null) {
log.trace("Resolved a configured FilterChain for the current request.");
//不为null才赋值,否则取默认值
chain = resolved;
} else {
log.trace("No FilterChain configured for the current request. Using the default.");
}
return chain;
}
结论
shiro在启动后,权限过滤器会维护一个类似
{
/index.jsp=[Ljava.lang.String;@5a24afbf,
/admin.jsp=[Ljava.lang.String;@54e35a8b
}
的集合,当接收到请求后,拿key对应的value值去与缓存或数据库里用户的权限对比。我们注意到,事先权限过滤器已经知道访问某个url对应哪个权限了,这是重点,慢慢品位。
未完待续….