背景:使用SpringBoot + shiro + vue 搭建一个权限控制的系统
问题: 遇到的一些可能感兴趣的问题
1、doGetAuthorizationInfo不会被调用
2、自定义过滤器导致访问资源认证出现混乱(过滤器失效)
3、使用shiro进行动态的权限配置
4、更改用户角色,如何让shiro知道并在下一次访问能做出调整
5、前后端分离如何做到让unauthorizedUrl和loginUrl能访问到正确的路径(交由vue前端控制)
6、定义一个过滤器进行授权校验
问题一、doGetAuthorizationInfo不会被调用
如果是采用注解或者配置文件写死那么不会这么纠结,如果是一个beginner并且没学会跑就要飞,那么肯定在doGetAuthorizationInfo不调用吃过很大的苦头,官网指出了有几种途径可以让doGetAuthorizationInfo执行,这里需要动态权限,所以采用编码的方式
角色检查
//get the current Subject
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.hasRole("administrator")) {
//show a special button
} else {
//don’t show the button?)
}
权限检查
If (currentUser.isPermitted(printPermission)) {
//do one thing (show the print button?)
} else {
//don’t show the button?
}
上面两种hasRole/isPermitted都会触发doGetAuthorizationInfo调用,但是这种触发方式并不是很好,因为多角色有相同的资源路径,会多次调用doGetAuthorizationInfo影响性能
具体参考文章Shiro授权链接,shiro动态权限
自定义过滤器导致访问资源认证出现混乱(过滤器失效)
下面代码重写过滤器的时候需要特别注意要用new CustomRolesAuthorizationFilter() 这么创建过滤器而不是使用@Bean,具体原因很复杂,可以看看这篇文章为什么这样子 。
然后讲讲这篇文章没有讲到的内容,为什么会这样? springboot使用ApplicationFilterChain管理过滤器,shiro的11个默认过滤器和自定义shiro过滤器(自定义过滤器可以覆盖默认过滤器,因为默认过滤器先加入Map缓存)是放在一个名为shiroFilter的对象交由ApplicationFilterChain管理,如果给自定义过滤器加上@Bean,那么springboot会将这些自定义的过滤器放到ApplicationFilterChain上管理。
有什么问题? 简单说就是shiroFilter对象执行完里面的shiro过滤器以后(login/xx = anon验证通过了)会继续执行ApplicationFilterChain对象剩余的过滤器,而自定义过滤器(重写authc也就是FormAuthenticationFilter)刚好就是这些剩余的过滤器中的一个,这时会这个login/xx又会调用自定义过滤器再验证一遍,这导致授权不通过。
/**
ProxiedFilterChain类doFilter方法可以了解一下
作用执行完当前shiro过滤器后回到springboot的过滤器
*/
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.filters == null || this.filters.size() == this.index) {
//we've reached the end of the wrapped chain, so invoke the original one:
if (log.isTraceEnabled()) {
log.trace("Invoking original filter chain.");
}
this.orig.doFilter(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Invoking wrapped filter at index [" + this.index + "]");
}
this.filters.get(this.index++).doFilter(request, response, this);
}
}
/**
其实不用重写ShiroFilterFactoryBean,下面一个bean了解一下
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, ShiroService shiroService) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 设置无权限时跳转的 url;
shiroFilterFactoryBean.setUnauthorizedUrl("/login/unAuthor.do");
// 重写roles拦截规则 path = roles['role,role2']
Map<String, Filter> extraFilter = shiroFilterFactoryBean.getFilters();
extraFilter.put("roles", new CustomRolesAuthorizationFilter());
extraFilter.put("authc", new CustomFormAuthenticationFilter());
shiroFilterFactoryBean.setFilters(extraFilter);
// 加载拦截资源
shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroService.loadFilterChainDefinitions());
return shiroFilterFactoryBean;
}
使用shiro进行动态的权限配置
参考:shiro动态权限
更改用户角色,如何让shiro知道并在下一次访问能做出调整
写个service交由spring管理,提供加载清除shiro缓存的授权内容的功能
public void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean) {
synchronized (this) {
AbstractShiroFilter shiroFilter;
try {
shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject();
} catch (Exception e) {
throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!");
}
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();
DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
// 清空老的权限控制
manager.getFilterChains().clear();
shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitions());
// 重新构建生成
Map<String, String> chains = shiroFilterFactoryBean.getFilterChainDefinitionMap();
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry.getKey();
String chainDefinition = entry.getValue().trim()
.replace(" ", "");
manager.createChain(url, chainDefinition);
}
}
}
前后端分离如何做到让unauthorizedUrl和loginUrl能访问到正确的路径(交由vue前端控制)
最简单做法就是指定一个能访问后台的路径,比如 shiroFilterFactoryBean.setUnauthorizedUrl("/login/unAuthor.do");
,然后写一个controller方法返回状态码,交给前端路由axios的拦截器进行拦截处理跳转。当然这里对登录处理可以重写FormAuthenticationFilter,在这个类中进行状态码的返回
定义一个过滤器进行授权校验
public class CustomRolesAuthorizationFilter extends AuthorizationFilter {
private HttpServletRequest request;
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
Subject subject = getSubject(request, response);
CmsUserEntity user = (CmsUserEntity) subject.getPrincipals().getPrimaryPrincipal();
if (null == user) {
return true;
}
String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
List<String> rolesList = Arrays.asList(rolesArray);
UserBiz userBiz = SpringUtil.getBean(UserBiz.class);
List<String> roleNames = userBiz.findRoleNameByUserId(user.getId());
Set<String> roleSet = new HashSet<>(roleNames);
boolean disjoint = Collections.disjoint(roleSet, rolesList);
return !disjoint;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。