首先这不是一个介绍或者使用SpringSecurity的博客。他是使用自定义注解和拦截器实现的权限管理(只供学习不可用于生产环境)
技术栈:
SpringBoot 2.1.6
MySQL5.7
大体思路:
- 使用拦截器拦截请求,在拦截器中使用 HandlerMethod 类获取当前请求方法上的自定义权限注解。判断是否有此访问权限。
- 动态改变注解值(实际生产环境中不同角色拥有的权限不可能一直不发生改变)
首先实现第一步:
创建两个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyClass {
// 放置在类上
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {
String[] v() default {"-1"}; // 防止在方法上
// v() 中保存的是权限ID 默认-1 即没有任何角色可以访问
}
编写一个Controller并添加注解
@RestController
@RequestMapping("/myController")
@MyClass
public class MyController{
@RequestMapping("/get")
@MyTest(v = {"1", "2", "3"}) // 赋值上角色ID为 1,2,3才能访问
public String get() {
return "Hello,world";
}
@RequestMapping("/set")
@MyTest
public String set() {
return "Hello,world";
}
}
编写拦截器(不要忘记使用这个拦截器)
package com.annie.interceptor;
import com.alibaba.fastjson.JSON;
import com.annie.annotation.MyTest;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
MyTest annotation = method.getAnnotation(MyTest.class);
if (annotation != null) {
// 从 request 或者 session 中获取当前请求角色
String roleId = "1"; // 假定请求角色的权限ID为1
String[] v = annotation.v(); // 获取权限
for (String s : v) {
if (s.equals(roleId)) { // 判断是否拥有访问权限
return true;
}
}
return false;
}
return false;
}
}
到此一个简单的权限判断就算是完成了但是我们不能预知一个方法可以使用的所有角色。所以我们要在使用一个方法让这个注解可以动态赋值。因为我们使用的是Spring框架,Spring在项目启动初期就将所有的Bean收集起来了。我们这要拿到那个容器就可以拿到当前项目中所有方法。在通过去数据库类比。为每个自定义注解动态设置值。
package com.annie.task;
import com.alibaba.fastjson.JSON;
import com.annie.annotation.MyClass;
import com.annie.annotation.MyTest;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.lang.reflect.*;
import java.util.Map;
@Component
public class MyTask implements ApplicationContextAware {
private ApplicationContext applicationContext; // 保存所有Bean的容器
@Scheduled(fixedRate = 1000)
public void task01() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Map<String, Object> beansWithAnnotation = this.applicationContext.getBeansWithAnnotation(MyClass.class);
for (String key : beansWithAnnotation.keySet()) {
// 反射获取类,这里有一个问题。我在使用 Swagger2 时添加了注解。用于Swagger2使用了动态代理我拿不到注解数据。
// 于是这个我提供一个备用方案。 获取这个类的名称然后 通过 Class.forName() 获取注解值
Object testController = beansWithAnnotation.get(key);
Class<?> aClass = testController.getClass();
Method[] methods = aClass.getDeclaredMethods();
for (Method m : methods) {
MyTest annotation = m.getAnnotation(MyTest.class);
if (annotation == null) {
continue;
}
// 获取代理处理器
InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
// 过去私有 memberValues 属性
Field f = invocationHandler.getClass().getDeclaredField("memberValues");
f.setAccessible(true);
// 获取实例的属性map
Map<String, Object> memberValues = (Map<String, Object>) f.get(invocationHandler);
// 修改属性值
String arr[] = {"1", "2"};
memberValues.put("v", arr);
System.out.println(JSON.toJSONString(annotation.v()));
}
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
我这里懒得获取数据库中的值进行类比。直接写的死值。
项目下载:下载
扩展内容github个人博客