diff --git a/java/spring/aop/aop模板/权限校验/SSM权限校验.md b/java/spring/aop/aop模板/权限校验/SSM权限校验.md new file mode 100644 index 0000000..aab36a9 --- /dev/null +++ b/java/spring/aop/aop模板/权限校验/SSM权限校验.md @@ -0,0 +1,285 @@ +# SSM注解+AOP实现鉴权(功能鉴权+数据鉴权) + +## 开启配置 + +```xml +##设定扫描路径确定能够扫描到后续我们自定义AOP类 + +##开启自动代理 不开启这个的话后续注解在家接口上也是不会生效的 + +``` + +## 自定义权限注解 + +### 属性介绍 + +- **perm(功能权限)** + +​ 可以看到我这里功能权限的字段用的是int类型的数组,这是因为我当前系统下权限是存在一个长字符串中的 这个字符串的每一个位置代表一个权限,比如新增订单在这个字符串的第二位那么给新增订单添加数据权限perm就应该等于2,代表着是去找这个用户的权限字符串中的第二位是否是1,是1的话代表拥有这个权限,我这里用数组是可以使一个接口在拥有多个权限下的情况才能调用,此处如果不需要可以直接改成int,不过通常用户权限列表不会使用一个长字符串来保存,而是一个字符串类型的集合,每一个权限是一个字符串,一般可以直接用接口的path作为权限名,或者另取别名作为权限名;数据校验的时候就是通过AOP校验传入的权限字符串在他本身权限列表中是否存在,如果不存在就代表他没有权限,需要给他拦截掉不能完成当前的请求 + +- **dataPerm(数据权限)** + +​ `dataPerm`是数据权限,用于校验当前数据是否可以进行修改,比如一个列表,查看一个列表是一个功能权限,删除列表中的一个数据也是一个功能权限,但是有些数据是指定人才可以删除,`dataPerm`就是针对这些特殊数据设定的权限 + +- **shopNoParam** + +​ 配合`dataPerm`实现数据权限控制,上面的`dataPerm`用于判断是否开启了数据权限,如果开启了数据权限,那么我们就要验证操作的数据是否可以被当前用户所操控,`shopNoParam`就是用于获取当前操作数据,此字段指定到方法的某个参数为目标,从这个目标上获取到门店号或者门店ID + +- **shopNoParamHandler** + +​ 通过`shopNoParam`获取到了目标对象,但是这个对象还不是我们想要的值,我们想要的值在这个对象里面,这个时候我们需要指定一个处理器,这个处理器用来解析这个对象中的值 + +```java +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthorityVerify { + + //功能权限 + int[] perm() default {}; + + //数据权限 + int dataPerm() default -1; + + /** + * 门店类数据权限校验 如果不为空则代表开启门店权限校验 会验证当前操作店铺是否拥有操作权限 此权限只有在dataPerm开启时才会生效 + * @return 获取验证门店的字段 + */ + String shopNoParam() default ""; + + /** + * ApiParamHandler为抽象模板 不可以直接作为参数处理器使用 应选择其子类作为处理类 + */ + ParamHandlerEnum shopNoParamHandler() default ParamHandlerEnum.OBJECT; + +} +``` + + + +### 解析处理类 + +```java +/** + * 解析处理类枚举,注册了两个不同的处理对象,可以直接试用这个枚举进行调用 + * @author dss + * @since 2022/3/9 + */ +public enum ParamHandlerEnum{ + + + OBJECT(1,"",new ObjectParamHandler()) + ,GET_TARGET_PARAM(2,"",new GetTargetParamHandler()) + + ; + + private final Integer code; + private final String name; + private final ApiParamHandler handler; + + + public Integer getCode() { + return code; + } + + public String getName() { + return name; + } + + public ApiParamHandler getHandler() { + return handler; + } + + ParamHandlerEnum(Integer code, String name, ApiParamHandler handler) { + this.code = code; + this.name = name; + this.handler = handler; + } +} + +/** + * 处理器抽象模板 + * @author dss + * @since 2022/8/1 + */ +public abstract class ApiParamHandler{ + + protected T pathParam = null; + protected T param = null; + + //获取目标参数对象 + protected void getParam(String tarParamName, String[] paramsSerial,Object[] argsSerial){ + + for (int i = 0; i < paramsSerial.length; i++) { + if (argsSerial[i]!=null){ + PathVariable pathAnno = argsSerial[i].getClass().getAnnotation(PathVariable.class); + if (pathAnno!=null&&tarParamName.equals(pathAnno.value())){ + pathParam= (T) argsSerial[i]; + } + if (tarParamName.equals(paramsSerial[i])){ + param= (T) argsSerial[i]; + } + } + } + } + + // 将目标对象转换为String类型 + protected abstract String targetToString(String tarParamName, String[] paramsSerial,Object[] argsSerial); + + // 清空参数 + protected void clearParam(){ + pathParam=null; + param=null; + } + + public synchronized String getTargetStr(String tarParamName, String[] paramsSerial,Object[] argsSerial){ + getParam(tarParamName, paramsSerial,argsSerial); + String s = targetToString(tarParamName, paramsSerial, argsSerial); + clearParam(); + return s; + } +} + +/** + * 简单的处理对象,直接将对象toString,适用于传的参数本身就是要取的值,比如一个接口传入的直接就是门店号,我就不需要再去解析他了 + * @author dss + * @since 2022/8/1 + * Object 类型参数处理器 + */ +public class ObjectParamHandler extends ApiParamHandler { + + @Override + public String targetToString(String tarParamName, String[] paramsSerial,Object[] argsSerial){ + + if (pathParam!=null){ + return pathParam.toString(); + } + if (param!=null){ + return param.toString(); + } + return null; + } +} + +public interface GetTargetParamAble { + String getTargetId(); //获取目标值 +} +/** + * 自定义解析处理类,传入的对象需要实现GetTargetParamAble接口,这样就可以通过这个接口的getTargetId方法获取到目标值 + * @author dss + * @since 2023 + * LogTargetAble GetTargetParamAble + */ +public class GetTargetParamHandler extends ApiParamHandler { + @Override + protected String targetToString(String tarParamName, String[] paramsSerial, Object[] argsSerial) { + if (pathParam!=null){ + return pathParam.getTargetId(); + } + if (param!=null){ + return param.getTargetId(); + } + return null; + } +} + + +``` + +### 切面类 + +```java +@Aspect +@Component +public class AuthorityVerifyAspect { + + private static final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer(); + Logger logger = Logger.getLogger(AuthorityVerifyAspect.class); + + //设置切点为AuthorityVerify注解 + @Pointcut(value = "@annotation(authorityVerify)") + public void pointcut(AuthorityVerify authorityVerify) { + + } + + //对切点进行环绕增强 + @Around(value = "pointcut(authorityVerify)") + public Object doAround(ProceedingJoinPoint joinPoint, AuthorityVerify authorityVerify) throws Throwable { + + ServletRequestAttributes attribute = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + + HttpServletRequest request = attribute.getRequest(); + HttpServletResponse response = attribute.getResponse(); + + SysUser sysUser = (SysUser) request.getSession().getAttribute("back_sysUser"); + List shopNos = (List) request.getSession().getAttribute("shopNos"); + + String perm = (String) request.getSession().getAttribute("perm"); + String dataPerm = (String) request.getSession().getAttribute("dataPerm"); + + //用户未登录重定向到登录页面 + if (sysUser == null) { + response.sendRedirect(request.getContextPath()+"/index.shtml?loginFlag=noLogin"); + } + + //验证功能权限、功能权限不存在重定向到没有权限页面 + int[] perms = authorityVerify.perm(); + if (perms!=null&&perms.length>0){ + logger.debug("进行功能校验"); + for (int s : perms) { + if (perm==null|| perm.charAt(s - 1) == '0'){ + response.sendRedirect(request.getContextPath()+"/noPerm.shtml"); + } + } + } + + //验证是否开启了数据校验 如果开启了数据权限则才会触发数据权限的附加验证 比如验证是否拥有门店的操作权限 + int dataP = authorityVerify.dataPerm(); + if (dataP!=-1 && dataPerm!=null && dataPerm.charAt(dataP-1)=='1'){ + logger.debug("进行数据校验"); + Method method = ((MethodSignature) (joinPoint.getSignature())).getMethod(); + // 形参数组 + String[] paramsSerial = nameDiscoverer.getParameterNames(method); + // 实参数组 + Object[] argsSerial = joinPoint.getArgs(); + //验证门店权限 + //tarParam不为空 则获取目标参数值 + if (!StringUtils.isEmpty(authorityVerify.shopNoParam())){ + if (paramsSerial!=null&¶msSerial.length!=0 && authorityVerify.shopNoParamHandler()!=null&&shopNos!=null){ + ApiParamHandler apiParamHandler = authorityVerify.shopNoParamHandler().getHandler(); + String targetStr = apiParamHandler.getTargetStr(authorityVerify.shopNoParam(), paramsSerial, argsSerial); + if (StringUtils.isEmpty(targetStr)){ + //跳转到没有权限页面 + response.sendRedirect(request.getContextPath()+"/noPerm.shtml"); + } + if (!shopNos.contains(targetStr)) { + //跳转到没有权限页面 + response.sendRedirect(request.getContextPath()+"/noPerm.shtml"); + } + }else{ + //解析参数出错 导致权限校验失败 + //跳转到没有权限页面 + response.sendRedirect(request.getContextPath()+"/noPerm.shtml"); + } + } + } + + return joinPoint.proceed(); + } +} +``` + +### 使用 + +```java +@RequestMapping("/test") +@AuthorityVerify(perm = 69,dataPerm = 69,shopNoParam = "shop",shopNoParamHandler = ParamHandlerEnum.GET_TARGET_PARAM) +public Map test(StShop shop,String token,Model model){ + return new HashMap<>(); +} +@RequestMapping("/test2") +@AuthorityVerify(perm = 70,dataPerm = 70,shopNoParam = "shopNo",shopNoParamHandler = ParamHandlerEnum.OBJECT) +public Map test2(String shopNo){ + return new HashMap<>(); +} +``` +