专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

权限相关-SpringBoot 在启动时获取所有的请求路径url

引子

最近在做一个小项目,涉及到权限相关。没有使用Shiro框架和Spring Security,是想自己控制权限。

  • 权限表
  • 角色表
  • 权限-角色表
  • 用户表
  • 用户角色表

建表完成,然后自定义注解,使用拦截器,都是用烂的套路。

遇到的问题

有一个恶心的问题是,在需要权限的接口上,我要都使用注解,而且还要在注解的value中标识需要什么权限。 所以一开始我是用枚举类,定义了一些权限code,然后数据库里也存了一份。
枚举类中的权限code主要是在注解里用。数据库中的用于配置角色的权限。
这样有一个问题,如果定义了新的接口,需要添加新的权限;那么我需要同时改数据库和枚举类。
忍不了…

解决办法

最好的方法就是在项目启动的时候,自动的检查有没有新的接口,如果有就自动的把权限code同步到数据库中。
所以权限code就要用现有的,且唯一的东西来标识。
那就是接口请求路径的url

开始动手

首先百度一下,毕竟我能想到的,肯定早就有人想到了。
果然,找到了一种方法:使用InitializingBean

  • 实现 InitializingBean 接口,复写 afterPropertiesSet方法。 然后就会在初始化bean的时候执行该方法。

问题不大,因为要获取所有的请求路径,需要使用ApplicationContextgetBeansWithAnnotation方法。而我有一个工具类,可以随时获取ApplicationContext对象。

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author lww
 * @date 2019-03-27 11:20 AM
 */
@Component
public class SpringBeanFactoryUtils implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringBeanFactoryUtils.context = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return context;
    }

    public static Object getBean(String beanName) {
        return context.getBean(beanName);
    }
}

然后使用 ApplicationContext context = SpringBeanFactoryUtils.getApplicationContext(); 获取ApplicationContext对象,结果为null…

当然不是这个方法不行,是使用工具类取不到,但是直接注入是可以取到的。不过如果成功了,也不会有后面的事了。工具类取不到,是因为实现的是ApplicationContextAware接口。在SpringBoot的启动流程中,InitializingBeanafterPropertiesSetApplicationContextAwaresetApplicationContext之前。所以这时候是getApplicationContext的结果是null。但是这时容器中是有ApplicationContext的,所以使用Resource或者Autowired是可以注入的。可以去看一看SpringBoot的启动流程。

第二种方法

在敲代码的时候看到一个类SpringApplicationRunListener,点进去看看这个类,这是一个接口 看到Listener for the {@link SpringApplication} {@code run} method.,一看就知道,就是这个类啦。

package org.springframework.boot;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;

/**
 * Listener for the {@link SpringApplication} {@code run} method.
 * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}
 * and should declare a public constructor that accepts a {@link SpringApplication}
 * instance and a {@code String[]} of arguments. A new
 * {@link SpringApplicationRunListener} instance will be created for each run.
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Andy Wilkinson
 * @since 1.0.0
 */
public interface SpringApplicationRunListener {

    /**
     * 在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
     */
    default void starting() {
    }

    /**
     * 当environment构建完成,ApplicationContext创建之前,该方法被调用
     */
    default void environmentPrepared(ConfigurableEnvironment environment) {
    }

    /**
     * 当ApplicationContext构建完成时,该方法被调用
     */
    default void contextPrepared(ConfigurableApplicationContext context) {
    }

    /**
     * 在ApplicationContext完成加载,但没有被刷新前,该方法被调用
     */
    default void contextLoaded(ConfigurableApplicationContext context) {
    }

    /**
     * 在ApplicationContext刷新并启动后,CommandLineRunners和ApplicationRunner未被调用前,该方法被调用
     */
    default void started(ConfigurableApplicationContext context) {
    }

    /**
     * 在run()方法执行完成前该方法被调用
     */
    default void running(ConfigurableApplicationContext context) {
    }

    /**
     * 当应用运行出错时该方法被调用
     */
    default void failed(ConfigurableApplicationContext context, Throwable exception) {
    }
}

不过如果要使用监听器,需要先创建一个配置文件

  • resources目录下创建一个文件夹,名称为META-INF
  • 创建一个文件 spring.factories
  • 最后配置启动的监听器org.springframework.boot.SpringApplicationRunListener=你实现的监听器类

这里我的配置是 org.springframework.boot.SpringApplicationRunListener=com.ler.sparrowmanager.config.PermissionInitListener

在实现的监听器类,需要一个构造函数,这个是SpringBoot创建这个监听器的init方法,如果没有会报错

Exception in thread "main" java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.boot.SpringApplicationRunListener : com.ler.sparrowmanager.config.PermissionInitListener
    at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:445)
    at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:427)
    at org.springframework.boot.SpringApplication.getRunListeners(SpringApplication.java:416)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:304)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
    at com.ler.sparrowmanager.SparrowManagerApplication.main(SparrowManagerApplication.java:12)
Caused by: java.lang.NoSuchMethodException: com.ler.sparrowmanager.config.PermissionInitListener.<init>(org.springframework.boot.SpringApplication, [Ljava.lang.String;)
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:440)
    ... 6 more

  public PermissionInitListener(SpringApplication application, String[] args) {
        super();
    }

复写started方法就可以了,下面是完整的例子

package com.ler.sparrowmanager.config;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ler.sparrowmanager.domain.SmPermission;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Map.Entry;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 启动时 扫描所有url 添加到数据库
 *
 * @author lww
 * @date 2020-04-11 09:27
 */
@Slf4j
public class PermissionInitListener implements SpringApplicationRunListener {

    public PermissionInitListener(SpringApplication application, String[] args) {
        super();
    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        //获取application.properties配置中的属性 请求路径
        ConfigurableEnvironment environment = context.getEnvironment();
        String property = environment.getProperty("server.servlet.context-path");
        if (StringUtils.isBlank(property)) {
            property = "";
        }

        SmPermission permission;
        //获取所有controller,因为我的Controller使用的都是RestController注解,如果使用的是Controller注解,要自己处理一下
        Map<String, Object> beanMap = context.getBeansWithAnnotation(RestController.class);
        for (Entry<String, Object> objectEntry : beanMap.entrySet()) {
            Object value = objectEntry.getValue();
            Class<?> valueClass = value.getClass();
            //获取类上的 请求路径
            RequestMapping mapping = valueClass.getAnnotation(RequestMapping.class);
            String mid = "";
            if (mapping != null) {
                String[] value1 = mapping.value();
                if (value1.length == 1) {
                    mid = value1[0];
                }
            }
            Method[] methods = valueClass.getMethods();
            for (Method method : methods) {
                String rname = "";
                String rcp = property;
                permission = new SmPermission();

                //获取方法上的url
                RequestMapping rm = method.getAnnotation(RequestMapping.class);
                GetMapping gm = method.getAnnotation(GetMapping.class);
                PostMapping pm = method.getAnnotation(PostMapping.class);
                PutMapping pum = method.getAnnotation(PutMapping.class);
                DeleteMapping dm = method.getAnnotation(DeleteMapping.class);
                if (rm != null && rm.value().length == 1) {
                    rcp = rcp + mid + rm.value()[0];
                    rname = rm.name();
                } else if (gm != null && gm.value().length == 1) {
                    rcp = rcp + mid + gm.value()[0];
                    rname = gm.name();
                } else if (pm != null && pm.value().length == 1) {
                    rcp = rcp + mid + pm.value()[0];
                    rname = pm.name();
                } else if (pum != null && pum.value().length == 1) {
                    rcp = rcp + mid + pum.value()[0];
                    rname = pum.name();
                } else if (dm != null && dm.value().length == 1) {
                    rcp = rcp + mid + dm.value()[0];
                    rname = dm.name();
                } else {
                    continue;
                }
                permission.setPermsUrl(rcp);
                //RequestMapping,GetMapping这些注解都有一个 name属性,可以用来存 权限名称
                permission.setPermName(rname);
                //检查是否已经存在,存在不插入
                SmPermission selectOne = permission.selectOne(new QueryWrapper<SmPermission>().lambda()
                        .eq(SmPermission::getPermsUrl, permission.getPermsUrl()));
                log.info("PermissionInitScanConfig_started_selectOne:{}", JSONObject.toJSONString(selectOne));
                if (selectOne == null) {
                    //不存在插入,这里用了 Mybatis-Plus的 AR特性
                    boolean insertPermission = permission.insert();
                    log.info("PermissionInitScanConfig_started_insertPermission:{}", insertPermission);
                }
            }
        }
    }
}

结果:

启动时执行:

89_1.png

在数据库权限表中存储:

89_2.png

最后:

再使用拦截器,对请求的url和用户的角色对应的url进行比较来判断权限就可以了。
这样的好处就是,如果新增了接口,不需要额外配置,在启动的时候就会自动插入到数据库权限表中了。

文章永久链接:https://tech.souyunku.com/33760

未经允许不得转载:搜云库技术团队 » 权限相关-SpringBoot 在启动时获取所有的请求路径url

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们