AuthorizingRealm
AuthorizingRealm是shiro控制登录认证和鉴权的关键类。这个类集成了AuthenticatingRealm,提供了两个抽象方法:
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
通常这两个抽象方法需要我们自己去实现,也就是自定义Realm类,如:
public class UserRealm extends AuthorizingRealm
具体的实现方法网上有很多,这里就不贴代码了。重点介绍一下doGetAuthorizationInfo,该方法主要干的就一件事,就是把当前用户的角色和权限信息添加进AuthorizationInfo实例中,以便后续鉴权时使用。如下示例代码:
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
String accountId = JwtUtil.getClaim(principalCollection.toString(), CommonConstant.ACCOUNT);
// 查询用户角色
List<Role> roleLists = roleMapper.findRoleByAccountId(Integer.parseInt(accountId));
for (Role role : roleLists) {
if (role != null) {
// 添加角色
simpleAuthorizationInfo.addRole(role.getRoleName());
// 根据用户角色查询权限
List<Permission> permissions = permissionMapper.findSubPermissionByRole(role);
for (Permission permission : permissions) {
if (permission != null) {
// 添加权限
simpleAuthorizationInfo.addStringPermission(permission.getPermissionCode());
}
}
}
}
return simpleAuthorizationInfo;
}
鉴权原理
仍然还是在AuthorizingRealm类中有如下三个方法用来鉴权,最后一个方法属于具体的鉴权实现。第一个属于自定义PermissionResolver,这个本篇暂不做介绍。主要介绍第二个默认的鉴权处理方法。
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(principals, p);
}
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
AuthorizationInfo info = getAuthorizationInfo(principals);
return isPermitted(permission, info);
}
//visibility changed from private to protected per SHIRO-332
protected 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;
}
从方法里边可以看出,首先就是根据登录用户身份从AuthorizationInfo中取出用户的角色权限信息,具体实现如下:
//visibility changed from private to protected per SHIRO-332
protected Collection<Permission> getPermissions(AuthorizationInfo info) {
Set<Permission> permissions = new HashSet<Permission>();
if (info != null) {
Collection<Permission> perms = info.getObjectPermissions();
if (!CollectionUtils.isEmpty(perms)) {
permissions.addAll(perms);
}
perms = resolvePermissions(info.getStringPermissions());
if (!CollectionUtils.isEmpty(perms)) {
permissions.addAll(perms);
}
perms = resolveRolePermissions(info.getRoles());
if (!CollectionUtils.isEmpty(perms)) {
permissions.addAll(perms);
}
}
if (permissions.isEmpty()) {
return Collections.emptySet();
} else {
return Collections.unmodifiableSet(permissions);
}
}
private Collection<Permission> resolvePermissions(Collection<String> stringPerms) {
Collection<Permission> perms = Collections.emptySet();
PermissionResolver resolver = getPermissionResolver();
if (resolver != null && !CollectionUtils.isEmpty(stringPerms)) {
perms = new LinkedHashSet<Permission>(stringPerms.size());
for (String strPermission : stringPerms) {
if (StringUtils.clean(strPermission) != null) {
Permission permission = resolver.resolvePermission(strPermission);
perms.add(permission);
}
}
}
return perms;
}
关注一下代码最后的这两句:
Permission permission = resolver.resolvePermission(strPermission); perms.add(permission);
第一句的resolver默认处理类是:
WildcardPermissionResolver
然后resolvePermission方法实现为:
public Permission resolvePermission(String permissionString) {
return new WildcardPermission(permissionString, caseSensitive);
}
继续往源码里边走,发现如下实现方法:
public WildcardPermission(String wildcardString, boolean caseSensitive) {
setParts(wildcardString, caseSensitive);
}
protected void setParts(String wildcardString, boolean caseSensitive) {
wildcardString = StringUtils.clean(wildcardString);
if (wildcardString == null || wildcardString.isEmpty()) {
throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.");
}
if (!caseSensitive) {
wildcardString = wildcardString.toLowerCase();
}
List<String> parts = CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN));
this.parts = new ArrayList<Set<String>>();
for (String part : parts) {
Set<String> subparts = CollectionUtils.asSet(part.split(SUBPART_DIVIDER_TOKEN));
if (subparts.isEmpty()) {
throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted.");
}
this.parts.add(subparts);
}
if (this.parts.isEmpty()) {
throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted.");
}
}
权限CODE分隔符
重点关注一下两个split方法,分隔符分别是冒号和逗号,这也就是为什么很多shiro定义的权限编码中间有冒号或者逗号的原因。暂且把冒号前定义的code为资源权限,逗号前定义的为操作权限。通常资源在前,操作在后。故源码也是先用冒号分隔,然后再用逗号分隔,最终把分隔完的字符放在
List<Set<String>>
集合里边备用。
protected static final String PART_DIVIDER_TOKEN = ":"; protected static final String SUBPART_DIVIDER_TOKEN = ",";
比如我们定义了一个权限的code为system_manager:user_manager:list,最后集合放的就是List的size为3,3个Set集合每个各一个元素。【system_manager ,user_manager ,list】这三个字符串。
比如我们定义了一个权限的code为system_manager,user_manager,list,最后集合放的就是List的size为1,1个Set集合3个元素。【system_manager ,user_manager ,list】这三个字符串。
两类分隔符实现的权限判断逻辑也有不同,具体可用文末的DEMO进行测试。
权限校验
回到前边截图的 这个方法protected boolean isPermitted(Permission permission, AuthorizationInfo info) 。前边讲了第二个参数用于获取用户拥有的权限并用分隔符分隔。第一个参数就是当我们请求一个接口时,给这个接口定义分访问权限。比如通过注解形式定义的:
@RequiresPermissions("system_manager:user_manager:list")
方法参数明白之后,继续看里边的实现方法:
for (Permission perm : perms) { if (perm.implies(permission)) { return true; } }
首先for循环遍历用户拥有的所有权限。然后就是看用户拥有的权限与接口访问定义的权限是否匹配,匹配则证明有权限,可访问该接口。不匹配就表示无权访问。
权限校验规则
重点就是这个方法perm.implies(permission),实现源码如下:
public boolean implies(Permission p) {
// By default only supports comparisons with other WildcardPermissions
if (!(p instanceof WildcardPermission)) {
return false;
}
WildcardPermission wp = (WildcardPermission) p;
List<Set<String>> otherParts = wp.getParts();
int i = 0;
for (Set<String> otherPart : otherParts) {
// If this permission has less parts than the other permission, everything after the number of parts contained
// in this permission is automatically implied, so return true
if (getParts().size() - 1 < i) {
return true;
} else {
Set<String> part = getParts().get(i);
if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
return false;
}
i++;
}
}
// If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
for (; i < getParts().size(); i++) {
Set<String> part = getParts().get(i);
if (!part.contains(WILDCARD_TOKEN)) {
return false;
}
}
return true;
}
重点关注return结果的地方,第一个return true需要等第二个return false的结果,故先说
第二个return false。
看if条件就知道只有当用户权限不存在*号且用户权限code中有不包含的接口权限code则返回false无权限。
这里有点绕,先需要说明一点就是进入这个方法是把用户拥有的每个权限code,都拿来遍历一下,然乎对比接口定义的权限code。然后不管是用户拥有的权限code或者接口定义的权限code都是被split形成一个Set<String>。
假设当前传递进方法的用户权限code为:system_manager:user_manager:create,而接口权限code为system_manager:user_manager:list,则回触发第二个return false。
怎么才会触发返回第一个return true呢?
看源码知道当用户权限code分隔后的长度小于接口定义访问权限的code就会触发。什么意思呢?就是当用户权限code长度小于等于接口定义权限code长度,则先走第二个return false的判断,如果始终没有被return false,则会触发return true。比如:
用户权限code定义为:system_manager:user_manager,而接口权限code为system_manager:user_manager:list,则回触发第一个return true。
第三个return false
当用户权限code中不存在*号则返回无权限。比如用户权限code定义为:system_manager:user_manager:list:check,而接口权限code为system_manager:user_manager:list,则会触发第三个return false。文章来源:https://www.toymoban.com/news/detail-511776.html
当上边三个都没返回就返回第四个return true
权限CODE判断的工具类
下边为自己写的一个接口权限code和用户权限code比对的工具类,可以方便的让用户判断某些权限校验的异常场景,而不用在SHIRO源码里边DEBUG。文章来源地址https://www.toymoban.com/news/detail-511776.html
public class RealmTest {
protected static final String WILDCARD_TOKEN = "*";
protected static final String PART_DIVIDER_TOKEN = ":";
protected static final String SUBPART_DIVIDER_TOKEN = ",";
public static void main(String[] args) {
System.out.println(implies());
}
public static boolean implies() {
//定义接口权限code
List<Set<String>> httpParts = setParts("system_manager:role_manager:list");
//定义用户权限code
String userParts = "system_manager:role_manager:list:check";
int i = 0;
for (Set<String> otherPart : httpParts) {
// If this permission has less parts than the other permission, everything after the number of parts contained
// in this permission is automatically implied, so return true
if (getParts(userParts).size() - 1 < i) {
System.out.println("返回第一个true");
return true;
} else {
Set<String> part = getParts(userParts).get(i);
if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
System.out.println("返回第二个fasle");
return false;
}
i++;
}
}
// If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
for (; i < getParts(userParts).size(); i++) {
Set<String> part = getParts(userParts).get(i);
if (!part.contains(WILDCARD_TOKEN)) {
System.out.println("返回第三个fasle");
return false;
}
}
System.out.println("返回第四个true");
return true;
}
private static List<Set<String>> getParts(String wildcardString){
/*Set<String> userCodeSet1 = new HashSet<>();
Set<String> userCodeSet2 = new HashSet<>();
Set<String> userCodeSet3 = new HashSet<>();
Set<String> userCodeSet4 = new HashSet<>();
userCodeSet1.add("system_manager");
userCodeSet2.add("role_manager");
userCodeSet3.add("list");
userCodeSet4.add("aaa");
List<Set<String>> parts = new ArrayList<>();
parts.add(userCodeSet1);
parts.add(userCodeSet2);
parts.add(userCodeSet3);
parts.add(userCodeSet4);*/
return setParts(wildcardString);
}
protected static List<Set<String>> setParts(String wildcardString) {
List<Set<String>> parts2 = new ArrayList<>();
wildcardString = StringUtils.clean(wildcardString);
if (wildcardString == null || wildcardString.isEmpty()) {
throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.");
}
List<String> parts = CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN));
parts2 = new ArrayList<Set<String>>();
for (String part : parts) {
Set<String> subparts = CollectionUtils.asSet(part.split(SUBPART_DIVIDER_TOKEN));
if (subparts.isEmpty()) {
throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted.");
}
parts2.add(subparts);
}
if (parts2.isEmpty()) {
throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted.");
}
return parts2;
}
}
到了这里,关于shiro权限控制原理及权限分隔符使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!