SaToken学习笔记-03

如果排版有问题,请点击:传送门

核心思想

所谓权限验证,验证的核心就是一个账号是否拥有一个权限码
有,就让你通过。没有?那么禁止访问!

再往底了说,就是每个账号都会拥有一个权限码集合,我来验证这个集合中是否包含指定的权限码
例如:当前账号拥有权限码集合:["user-add", "user-delete", "user-get"],这时候我来验证权限 "user-update",则其结果就是:验证失败,禁止访问

所以现在问题的核心就是:

如何获取一个账号所拥有的的权限码集合
本次操作需要验证的权限码是哪个 

模拟使用场景

准备工作:

package com.pj.satoken;

import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.stp.StpInterface;

/**
 * 自定义权限验证接口扩展 
 */
@Component    // 保证此类被SpringBoot扫描,完成sa-token的自定义权限验证扩展 
public class StpInterfaceImpl implements StpInterface {

    /**
     * 返回一个账号所拥有的权限码集合 
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginKey) {
        // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
        List<String> list = new ArrayList<String>();    
        list.add("101");
        list.add("user-add");
        list.add("user-delete");
        list.add("user-update");
        list.add("user-get");
        list.add("article-get");
        return list;
    }

    /**
     * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginKey) {
        // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
        List<String> list = new ArrayList<String>();    
        list.add("admin");
        list.add("super-admin");
        return list;
    }

}

准备完毕,可以使用相关api

权限验证api

// 当前账号是否含有指定权限, 返回true或false 
StpUtil.hasPermission("user-update");        

// 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException 
StpUtil.checkPermission("user-update");        

// 当前账号是否含有指定权限 [指定多个,必须全部验证通过] 
StpUtil.checkPermissionAnd("user-update", "user-delete");        

// 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] 
StpUtil.checkPermissionOr("user-update", "user-delete");        

扩展:NotPermissionException 对象可通过 getLoginKey() 方法获取具体是哪个 StpLogic 抛出的异常

源码解析

StpUtil.hasPermission("user-update");

进入第一层

/** 
 	 * 当前账号是否含有指定权限, 返回true或falsquae 
 	 * @param permission 权限码
 	 * @return 是否含有指定权限
 	 */
	public static boolean hasPermission(String permission) {
		return stpLogic.hasPermission(permission);
	}

将传入的权限名称传入调用的stpLogic.hasPermission(permission)方法
继续进入

/** 
 	 * 当前账号是否含有指定权限, 返回true或false 
 	 * @param permission 权限码
 	 * @return 是否含有指定权限
 	 */
 	public boolean hasPermission(String permission) {
 		return hasPermission(getLoginId(), permission);
 	}

将获取的loginId和传入的权限名称一起传入调用的hasPermission方法
下一步

/** 
 	 * 指定账号id是否含有指定权限, 返回true或false 
 	 * @param loginId 账号id
 	 * @param permission 权限码
 	 * @return 是否含有指定权限
 	 */
 	public boolean hasPermission(Object loginId, String permission) {
 		List<String> permissionList = SaManager.getStpInterface().getPermissionList(loginId, loginKey);
 		return SaManager.getSaTokenAction().hasElement(permissionList, permission);
//		return !(permissionList == null || permissionList.contains(permission) == false);
 	}

创建了一个list来接受SaManager.getStpInterface().getPermissionList(loginId, loginKey)返回的ArrayList
先看什么是getStpInterface()

public static StpInterface getStpInterface() {
		if (stpInterface == null) {
			synchronized (SaManager.class) {
				if (stpInterface == null) {
					setStpInterface(new StpInterfaceDefaultImpl());
				}
			}
		}
		return stpInterface;
	}

很简单就是创建并且返回了一个stpInterface接口的实现类StpInterfaceDefaultImpl
接下来调用了实现类中的getPermissionList方法

@Override
	public List<String> getPermissionList(Object loginId, String loginKey) {
		return new ArrayList<String>();
	}

可以看到其实跟传入的值没有关系,就是简单的返回了一个ArrayList。
所以第一个操作基本等同于
List permissionList = new ArrayList<>();
但是,之前的模拟场景中我们重写了此方法,所以此时应该返回的是带有我们之前定义过的所有权限字段的list集合
ok,下一个操作我们先看getStpInterface()

public static SaTokenAction getSaTokenAction() {
		if (saTokenAction == null) {
			synchronized (SaManager.class) {
				if (saTokenAction == null) {
					setSaTokenAction(new SaTokenActionDefaultImpl());
				}
			}
		}
		return saTokenAction;
	}

和上面的getStpInterface()类似,同样是创建并且返回了一个SaTokenAction的实现类SaTokenActionDefaultImpl
然后调用了实现类中的hasElement方法

/**
	 * 指定集合是否包含指定元素(模糊匹配) 
	 */
	@Override
	public boolean hasElement(List<String> list, String element) {
		// 集合为空直接返回false
		if(list == null || list.size() == 0) {
			return false;
		}
		// 遍历匹配
		for (String patt : list) {
			if(SaFoxUtil.vagueMatch(patt, element)) {
				return true;
			}
		}
		// 走出for循环说明没有一个元素可以匹配成功 
		return false;
	}

可以看到首先判断之前得到的权限集合是否为空,若是的话则代表此用户没有任何权限,直接返回false,表示没有权限
之后,通过遍历集合的方式将每个元素和传入的要查找的权限字符串进行对比,此对比底层为模糊比对,如果成功则表示确实有这项权限返回true,否则返回false,代表没有此项权限


StpUtil.checkPermission("user-update");

判断当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
使用场景:

@RequestMapping("/checkPermission")
    public boolean CheckPermission(@RequestParam("limit")String limit){
       boolean flag = true;
       try
       {
           StpUtil.checkPermission(limit);
           
       }catch (NotPermissionException e){
           flag = false;
           String key = e.getLoginKey();
           String code = e.getCode();
           System.out.println("key=>"+key+",code=>"+code);
       }
       return flag;
   }

开始查看源码:

/** 
 	 * 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException 
 	 * @param permission 权限码
 	 */
	public static void checkPermission(String permission) {
		stpLogic.checkPermission(permission);
	}

将传入的权限字符串传入调用的stpLogic.checkPermission方法中,继续下一步

/** 
 	 * 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException 
 	 * @param permission 权限码
 	 */
 	public void checkPermission(String permission) {
 		if(hasPermission(permission) == false) {
			throw new NotPermissionException(permission, this.loginKey);
		}
 	}

其中调用了hasPermission方法,与上面方法调用的hasPermission方法一致,不做解析
如果返回false则说明没有这项权限也就是验证未通过,就抛出NotPermissionException异常,结束


什么是NotPermissionException?
/**
 * 没有指定权限码,抛出的异常 
 * 
 * @author kong
 *
 */
public class NotPermissionException extends SaTokenException 

StpUtil.checkPermissionAnd("user-update", "user-delete")

当前账号是否含有指定权限 [指定多个,必须全部验证通过]
使用场景:

@RequestMapping("checkPermissionAnd")
    public boolean checkPermissionAnd(@RequestParam("limits")String... limits){
       boolean flag = true;
       try{
           StpUtil.checkPermissionAnd(limits);
       }catch (NotPermissionException e){
           flag=false;
           String key = e.getLoginKey();
           String code = e.getCode();
           System.out.println("key=>"+key+",code=>"+code);
       }
       return flag;
   }

开始浏览源码

/** 
 	 * 当前账号是否含有指定权限 [指定多个,必须全部验证通过] 
 	 * @param permissionArray 权限码数组
 	 */
	public static void checkPermissionAnd(String... permissionArray) {
		stpLogic.checkPermissionAnd(permissionArray);
	}

什么是String...?

类型后面三个点(String…),是从Java 5开始,Java语言对方法参数支持一种新写法,叫可变长度参数列表,其语法就是类型后跟…,表示此处接受的参数为0到多个Object类型的对象,或者是一个Object[]。 例如我们有一个方法叫做test(String…strings),那么你还可以写方法test(),但你不能写test(String[] strings),这样会出编译错误,系统提示出现重复的方法。

在使用的时候,对于test(String…strings),你可以直接用test()去调用,标示没有参数,也可以用去test(“aaa”),也可以用test(new String[]{“aaa”,”bbb”})。

另外如果既有test(String…strings)函数,又有test()函数,我们在调用test()时,会优先使用test()函数。只有当没有test()函数式,我们调用test(),程序才会走test(String…strings)。


将permissionArray传入调用的checkPermissionAnd方法

/** 
 	 * 当前账号是否含有指定权限 [指定多个,必须全部验证通过] 
 	 * @param permissionArray 权限码数组
 	 */
 	public void checkPermissionAnd(String... permissionArray){
 		Object loginId = getLoginId();
 		List<String> permissionList = SaManager.getStpInterface().getPermissionList(loginId, loginKey);
 		for (String permission : permissionArray) {
 			if(SaManager.getSaTokenAction().hasElement(permissionList, permission) == false) {
 				throw new NotPermissionException(permission, this.loginKey);	
 			}
 		}
 	}

不难看出首先获取到loginId,然后通过调用SaManager.getStpInterface().getPermissionList(loginId, loginKey)获取到该loginId的所有权限列表并且用List类型进行接收。其中的getStpInterface和getPermissionList方法在本文档第一个解析的方法中解析过了,这里就不再解析。接着对获取到的权限列表进行遍历,并且通过调用getSaTokenAction().hasElement(permissionList, permission)对权限进行一一比对,此方法在本文档解析的第一个方法中解析过了,同样不再次解析。如果存在一个权限不存在也就是比对不上,就抛出NotPermissionException异常。结束

StpUtil.checkPermissionOr("user-update", "user-delete")

实现了当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
使用场景:

@RequestMapping("checkPermissionOr")
    public boolean checkPermissionOr(@RequestParam("limits")String... limits){
        boolean flag = true;
        try{
            StpUtil.checkPermissionOr(limits);
        }catch (NotPermissionException e){
            flag=false;
            String key = e.getLoginKey();
            String code = e.getCode();
            System.out.println("key=>"+key+",code=>"+code);
        }
        return flag;
    }

开始浏览源码

/** 
 	 * 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] 
 	 * @param permissionArray 权限码数组
 	 */
	public static void checkPermissionOr(String... permissionArray) {
		stpLogic.checkPermissionOr(permissionArray);
	}

与chekPermissionAnd方法相似,调用了stpLogic.checkPermissionOr方法并将权限信息传入。

/** 
 	 * 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] 
 	 * @param permissionArray 权限码数组
 	 */
 	public void checkPermissionOr(String... permissionArray){
 		Object loginId = getLoginId();
 		List<String> permissionList = SaManager.getStpInterface().getPermissionList(loginId, loginKey);
 		for (String permission : permissionArray) {
 			if(SaManager.getSaTokenAction().hasElement(permissionList, permission) == true) {
 				// 有的话提前退出
 				return;		
 			}
 		}
		if(permissionArray.length > 0) {
	 		throw new NotPermissionException(permissionArray[0], this.loginKey);
		}
 	}

发现其中的大部分操作都与checkPermissionAnd中类似。在遍历判断权限时条件变为如果有一个权限比对上了就提前退出。如果没有退出就说明没有任何一个权限比对成功,最后判断传入的权限信息是否存在,如果存在则说明传入的权限信息都不符合,就抛出NotPermissionException异常,否则就不进行操作,因为表示根本就没有传入相关的权限信息,至此结束。


END