自定义注解+拦截器/AOP切面 实现权限管理

一、通过拦截器实现

1 权限表

为了方便,我直接用的现成的权限表,这是表结构
在这里插入图片描述

2 自定义注解

首先,创建一个自定义注解,用于controller层的方法或类上

// @Target表示该注解可以用在方法和类上
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiPermission {

    PermissionEnum permission();

}

3 权限枚举类

PermissionEnum枚举内是权限枚举,值对应数据表中 api_permission 字段,后期会通过这个来校验权限

@Getter
public enum PermissionEnum {

    DEVICELIST_A("north:device:list"),
    DEVICEINFO_A("north:device:info"),
    DEVICEEXTEND_A("north:device:info:extend"),
    DEVICEHISTORICALDATA_A("north:device:historicalData"),
    DEVICESTATUS_A("north:device:status")
    ;

    private String value;

    PermissionEnum(String value) {
        this.value = value;
    }

}

4 拦截器

最重要的就是拦截器了,通过拦截器获取方法或类上的注解信息,然后比对权限表,判断是否拥有权限

@Component
@Slf4j
public class PermissionInterceptor implements HandlerInterceptor {

    @Autowired
    INorthAppApiService iNorthAppApiService;

    // 前置处理器
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            ApiPermission methodAnnotation = method.getMethodAnnotation(ApiPermission.class);
            //获取方法上的注解
            if (methodAnnotation == null) {
                System.out.println("内部错误");
                return false;
            }
            String appId = request.getParameter("appId");
            List<NorthAppApi> northAppApiList = iNorthAppApiService.getInfo(appId);
            boolean perBools = false;
            for (NorthAppApi northAppApi : northAppApiList) {
                if(northAppApi.getApiPermission().equals(methodAnnotation.permission().getValue())) {
                    perBools = true;
                }
            }
            if(!perBools) {
                System.out.println("无权限");
                return false;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

5 WebMvcConfig

别忘了将拦截器注册,不然拦截器不会生效

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private PermissionInterceptor permissionInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加拦截器
        registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");
    }
}

6 使用

最后在controller层使用自己的自定义注解,在这里要注意,要使用@RequestMapping,不然会获取不到注解信息,但是我同事使用Post又可以获取,我也不知道为什么,有知道的可以评论告知

@RestController
@RequestMapping("/device")
public class DeviceController {

    @Autowired
    private DeviceService deviceService;

    // POSTMapping不行
    @ApiPermission(permission = PermissionEnum.DEVICELIST_A)
    @RequestMapping(value = "/list" )
    @ResponseBody
    public List<DeviceInfo> list(String appId) {
        System.out.println(deviceService.list());
        return null;
    }

    @ApiPermission(permission = PermissionEnum.DEVICEINFO_A)
    @RequestMapping(value = "/info" )
    @ResponseBody
    public List<DeviceInfo> info(String appId) {
        System.out.println(deviceService.list());
        return null;
    }
}

当我访问:http://localhost:9999/device/list?appId=dididache时,就可以通过权限返回数据,因为/device/list这个接口中写了我的自定义注解,传的appId可以查到该应用下拥有自定义注解中的权限记录,所以放行

如果我访问:http://localhost:9999/device/info?appId=dididache,我事先把数据表中第四条记录删了,因此权限表中 appId为dididache 的应用就不存在这个权限,因此无法放行

二、通过AOP实现

前面三个步骤跟上面的一样

4 切面类

@Aspect
@Component
public class InnerAuthAspect implements Ordered
{
    @Around("@annotation(innerAuth)")
    public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable
    {
        String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE);
        // 内部请求验证
        if (!StringUtils.equals(SecurityConstants.INNER, source))
        {
            throw new BusinessException("没有内部访问权限,不允许访问");
        }
        return point.proceed();
    }

    /**
     * 确保在权限认证aop执行前执行
     */
    @Override
    public int getOrder()
    {
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }
}

这个切面类,环绕通知 @innerAuth注解,所以当执行带 @innerAuth 注解的方法或类时,就会执行这个通知逻辑,也就实现了代替拦截器实现权限校验的功能

然后可以跳过第五步,直接第六个步骤调用接口试一下,效果跟拦截器那个版本一样的

你可能感兴趣的:(java,springboot,java)