bug:
使用shiro对某一个接口路径进行角色限制的时候,发现只要设置了多角色之后权限失效的问题。
shiro配置的部分代码如下:
//DefaultFilter
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager){
System.out.println("shiroFilter============4");
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
//配置登录、登录成功、失败等跳转接口
bean.setLoginUrl("/login");
bean.setSuccessUrl("/index");
bean.setUnauthorizedUrl("/unauthorized");
//配置权限规则
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//index的路径使用authc的权限认证(DefaultFilter,每一个都是一个拦截类)
filterChainDefinitionMap.put("/index","authc");//需要身份认证
filterChainDefinitionMap.put("/","authc");//需要身份认证
filterChainDefinitionMap.put("/login","anon");//忽略
filterChainDefinitionMap.put("/loginUser","anon");//忽略
//RolesAuthorizationFilter,具有这个角色的用户才可以访问
filterChainDefinitionMap.put("/search","roles[admin,user]");
//PermissionsAuthorizationFilter,具有这个权限的用户才可以访问
filterChainDefinitionMap.put("/edit","perms[edit]");
filterChainDefinitionMap.put("/druid/**","anon");//将druid的请求全部放开
filterChainDefinitionMap.put("/**","user");//当前是否登录用户
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
配置中这一行(filterChainDefinitionMap.put("/search",“roles[admin,user]”);)如果换成单一角色可以正常使用,但是配置多角色之后会导致权限失效,进入“/unauthorized”接口
排查过程:
在网上翻了些资料,并没有找到特别合适的,基本都是在老的xml配置中进行权限角色分配,有些干脆没写。后面得到一点启发,去看了(RolesAuthorizationFilter)源码,通过debug定位到了DelegatingSubject中的hasAllRoles方法,但是遇到了一个问题,在这里记录一下,暂时走不下去了。
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
}
Set<String> roles = CollectionUtils.asSet(rolesArray);
return subject.hasAllRoles(roles);
}
public boolean hasAllRoles(Collection<String> roleIdentifiers) {
return hasPrincipals() && securityManager.hasAllRoles(getPrincipals(), roleIdentifiers);
}
protected boolean hasPrincipals() {
return !isEmpty(getPrincipals());
}
卡在了hasPrincipals()这个方法,按理说应该是非空的,然后走到securityManager.hasAllRoles()这个方法,但是debug走不过去,不知道是不是找错实现类的原因。
解决方法:
源码走不过去,发现不了原因,但是RolesAuthorizationFilter类中有一个isAccessAllowed()方法,大体就是判断当前用户中是否存在这个角色名称,既然发现不了问题,就绕开重写这个方法,定义一个新的规则。
1、重写拦截器代码:其中主要就是修改了最后一个步骤,直接进行角色的判断
package com.shiro.filter;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.Set;
/**
* Created by Jzs on 2021/8/2
* 角色过滤器
*/
public class RoleFilter extends RolesAuthorizationFilter {
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
}
Set<String> roles = CollectionUtils.asSet(rolesArray);
for (String role : roles) {
if(subject.hasRole(role)){
return true;
}
}
return false;
}
}
2、将自定义的拦截器注入到shiro的配置中
修改后的配置代码:经测试可正常使用
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager){
System.out.println("shiroFilter============4");
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
// 存放自定义的filter
LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
// 配置自定义角色认证过滤器
filtersMap.put("roles", new RoleFilter());
bean.setFilters(filtersMap);
//配置登录、登录成功、失败等跳转接口
bean.setLoginUrl("/login");
bean.setSuccessUrl("/index");
bean.setUnauthorizedUrl("/unauthorized");
//配置权限规则
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//index的路径使用authc的权限认证(DefaultFilter,每一个都是一个拦截类)
filterChainDefinitionMap.put("/index","authc");//需要身份认证
filterChainDefinitionMap.put("/","authc");//需要身份认证
filterChainDefinitionMap.put("/login","anon");//忽略
filterChainDefinitionMap.put("/loginUser","anon");//忽略
//RolesAuthorizationFilter,具有这个角色的用户才可以访问
filterChainDefinitionMap.put("/search","roles[admin,user]");
//PermissionsAuthorizationFilter,具有这个权限的用户才可以访问
filterChainDefinitionMap.put("/edit","perms[edit]");
filterChainDefinitionMap.put("/druid/**","anon");//将druid的请求全部放开
filterChainDefinitionMap.put("/**","user");//当前是否登录用户
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
猜测(待验证):
可以看到其实只是修改了代码中的hasAllRoles()方法为hasRole()方法,所以推测按照原先的代码逻辑是当前用户需要拥有角色列表中所有的角色名,才可以拥有访问权限。