前言:前一篇文章我们做了权限认证,看着就用一个注解就是实现权限认证,那么他的底层原理怎么实现的呢?家人们,不要着急,让我慢慢道来。
# SaCheckPermission 注解
package cn.dev33.satoken.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限认证:必须具有指定权限才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
* @author kong
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface SaCheckPermission {
/**
* 需要校验的权限码
* @return 需要校验的权限码
*/
String [] value() default {};
/**
* 验证模式:AND | OR,默认AND
* @return 验证模式
*/
SaMode mode() default SaMode.AND;
/**
* 多账号体系下所属的账号体系标识
* @return see note
*/
String type() default "";
/**
* 在权限认证不通过时的次要选择,两者只要其一认证成功即可通过校验
*
* <p>
* 例1:@SaCheckPermission(value="user-add", orRole="admin"),
* 代表本次请求只要具有 user-add权限 或 admin角色 其一即可通过校验
* </p>
*
* <p>
* 例2: orRole = {"admin", "manager", "staff"},具有三个角色其一即可 <br>
* 例3: orRole = {"admin, manager, staff"},必须三个角色同时具备
* </p>
*
* @return /
*/
String[] orRole() default {};
}
#SaStrategy#checkElementAnnotation
该方法由#checkMethodAnnotation调用,继续完前推
哦豁,原来是从#SaAnnotationInterceptor拦截器调用的呀
package cn.dev33.satoken.interceptor;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import cn.dev33.satoken.strategy.SaStrategy;
/**
* 注解式鉴权 - 拦截器
*
* @author kong
*/
public class SaAnnotationInterceptor implements HandlerInterceptor {
/**
* 构建: 注解式鉴权 - 拦截器
*/
public SaAnnotationInterceptor() {
}
/**
* 每次请求之前触发的方法
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 获取处理method
if (handler instanceof HandlerMethod == false) {
return true;
}
Method method = ((HandlerMethod) handler).getMethod();
// 进行验证
SaStrategy.me.checkMethodAnnotation.accept(method);
// 通过验证
return true;
}
}
这拦截器不就是开始集成springBoot中配置Sa-token的吗?
好了,明白入口是从拦截器这边过来的,那我们去看里面的实现,回到#SaStrategy
直接点击checkByAnnotation进去看看是啥
/**
* 根据注解(@SaCheckPermission)鉴权
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckPermission at) {
String[] permissionArray = at.value();
try {
if(at.mode() == SaMode.AND) {
this.checkPermissionAnd(permissionArray);
} else {
this.checkPermissionOr(permissionArray);
}
} catch (NotPermissionException e) {
// 权限认证未通过,再开始角色认证
if(at.orRole().length > 0) {
for (String role : at.orRole()) {
String[] rArr = SaFoxUtil.convertStringToArray(role);
// 某一项role认证通过,则可以提前退出了,代表通过
if(hasRoleAnd(rArr)) {
return;
}
}
}
throw e;
}
}
因为SaCheckPermission注解默认的mode就是AND,所以应该看checkPermissionAnd方法,
/**
* 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
* @param permissionArray 权限码数组
*/
public void checkPermissionAnd(String... permissionArray){
Object loginId = getLoginId();
List<String> permissionList = getPermissionList(loginId);
for (String permission : permissionArray) {
if(!hasElement(permissionList, permission)) {
throw new NotPermissionException(permission, this.loginType);
}
}
}
哦豁,这不就是从我们注解中拿到的字符串集合与getPermissionList方法获取到集合进行一个匹配而已,没有就抛出异常,那么getPermissionList是从哪里拿数据的呢?
/**
* 获取:指定账号的权限码集合
* @param loginId 指定账号id
* @return /
*/
public List<String> getPermissionList(Object loginId) {
return SaManager.getStpInterface().getPermissionList(loginId, loginType);
}
让我点击来看看,什么,居然是这个接口
等等,这不就是我们自定义实现的那个接口,嗦嘎,
所以简单来说就是从我们自定义的实现类中获取当前用户的权限集合与注解中的集合进行一个匹配。
嗯嗯,但是有些家人就要问了,他是怎么知道并拿到我们实现类SaPermissionImpl
可以看到这里并无我们实现类信息,但是他有个set方法,会不会是从这个方法set进来的呢?
还真的是,原来是从这里注入进来的,真相大白
原来是利用springBoot的自动装配,那我也自定义一个来玩玩看看。
#SaBeanInjectPlus
package com.example.satoken.satoken.inject;
import cn.dev33.satoken.stp.StpInterface;
import com.example.satoken.satoken.config.SaManagerPlus;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author yueF_L
* @version 1.0
* @date 2022-09-08 14:36
*/
public class SaBeanInjectPlus {
/**
* 注入权限认证Bean
*
* @param stpInterface StpInterface对象
*/
@Autowired(required = false)
public void setStpInterface(StpInterface stpInterface) {
SaManagerPlus.setStpInterface(stpInterface);
}
}
#SaManagerPlus
package com.example.satoken.satoken.config;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
/**
* @author yueF_L
* @version 1.0
* @date 2022-09-08 11:52
*
* 管理 Sa-Token 所有全局组
* 自定义测试
*/
public class SaManagerPlus {
private volatile static StpInterface stpInterface;
public static void setStpInterface(StpInterface stpInterface) {
SaManagerPlus.stpInterface = stpInterface;
}
public static StpInterface getStpInterface() {
if (stpInterface == null) {
synchronized (SaManagerPlus.class) {
if (stpInterface == null) {
setStpInterface(new StpInterfaceDefaultImpl());
}
}
}
return stpInterface;
}
}
在登录接口中debug看下信息。
可以看到确实走我们自定义的方法
打印出来的信息也是正确的。