越权访问解决方案探索(基于Spring与Shiro,多页面前端应用)

越权访问(Broken Access Control,简称BAC)是Web应用程序中一种常见的漏洞,由于其存在范围广、危害大,被OWASP列为Web应用十大安全隐患的第二名。越权访问的介绍可以参考如下文章,介绍得比较详细:

https://blog.csdn.net/u012068483/article/details/89553797

目前的问题分析与解决思路:

问题1、水平越权访问是一种“基于数据的访问控制”设计缺陷引起的漏洞。由于服务器端在接收到请求数据进行操作时没有判断数据的所属人/所属部门而导致的越权数据访问漏洞。

解决思路:在请求数据时获取登录用户信息,根据登录用户信息判定此用户有没有这类数据的访问权限。

问题2:垂直越权是一种“基于URL的访问控制”设计缺陷引起的漏洞,又叫做权限提升攻击。

解决思路:利用shiro的权限认证功能判定用户对改用户是否有操作权限。

 

上述内容衍生问题:在问题2的解决方案中这是最终的解决方向,但是在实际的开发和使用过程中,对用户分别针对响应的URL进行权限标识设定,因为一个项目存在大量的用户且用户在发生变化,且系统有非常多的URL,导致配置工作较大且容易配置错误。

解决思路:

1、针对权限的分配还是使用角色与对应界面的操作权限,并使用界面的权限标识对响应的接口进行权限限定。即对如果用户属于某个角色,这个角色对某个界面有访问与操作权限,这个界面对REST接口有访问权限,则推导出这个用户对这个REST接口具有访问权限。

解决步骤:

1、用户与角色的对应关系还是系统中配置产生数据;

2、角色与HTML页面的权限在系统中进行配置;

3、根据HTML页面的权限对REST接口进行权限配置注解(采用Shiro框架);

 

上述方案衍生问题:

       维护用户信息、用户与角色信息、HTML页面是必须的操作,这样解决的是用户对功能访问的权限问题。根据HTML页面的权限对REST接口进行权限配置注解(采用Shiro框架)会随着页面的增加或者取消,界面功能的修改而去修改对应的权限注解,这个针对老项目或者一开始没有很好规划的项目会造成一定的工作量,所以公司希望推出一个自动化的方案。

自动化的解决方案:

1、用户与角色的对应关系还是系统中配置产生数据,手动操作;

2、角色与HTML页面的权限在系统中进行配置,手动操作;

3、通过添加过滤器捕获用户访问的界面与REST接口的对应关系,代码实现;

4、通过编写代码从Spring项目中获取到所用的REST接口信息,代码实现;

5、将界面的权限标识通过代码自动添加到Java文件中的方法的注解中,代码实现;

 

PS:本来讨论和想了几个方案,但是都有一些缺陷,想着先实现一个较优的方案试试看,内容如下:

1、将上面所说的用户与角色,角色与后端URL的对应关系保存到数据库中。将数据查出来以用户为基础在内存中缓存用户与对应后端的URL映射,当用户访问后端URL时渠道映射关系中查到映射,如果找到则拥有访问权限。(在内存缓存数据解决了频繁访问数据库的问题。但是用户多,URL多,需要缓存的数据量较大,可能会导致JVM出现问题),这个方案基础且古老(虽然能解决问题),所以衍生了第二种方案。

2、将用户与角色,角色与后端URL的对应关系从数据库查出来,以后端URL为基础,用链表或map保存能访问这个URL的用户位图信息(将所有用户或者活跃用户查询出来,将用户按照顺序进行位图映射,如A用户对应位图信息的第一个bit,B用户对应位图的第二个bit)。这个思想是用时间换取空间的思想,虽然可行,但是用户的变化会影响位图的变化,需要动态的操作这个位图信息。这个方案未利用框架,感觉没有好好利用现有的开源shiro。然后才确定了还是使用shiro的方案。

3、使用shiro的权限标识,将权限标识写到java文件中(采用方案)。

4、将权限标识在项目启动时将权限标识通过反射动态添加到Class对象上,此种方式排查问题困难,暂未实现,其实实现起来比方案3简单。

以上方案其实都需要解决以下问题:

1、得到用户跟角色(岗位)之间的对应关系、岗位与界面的权限关系。从而得出用户与界面的权限关系。

2、得到页面的信息与后端URL的信息以及他们之间的访问权限信息。

 

步骤3的代码参考:

public class RequestURLLogFilter implements Filter {
  private Logger logger = LoggerFactory.getLogger(getClass());

  private SysRequestUrlLogDao sysRequestUrlLogDao;

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
    sysRequestUrlLogDao = (SysRequestUrlLogDao) wac.getBean("sysRequestUrlLogDao");
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    // 获取请求URL的HTML界面,暂时不管JSP
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    String pageUrl = httpRequest.getHeader("Referer");
    if (StringUtils.isNotEmpty(pageUrl) && pageUrl.matches(".*/innosill/appweb/ptman/.*")) {
      String requestURI = httpRequest.getRequestURI();
      String method = httpRequest.getMethod();
      if (requestURI.matches(".*/innosill/a/.*")) {
        logger.info(";{},请求URL:{},请求方法:{}", pageUrl, requestURI, method);
        SysRequestUrlLogPo po = new SysRequestUrlLogPo();
        po.setId(IdGen.uuid());
        po.setPageUrl(pageUrl);
        po.setRequestUrl(requestURI);
        po.setMethod(method);
        po.preInsert();
        po.preUpdate();

        sysRequestUrlLogDao.insert(po);
      }
    }
    chain.doFilter(request, response);
  }

  @Override
  public void destroy() {

  }

}

步骤4的代码参考:
 

@Controller
public class SpringMVCURLMappingUtils {
  @Autowired
  private SysRequestMappingDao sysRequestMappingDao;

  @Autowired
  private SysRequestUrlLogDao sysRequestUrlLogDao;

  @Autowired
  private ApplicationContext applicationContext;

  @RequestMapping(value = "{esbPath}/getURLMappings")
  public void getURLMappings() {
    List<SysRequestMappingPo> poList = new java.util.LinkedList<SysRequestMappingPo>();
    RequestMappingHandlerMapping mapping = applicationContext
        .getBean(RequestMappingHandlerMapping.class);
    Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
   
    Set<Entry<RequestMappingInfo, HandlerMethod>> entrySet = handlerMethods.entrySet();
    Iterator<Entry<RequestMappingInfo, HandlerMethod>> it = entrySet.iterator();
    while (it.hasNext()) {
      Entry<RequestMappingInfo, HandlerMethod> methods = it.next();
      RequestMappingInfo key = methods.getKey();
      RequestMethodsRequestCondition method = key.getMethodsCondition();
      Set<RequestMethod> methodsSet = method.getMethods();
      String name = null;
      for (RequestMethod method1 : methodsSet) {
        if (name == null) {
          name = method1.name();
        } else {
          name = name + "," + method1.name();
        }
      }

      HandlerMethod value = methods.getValue();
      Class<?> beanType = value.getBeanType();
      Method method2 = value.getMethod();
      String controllerName = beanType.getName();
      PatternsRequestCondition patterns = key.getPatternsCondition();
      Set<String> patternsSet = patterns.getPatterns();
      for (String url : patternsSet) {
        SysRequestMappingPo po = new SysRequestMappingPo();
        String anotaiton = null;
        for (Annotation a : method2.getAnnotations()) {
          if (anotaiton == null) {
            anotaiton = a.toString();
          } else {
            anotaiton = anotaiton + "," + a.toString();
          }
        }
        po.setUrl(url);
        po.setMethod(name);
        po.setController(controllerName);
        po.setControllerMethod(method2.getName());
        po.setAnotation(anotaiton);
        po.preInsert();
        poList.add(po);
      }
    }

    sysRequestMappingDao.batchInsert(poList);
  }

  /**
   * 方案说明: 1、只能针对html页面进行处理,如果是发起的restfule风格接口返回的界面,需要在菜单目录下添加隐藏菜单;
   * 2、针对不是菜单URL的界面,需要添加隐藏菜单到菜单界面下(针对测试环境,产生权限配置的界面);无法针对不是菜单URL的界面进行匹配;
   * 3、如果是相同界面、但是在不同的模块下,仅需配置一个就可以了,如果配置了多个且权限标识不同,则将按照数据库查询顺序进行匹配。
   */
  @RequestMapping(value = "{esbPath}/getMenuIdAndUrlIds")
  public void getMenuIdAndUrlId() {
    // List<String> menuPattern = new ArrayList<String>();
    // 获取所有的请求访问URL
    List<SysRequestUrlLogPo> log = sysRequestUrlLogDao.selectAll();
    // 获取所有的menu(需要清理菜单中的脏数据)
    List<SysMenuPo> menuList = sysRequestMappingDao.selectMenu();
    // 获取所有的后台URL
    List<SysRequestMappingPo> urlList = sysRequestMappingDao.selectAll();

    // 只处理HTML静态页面
    for (SysRequestUrlLogPo sysRequestUrlLogPo : log) {
      boolean contains = false;
      boolean match = false;
      for (SysMenuPo sysMenuPo : menuList) {
        // 通过访问URL匹配菜单URL获取menuId
        if (StringUtils.isEmpty(sysMenuPo.getHref()) || !sysMenuPo.getHref().contains(".html")) {
          continue;
        }

        if (StringUtils.isNoneEmpty(sysMenuPo.getHref())) {
          String patternString = sysMenuPo.getHref();
          int indexOf = sysRequestUrlLogPo.getPageUrl().indexOf("/appweb/");
          String requestUrl = sysRequestUrlLogPo.getPageUrl().substring(indexOf);
          contains = requestUrl.contains(patternString);
        }
        if (contains) {
          sysRequestUrlLogPo.setMenuId(sysMenuPo.getId());

          // 对请求的URL与后台的URL进行匹配,获取后台的urlId
          // 去掉项目名称(TODO 需要匹配mtthod)
          String requestUrl = sysRequestUrlLogPo.getRequestUrl().substring(9);
          for (SysRequestMappingPo po : urlList) {
            match = PathMatcherUtil.match(po.getUrl(), requestUrl);
            if (match) {
              sysRequestUrlLogPo.setRequestUrlId(String.valueOf(po.getId()));
              break;
            }
          }

          // 置位处理标识
          if (contains && match) {
            sysRequestUrlLogPo.setIsMaped(true);
            // 更新数据库记录
            sysRequestUrlLogDao.updateByPrimaryKey(sysRequestUrlLogPo);
          }

          continue;
        }
      }
    }
  }
}

通过步骤3与步骤4获取的信息,以及菜单表中的信息,将权限标识添加到Java文件中:

/**
   * 根据菜单页面的权限标识,对Controller文件中的方法添加权限标识 1、获取请求此URL的html页面; 2、通过html页面的id获取权限标识;
   * 3、获取Java文件并通过反射获取对应的方法,进行方法的校验; 4、添加注解; 返回页面、对应的URL以及权限标识注解信息
   *
   * @return
   * @throws ClassNotFoundException
   */
  @RequestMapping(value = "{esbPath}/annotation", method = RequestMethod.POST)
  public Map<String, Object> addAnnotations(String pageUrl) throws ClassNotFoundException {
    Map<String, Object> resultMap = new HashMap<String, Object>();
    Map<String, Object> result = new HashMap<String, Object>();
    // 参数校验,如果传入了页面名称,则根据对应的页面对对应的URL设置权限,如果是空则需要对所有页面进行设置
    if (StringUtils.isNotEmpty(pageUrl)) {
      List<String> permission = settingAnnotationByPage(pageUrl);
      result.put("pageUrl", URLDecoder.decode(pageUrl)); // 显示页面URL
      result.put("data", permission);

      resultMap.put("code", 200);
      resultMap.put("pages", result);
      resultMap.put("desc", "操作成功");
      return resultMap;
    } else {
      List<SysRequestUrlLogPo> pages = sysRequestUrlLogDao.selectAllDis();
      if (pages == null || pages.size() == 0) {
        resultMap.put("code", 200);
        resultMap.put("desc", "未查询到系统后端的请求页面URL地址信息,请确认后重试!");

        return resultMap;
      }

      for (int i = 0; i < pages.size(); i++) {
        if (StringUtils.isEmpty(pages.get(i).getPageUrl())) {
          result.clear();
          List<String> permission = settingAnnotationByPage(pages.get(i).getPageUrl());
          result.put("pageUrl", pages.get(i).getPageUrl()); // 显示页面URL
          result.put("data", permission);

          resultMap.put("pageUrl" + i, result);
        } else {
          continue;
        }
      }
      resultMap.put("code", 200);
      resultMap.put("desc", "操作成功!");
      return resultMap;
    }
  }

  /**
   * 根据页面标识进行权限标识设置
   *
   * @param pageUrl
   * @return
   * @throws ClassNotFoundException
   */
  private List<String> settingAnnotationByPage(String pageUrl) throws ClassNotFoundException {
    List<String> list = new ArrayList<String>();
    JSONObject json = new JSONObject();
    // 参数校验
    if (StringUtils.isEmpty(pageUrl)) {
      String result = "页面URL信息错误,设置权限注解失败!";
      list.add(result);

      return list;
    }

    // 获取页面访问的URL信息
    List<SysRequestUrlLogPo> pages = sysRequestUrlLogDao.selectByPageUrl(pageUrl);
    if (pages == null || pages.size() == 0) {
      String result = "当前页面访问后端的URL数量为0,如结果不正确,则请确认数据后重试!";
      list.add(result);

      return list;
    }

    // 根据界面ID获取界面的权限标识(包含页面以及页面的下一级)
    List<SysMenuPo> menuList = sysRequestUrlLogDao.getPermission(pages.get(0).getMenuId());
    String persisson = "";
    for (SysMenuPo po : menuList) {
      if (StringUtils.isNotEmpty(po.getPermission())) {
        if (StringUtils.isEmpty(persisson)) {
          persisson = po.getPermission();
        } else {
          persisson += "," + po.getPermission();
        }
      }
    }

    // 页面未设置任何权限标识
    if (StringUtils.isEmpty(persisson)) {
      String result = "当前页面未设置任何权限标识!";
      list.add(result);

      return list;
    }

    // 后台URL中添加此界面的访问权限标识(只能增加,因为测试数据的原因,暂时没法给后端URL减权限标识,除非有完整的数据以后端URL为基础重新设置)
    for (SysRequestUrlLogPo po : pages) {
      // 如果为空则默认是数据问题
      if (StringUtils.isEmpty(po.getRequestUrlId())) {
        continue;
      } else {
        boolean result = addAnnotation(po.getRequestUrlId(), persisson);
        if (result == true) {
          json.put("requestURL", po.getRequestUrl());
          json.put("permission", persisson);
          json.put("result", "成功");

          list.add(json.toJSONString());
          json.clear();
        } else {
          json.put("requestURL", po.getRequestUrl());
          json.put("permission", persisson);
          json.put("result", "失败");

          list.add(json.toJSONString());
          json.clear();
        }
      }
    }

    return list;
  }

  /**
   * 向Java文件中添加权限标识注解
   *
   * @param requestUrlId
   * @param persisson
   * @return
   * @throws ClassNotFoundException
   */
  private boolean addAnnotation(String requestUrlId, String annotations) {
    // 参数校验
    if (StringUtils.isEmpty(requestUrlId) || StringUtils.isEmpty(annotations)) {
      return false;
    }

    // 查询请求的URL信息
    SysRequestMappingPo urlMap = sysRequestMappingDao
        .selectByPrimaryKey(Integer.valueOf(requestUrlId));
    // 确认添加权限标识的类和方法正确(通过反射获取对应的URLID是否有对应的方法)
    int paramCount = 0;
    String paramStr = "";
    Method method = null;
    try {
      Class<?> c1 = Class.forName(urlMap.getController());
      String params = urlMap.getParams();
      Class<?>[] paramArr = null;
      if (StringUtils.isNotEmpty(params)) {
        String[] params2 = params.split(",");
        paramArr = new Class[params2.length];
        for (int i = 0; i < params2.length; i++) {
          // String param = params2[i].substring(params2[i].lastIndexOf("\\."));
          Class<?> c2 = Class.forName(params2[i]);
          paramArr[i] = c2;
        }
      } else {
        paramArr = new Class[0];
      }

      method = c1.getDeclaredMethod(urlMap.getControllerMethod(), paramArr);
      // 这段可以不要,留着不报错
      if (method == null) {
        return false;
      }
    } catch (Exception e) {
      // 当找不到对应的类和方法时返回false,表示数据库数据有问题
      return false;
    }
    // 获取Java文件
    String controller = urlMap.getController();
    String temp = controller.replaceAll("\\.", Matcher.quoteReplacement(File.separator));
    String controllerPathFile = PREV + temp + ".java";
    // 确认添加权限标识的类和方法正确(通过反射获取对应的URLID是否有对应的方法)
    File file = new File(controllerPathFile);
    if (file.exists() && file.isFile()) {
      try {
        // 像Java文件添加权限标识
        File newFile = decorateFileByCopyModel(file, method, annotations);
        if (newFile != null) {
          file.delete();
          newFile.renameTo(file);

          return true;
        }
      } catch (IOException e) {
        // 权限标识添加失败
        return false;
      }
    }
    return false;
  }

  /**
   * 需要添加注解的Java文件中针对指定的方法添加注解
   *
   * @param oldFile
   * @param method
   * @param annotations
   * @param paramCount
   *          //防止重载,对比个数不一致
   * @param paramStr
   *          //防止重载,对比参数类型不一致
   * @return
   * @throws IOException
   */
  public File decorateFileByCopyModel(File oldFile, Method method, String annotations)
      throws IOException {
    // 读取文件到FileInputStream中
    FileInputStream fileInputStream = new FileInputStream(oldFile);
    // 读取FileInputStream到InputStreamReader中,然后再读取到BufferedReader中
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));

    // 当前行,当找到对应的方法时,则是方法所在的行(一行)
    String thisLine = "";
    // 权限标识行(有可能是多行,带有换行符)
    String permissionLine = "";
    // 权限标识信息,原始注解信息
    String permissionTemp = "";
    // 最终的权限标识信息
    String permissionFinal = "";
    int lineNumber = 0; // 方法的行号
    int pemissionStartLineNumber = 0;
    int pemissionEndLineNumber = 0;
    while ((thisLine = bufferedReader.readLine()) != null) {
      lineNumber++;
      // 如果在运行过程中捕获到新的权限标识注解,则抛弃掉之前捕获的注解(没有遇到public,且方法名与需要添加注解的方法相同时)
      if (StringUtils.isNotEmpty(permissionLine)) {
        permissionLine += thisLine;
        if (permissionLine.contains(")")) {
          pemissionEndLineNumber = lineNumber;

          // 将权限标识信息置空
          permissionTemp = permissionLine;
          permissionLine = "";
        }
      }

      if (thisLine.contains("@RequiresPermissions")) {
        // 复制开始行号
        pemissionStartLineNumber = lineNumber;
        // 权限标识开始信息
        permissionLine = thisLine;
        // 如果包含结束符号,则复制结束行号
        if (permissionLine.contains(")")) {
          pemissionEndLineNumber = lineNumber;

          // 将权限标识信息置空
          permissionTemp = permissionLine;
          permissionLine = "";
        }
      }

      // 根据代码规范,如果遇到public但是不包含指定的方法名,则抛弃捕获的注解(暂时不管重载的情况,假设几乎没有)
      if (thisLine.contains("public") && !thisLine.contains(method.getName())) {
        // 清除行号信息
        pemissionStartLineNumber = 0;
        pemissionEndLineNumber = 0;
        // 清除注解信息
        permissionTemp = "";
      }

      // 找到方法的位置
      if (thisLine.contains(method.getName()) && thisLine.contains("public")) {
        // 校验方法位置正确且排除重载的方法(假定方法没有重载)
        String methodString = method.toString();
        String paramStr = methodString.substring(methodString.indexOf(method.getName()),
            methodString.lastIndexOf(")") + 1);
        paramStr = paramStr.substring((paramStr.indexOf("(") + 1), paramStr.lastIndexOf(")"));
        String[] paraTypeArr = paramStr.split(",");
        // 方法开始没有权限标识注解时
        if (StringUtils.isEmpty(permissionTemp)) {
          pemissionStartLineNumber = lineNumber;
          pemissionEndLineNumber = lineNumber;
        }
        // 添加注解,需要读取已有的注解,并添加不同的注解(存在权限标识注解或者不存在权限标识注解),如果返回值为空,则不对文件进行更改,如果返回不为空则对文件进行修改
        permissionFinal = addAnnotaionToMethod(permissionTemp, annotations);

        break;
      }
    }
    // 重置行号
    lineNumber = 0;
    // 关闭数据查询的数据流
    bufferedReader.close();

    if (StringUtils.isNotEmpty(permissionFinal)) {
      // 读取文件到FileInputStream中
      FileInputStream fileInputStream2 = new FileInputStream(oldFile);
      // 读取FileInputStream到InputStreamReader中,然后再读取到BufferedReader中
      BufferedReader bufferedReader2 = new BufferedReader(new InputStreamReader(fileInputStream2));

      // 创建输出的文件
      String oldFileName = oldFile.getName().replace(".java", "");
      String tempFileName = oldFileName + System.currentTimeMillis();
      String absolutePath = oldFile.getAbsolutePath();
      absolutePath = absolutePath.substring(0, absolutePath.lastIndexOf("\\"));
      File newFile = new File(absolutePath + "\\" + tempFileName);
      newFile.createNewFile();
      // 新建的文件绑定到FileOutputStream上
      FileOutputStream fileOutputStream = new FileOutputStream(newFile);
      // 把FileOutputStream绑定到PrintWriter上
      PrintWriter printWriter = new PrintWriter(fileOutputStream);
      // 输出文件
      lineNumber = 0; // 方法的行号
      while ((thisLine = bufferedReader2.readLine()) != null) {
        lineNumber++;
        if (lineNumber < pemissionStartLineNumber) {
          printWriter.println(thisLine);
        } else if (lineNumber == pemissionStartLineNumber) {
          if (pemissionStartLineNumber == pemissionEndLineNumber) {
            printWriter.println(permissionFinal);
          } else {
            // 不输出原来的权限标识信息
          }
        } else {
          if (pemissionStartLineNumber < pemissionEndLineNumber
              && pemissionEndLineNumber == lineNumber) {
            printWriter.println(permissionFinal);
          } else {
            printWriter.println(thisLine);
          }
        }
      }

      // 关闭输入输出流
      bufferedReader2.close();
      printWriter.flush();
      printWriter.close();

      return newFile;
    } else {
      return null;
    }

  }

  /**
   * 组合权限标识注解并去重,注解格式全采用@RequiresPermissions({"sys:dict:edit",...})
   *
   * @param permissionTemp
   * @param annotations
   * @return
   */
  private String addAnnotaionToMethod(String permissionTemp, String annotations) {
    // 方法之前没有权限标识注解,且传进来的注解参数为空
    if (StringUtils.isEmpty(permissionTemp) && StringUtils.isEmpty(annotations)) {
      return "";
    } else if (StringUtils.isEmpty(permissionTemp) && StringUtils.isNotEmpty(annotations)) {
      // 一开始不存在权限标识数据
      String[] annoArr = annotations.split(",");
      String permission = "@RequiresPermissions({";
      for (int i = 0; i < annoArr.length; i++) {
        if (StringUtils.isNotEmpty(annoArr[i])) {
          permission += "\"" + annoArr[i] + "\"" + ",";
        }
      }

      // 截取掉逗号
      permission = permission.substring(0, (permission.length() - 1));
      // 补起尾巴字符
      permission += "})";

      return permission;
    } else if (StringUtils.isNotEmpty(permissionTemp) && StringUtils.isEmpty(annotations)) {
      // 没有需添加的注解信息
      return "";
    } else if (StringUtils.isNotEmpty(permissionTemp) && StringUtils.isNotEmpty(annotations)) {
      // 去除原始注解中的换行、空格、制表符符号
      permissionTemp = permissionTemp.replaceAll("\\r", "").replaceAll("\\n", "")
          .replaceAll(" ", "").replaceAll("\\t", "");
      String permiStr = "";
      // 具有原始的权限标识注解与需要新增标识注解(原始注解有@RequiresPermissions("sys:dict:edit")与@RequiresPermissions({"sys:dict:edit"}))形式
      if (permissionTemp.contains("{") && permissionTemp.contains("}")) {
        // 获取权限标识符字符串并去掉双引号
        permiStr = permissionTemp.substring(23, permissionTemp.indexOf("}")).replaceAll("\"", "");
      } else {
        // 获取权限标识符字符串并去掉双引号
        permiStr = permissionTemp.substring(22, permissionTemp.indexOf(")")).replaceAll("\"", "");
      }

      permiStr = combinationPermisson(permiStr, annotations);
      String[] annoArr = permiStr.split(",");
      String permission = "@RequiresPermissions({";
      for (int i = 0; i < annoArr.length; i++) {
        if (StringUtils.isNotEmpty(annoArr[i])) {
          permission += "\"" + annoArr[i] + "\"" + ",";
        }
      }

      // 截取掉逗号
      permission = permission.substring(0, (permission.length() - 1));
      // 补起尾巴字符
      permission += "})";

      return permission;
    }

    return "";
  }

  /**
   * 构造营运范围信息
   *
   * @param busisCope
   * @param vehicleBusisCope
   * @return
   */
  private String combinationPermisson(String oldPermission, String newPermission) {
    // 参数校验
    if (StringUtils.isEmpty(oldPermission)) {
      return newPermission;
    }

    if (StringUtils.isEmpty(newPermission)) {
      return oldPermission;
    }

    // 将字符串转化为数组
    List<String> scopeList = null;
    if (StringUtils.isNotEmpty(oldPermission)) {
      String[] scopes = oldPermission.split(",");
      scopeList = new ArrayList<String>(Arrays.asList(scopes));
    }

    if (StringUtils.isNotEmpty(newPermission)) {
      String[] scopes = newPermission.split(",");
      if (scopeList != null && scopeList.size() > 0) {
        List<String> asList = new ArrayList<String>(Arrays.asList(scopes));
        scopeList.addAll(asList);
      } else {
        scopeList = new ArrayList<String>(Arrays.asList(scopes));
      }
    }

    List<String> scopeList2 = new ArrayList<String>(new TreeSet<String>(scopeList));

    return org.apache.commons.lang.StringUtils.join(scopeList2, ",");
  }

将权限标识写到Java文件中存在如下缺陷:

1、不支持Controller中有重载的public修饰的方法;

2、暂不支持删除权限标识注解和权限标识项的功能,只支持新增和去重权限标识的功能。

3、没有导入RequiredPermissions的注解包,需要开发人员在项目中自动保存文件导入。

4、不支持在权限标识中有逗号(,)的,否则会被视为两个权限标识。

5、不支持识别权限标识中的通配符并进行比较后去重操作。

支持功能:

1、支持在原始java文件中没有权限标识注解的文件中添加权限标识注解。

2、支持在原始Java文件中有类似@RequiredPermissions("user")或@RequiredPermissions({"user"})格式的单行注解中继续添加权限标识。

3、支持原始Java文件中有类似@RequiredPermissions({"user","sys:dispatch:view"})格式的多行注解中继续添加非重复的权限标识。

4、会对已经存在的配置重复的权限标识进行去重操作。

建议:

1、建议不在页面或者操作的权限标识符中使用通配符或缺省字符串的操作,否则可能会扩大操作权限。

2、建议在开发过程中如果单个后端URL的权限标识符过长,建议手动更改为使用通配符或者缺省字符串的形式。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值