一、概述
在之前的spring集成shiro权限控制一文中,实现了动态更新的权限的方法,即对权限表做修改后,需要更新到shiro的filterchain。后来测试的时候,发现两个问题:
1、更改权限后,并没有动态更新到shiro中去,取消权限的用户仍然可以正常访问。
2、权限的写法authec,roles[admin,guest]是逻辑AND的关系,必须用户同时满足两种角色才有权限,而我们现实中是满足其中一个就可以了,也就是逻辑OR的关系。
二、动态更新
先展示正确的更新代码:
package com.gameloft9.demo.security;
import com.gameloft9.demo.service.api.system.SysAccessPermissionService;
import com.gameloft9.demo.dataaccess.model.system.SysAccessPermissionTest;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* shiro 动态权限配置相关服务
*
* @author gameloft9
*/
@Service
@Slf4j
public class ShiroConfigService {
@Autowired
SysAccessPermissionService sysAccessPermissionServiceImpl;
/**
* 从数据库加载权限列表
*/
public Map<String, String> loadFilterChainDefinitions() {
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
List<SysAccessPermissionTest> list = sysAccessPermissionServiceImpl.getAll();
for (SysAccessPermissionTest item : list) {
filterChainDefinitionMap.put(item.getUrl(), item.getRoles());
}
return filterChainDefinitionMap;
}
/**
* 更新权限,解决需要重启tomcat才能生效权限的问题
*/
public void updatePermission() {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
ServletContext servletContext = request.getSession().getServletContext();
AbstractShiroFilter shiroFilter = (AbstractShiroFilter) WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext).getBean("myShiroFilter");
synchronized (shiroFilter) {
// 获取过滤管理器
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();
DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
// 清空初始权限配置
manager.getFilterChains().clear();
// 重新获取资源
Map<String, String> chains = loadFilterChainDefinitions();
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry.getKey();
String chainDefinition = entry.getValue().trim().replace(" ", "");
manager.createChain(url, chainDefinition);
}
log.info("更新权限成功!!");
}
} catch (Exception e) {
log.error("更新权限到shiro异常", e);
}
}
}
原来的错误的代码里面主要是ShiroFactoryBean的获取有问题,之前是通过@Autowired的方式获取的,这种方式是获取不到Spring MVC 上下文环境里的bean的。Spring MVC和spring上下文不是同一个,spring是用listener初始化的上下文,称为root上下文,Spring MVC是通过servlet初始化的上下文,称为servlet上下文,servlet里的能获取root里的,反之就获取不到。servlet应该是root的子上下文,而我获取bean的地方是处于root上下文的。
正确的获取方式是通过SelvetContext去拿到Spring MVC的上下文,然后再获取相应的bean。如下所示:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
ServletContext servletContext = request.getSession().getServletContext();
AbstractShiroFilter shiroFilter = (AbstractShiroFilter)
WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext).getBean("myShiroFilter");
然后就是从数据库读取最新的权限配置,重新加载到shiro中来。
二、逻辑OR的实现
shiro的roles filter只能是逻辑与的关系,想要实现逻辑或,就需要自定义一个filter。我们通过继承AuthorizationFilter并重写isAccessAllowed方法来实现。逻辑很简单了,只要有一个角色满足就返回true。
package com.gameloft9.demo.security;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* shiro的url定义中的roles参数,必须满足所有角色才通过(也就是and关系)
* 这里自定义一个权限满足角色之一的Or关系的filter
* Created by gameloft9 on 2018/7/23.
*/
public class RoleOrAuthorizationFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request,
ServletResponse response, Object mappedValue) throws Exception {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
for(int i=0;i<rolesArray.length;i++) {
if(subject.hasRole(rolesArray[i])) { // 有一个满足即可
return true;
}
}
return false;
}
}