primers
Recently, I have been working on a small project involving permission. Instead of using the Shiro framework and Spring Security, I wanted to control my own permissions.
- Permissions on the table
- Character sheet
- Permissions – Role table
- The users table
- User Role Table
Creating a table, then customizing annotations, using interceptors, is a bad recipe.
Problems encountered
The nasty problem is that I want to use annotations on every interface that requires permissions, and I need to identify what permissions are required in the value of the annotations. So I started with an enumeration class, defined some permission codes, and then I saved a copy in the database. The permission code in the enumeration class is mainly used in annotations. The permission of the role is configured in the database. The problem with this is that if you define a new interface, you need to add new permissions. So I need to change both the database and the enumeration class. Can’t endure…
The solution
The best way to do this is to automatically check for new interfaces when the project starts, and if so, automatically synchronize the permission code to the database. So the permission code has to be identified by something that’s existing and unique. That’s the URL of the interface request path
start
First of all, baidu, after all, I can think of it, it must have already been thought of. 🤣 Sure enough, a way was found: using initializingBeans
- implementation
InitializingBean
Interface, copyafterPropertiesSet
Methods. The method is then executed when the bean is initialized.
That’s fine, because to get all the request paths, you need to use the getBeansWithAnnotation method of the ApplicationContext. I have a utility class that gets the ApplicationContext object at any time.
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(a) {
return context;
}
public static Object getBean(String beanName) {
returncontext.getBean(beanName); }}Copy the code
Then use the ApplicationContext context = SpringBeanFactoryUtils. GetApplicationContext (); Get the ApplicationContext object and the result is NULL…
Of course, you can’t get it using the utility class, but you can get it by direct injection. But if it works, there won’t be any more. The utility class is not available because it implements the ApplicationContextAware interface. In the startup process of SpringBoot, the afterPropertiesSet of the InitializingBean precedes the setApplicationContext of the ApplicationContextAware. So the result of getApplicationContext is null. However, there is an ApplicationContext in the container, so using Resource or Autowired can be injected. Take a look at the SpringBoot startup process.
The second way
When knock code to see a class SpringApplicationRunListener, check this point in class, {@link SpringApplication} {@code run} Method 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
* @since1.0.0 * /
public interface SpringApplicationRunListener {
/** * This method is called immediately when the run() method starts executing and can be used to do some work during the very early stages of initialization */
default void starting(a) {}/** * This method is called when the environment is built and before the ApplicationContext is created
default void environmentPrepared(ConfigurableEnvironment environment) {}/** * This method is called when the ApplicationContext is built
default void contextPrepared(ConfigurableApplicationContext context) {}/** * This method is called after the ApplicationContext has been loaded, but has not been refreshed
default void contextLoaded(ConfigurableApplicationContext context) {}/** * This method is called */ after the ApplicationContext has been refreshed and started, but before the CommandLineRunners and ApplicationRunner have been called
default void started(ConfigurableApplicationContext context) {}/** * This method is called before the run() method is finished
default void running(ConfigurableApplicationContext context) {}/** * This method is called when the application runs incorrectly
default void failed(ConfigurableApplicationContext context, Throwable exception) {}}Copy the code
However, to use the listener, you need to create a configuration file first
- in
resources
Directory to create a folder namedMETA-INF
- Create a file
spring.factories
- Finally, configure the started listeners
Org. Springframework. Boot. SpringApplicationRunListener = you achieve listener classes
Here is my configuration org. Springframework. Boot. SpringApplicationRunListener = com. Ler. Sparrowmanager. Config. PermissionInitListener
In the listener class you implement, you need a constructor, which is SpringBoot’s init method to create the listener. If there is none, an error will be reported
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
Copy the code
public PermissionInitListener(SpringApplication application, String[] args) {
super(a); }Copy the code
Just copy the started method. Here’s the complete example
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;
/** * Scan all urls added to database at startup **@author lww
* @dateThe 2020-04-11 09:27 * /
@Slf4j
public class PermissionInitListener implements SpringApplicationRunListener {
public PermissionInitListener(SpringApplication application, String[] args) {
super(a); }@Override
public void started(ConfigurableApplicationContext context) {
// Get the property request path in the application.properties configuration
ConfigurableEnvironment environment = context.getEnvironment();
String property = environment.getProperty("server.servlet.context-path");
if (StringUtils.isBlank(property)) {
property = "";
}
SmPermission permission;
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
Map<String, Object> beanMap = context.getBeansWithAnnotation(RestController.class);
for(Entry<String, Object> objectEntry : beanMap.entrySet()) { Object value = objectEntry.getValue(); Class<? > valueClass = value.getClass();// Get the request path on the class
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();
// Get the URL on the method
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 and GetMapping annotations have a name attribute that can be used to store permission names
permission.setPermName(rname);
// Check whether it already exists
SmPermission selectOne = permission.selectOne(new QueryWrapper<SmPermission>().lambda()
.eq(SmPermission::getPermsUrl, permission.getPermsUrl()));
log.info("PermissionInitScanConfig_started_selectOne:{}", JSONObject.toJSONString(selectOne));
if (selectOne == null) {
// There is no insertion. Here is the AR feature of MyBtis-plus
boolean insertPermission = permission.insert();
log.info("PermissionInitScanConfig_started_insertPermission:{}", insertPermission);
}
}
}
}
}
Copy the code
Results:
Execution at startup:
Stored in the database permission table:
Finally:
The interceptor is used to compare the requested URL to the url of the user’s role to determine the permission. The advantage of this is that if an interface is added, no additional configuration is required and it is automatically inserted into the database permission table at startup.