• JAVA——通过自定义注解实现每次程序启动时,自动扫描被注解的方法,获取其路径及访问该路径所需的权限并写入数据库


    JAVA——通过自定义注解实现每次程序启动时,自动扫描被注解的方法,获取其路径及访问该路径所需的权限并写入数据库

    一、需求背景

    在spring cloud微服务项目中,要想实现鉴权,有以下两个方案:

    • 方案一:和网关关系不是特别大。auth-service 进行登录认证;各个微服务自己去做鉴权。
      网关最多也就只是做一些 jwt-token 的合法性校验(判断它是否是伪造的)。

    • 方案二:和网关关系很大。auth-service 进行登录认证;gate网关进行鉴权;各个微服务就不用再做鉴权(不需要去"引" spring-security)。

    这里选择方案二:
    方案二的优势在于:

    • 整个认证鉴权的工作的"参与方"变少了,更利于 bug 的定位和编码;
    • 各个微服务之间的调用变简单了,不用再相互之间传递 jwt-token 了。

    但是方案二的"代价":

    以前某个路径需要什么权限才能访问,是在 spring-security 的配置类中配置,或者是使用注解标明。现在需要将 路径-访问权 的关系存到数据库中

    那么就要求我们每写一个controller方法,都需要在 路径-访问权 表中插入一条数据,来表示访问该方法的路径所需要的权限

    因此决定采用自定义注解的方法,在需要进行权限控制的controller方法上面加上注解:
    用注解的value来表示访问该路径所需要的权限。
    在每次微服务启动的时候自动获取这些注解,并将这些信息写入 路径-访问权 表中。

    二、实现步骤

    1.自定义注解

    在需要扫描的微服务项目中新建一个自定义注解,因为这里定义的注解用于鉴权,因此给该注解取名叫做MyAccessControlAnnotation

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    //@Retention--用于指定注解保留范围:SOURCE源码 --> CLASS字节码 --> RUNTIME运行
    @Retention(RetentionPolicy.RUNTIME)
    //@Target--用于标记这个注解应该是哪种 Java 成员,METHOD用于放方法头上
    @Target({ElementType.METHOD})
    public @interface MyAccessControlAnnotation {
        //自定义成员变量:role,默认值是""
        String role() default "";
        //自定义成员变量:permission,默认值是""
        String permission() default "";
    }
    

    2.使用注解

    在需要进行权限控制的方法头上添加上面定义好的注解。
    如图所示,这里在user-service中的查询所有用户方法上加上了权限控制
    role = “ROLE_ADMIN”
    即,访问这个方法需要角色ROLE_ADMIN
    在这里插入图片描述
    在根据id查询用户信息这个方法上加上了权限控制
    role = “ROLE_QUERY”
    即,访问这个方法需要角色ROLE_QUERY
    在这里插入图片描述
    全部代码如下:

    package com.example.controller;
    
    
    import com.example.anno.MyAccessControlAnnotation;
    import com.example.converter.UserDtoConverter;
    import com.example.dao.po.UserPo;
    import com.example.http.dto.UserDto;
    import com.example.repository.UserRepository;
    import com.example.service.impl.UserServiceImpl;
    import com.example.util.ResponseResult;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    /**
     * 

    * 用户表 前端控制器 *

    * * @author z * @since 2022-09-15 */
    @RestController @RequestMapping("/user") public class UserController { @Resource private UserServiceImpl userService; @Resource private UserDtoConverter userDtoConverter; @Resource private UserRepository userRepository; /** * 查询所有用户 * @return */ @MyAccessControlAnnotation(role = "ROLE_ADMIN") @GetMapping("queryAll") public ResponseResult<List<UserDto>> queryAllUser(){ List<UserDto> userDtoList = userDtoConverter.toDto(userService.list()); return new ResponseResult<>(200,"ok",userDtoList); } /** * 注册用户 * @param user * @return */ @PostMapping("register") public ResponseResult<Boolean> register(UserPo user){ Boolean result = userService.register(user); return new ResponseResult<Boolean>(200,"ok",result); } /** * 通过id查询用户信息 * @param id * @return */ @MyAccessControlAnnotation(role = "ROLE_QUERY") @GetMapping("getById") public ResponseResult<UserDto> getUserById(@RequestParam("id") Long id){ UserDto userDto = userRepository.getById(id); return new ResponseResult<>(200,"ok",userDto); } /** * 通过角色id查询用户列表 * @param roleId * @return */ @GetMapping("getListByRoleId") public ResponseResult<List<UserDto>> getUserListByRoleId(@RequestParam("roleId") Long roleId){ List<UserDto> userDtoList = userRepository.getListByRoleId(roleId); return new ResponseResult<>(200,"ok",userDtoList); } /** * 通过用户名查询用户信息 * @param account * @return */ @GetMapping("getByAccount") public ResponseResult<UserDto> getUserByAccount(@RequestParam("account") String account){ UserDto userDto = userRepository.getByAccount(account); return new ResponseResult<>(200,"ok",userDto); } /** * 通过id更新用户状态 * @param id * @param statusId * @return */ @PostMapping("updateStatus") public ResponseResult<Boolean> updateUserStatus(@RequestParam("id") Long id, @RequestParam("statusId") Long statusId){ Boolean result = userService.updateStatus(id,statusId); return new ResponseResult<Boolean>(200,"ok",result); } /** * 通过id更新用户角色 * @param id * @param roleId * @return */ @PostMapping("updateRole") public ResponseResult<Boolean> updateUserRole(@RequestParam("id") Long id, @RequestParam("roleId") Long roleId){ Boolean result = userService.updateRole(id,roleId); return new ResponseResult<Boolean>(200,"ok",result); } }

    3.利用反射获取和处理注解相关信息

    定义一个方法handle()。
    扫描被自定义注解修饰的方法。
    将访问方法的路径和注解里设置的权限写入数据库。

    MyAccessControlAnnotationHandle.java

    package com.example.config.handle;
    
    import com.example.anno.MyAccessControlAnnotation;
    import com.example.http.api.AuthServiceClient;
    import com.example.http.dto.ServiceUriAuthorityDto;
    import com.example.util.ResponseResult;
    import org.reflections.Reflections;
    import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    import java.util.Set;
    
    @Component
    public class MyAccessControlAnnotationHandle {
        @Resource
        private AuthServiceClient authServiceClient;
    
        public void handle(){
            //扫描这个包下所有被@RestController注解修饰的类
            Reflections reflections = new Reflections("com.example.controller");
            Set<Class<?>> restController = reflections.getTypesAnnotatedWith(RestController.class);
            //遍历这些类
            restController.forEach(aClass -> {
                //获取类中所有的方法
                Method[] methods = aClass.getDeclaredMethods();
                for (int i = 0; i < methods.length; i++) {
                    //遍历类里的方法,获取被我们自定义注解修饰的方法
                    if(methods[i].isAnnotationPresent(MyAccessControlAnnotation.class)){
    //                    System.out.println(aClass.getSimpleName()+"类中被我自定义注解修饰的方法");
    //                    System.out.println(methods[i].getName());
                        //获取类和方法中的一些信息
                        String path1 = "";
                        String path2 = "";
                        String methodType = "";
                        if(aClass.isAnnotationPresent(RequestMapping.class)){
                            path1=aClass.getAnnotation(RequestMapping.class).value()[0];
                        };
    
                        if(methods[i].isAnnotationPresent(RequestMapping.class)){
                            path2 = methods[i].getAnnotation(RequestMapping.class).value()[0];
                        };
                        if(methods[i].isAnnotationPresent(GetMapping.class)){
                            path2 = methods[i].getAnnotation(GetMapping.class).value()[0];
                            methodType = "GET";
                        };
                        if(methods[i].isAnnotationPresent(PostMapping.class)){
                            path2 = methods[i].getAnnotation(PostMapping.class).value()[0];
                            methodType = "POST";
                        };
                        //获取到自定义注解对象
                        MyAccessControlAnnotation annotation = methods[i].getAnnotation(MyAccessControlAnnotation.class);
                        //获取自定义注解中的成员属性值
                        String role = annotation.role();
                        String permission = annotation.permission();
                        //拼接以上得到的信息,用于写入数据库
                        if(!"".equals(path1)){
                            path1 = path1 +"/";
                        }
                        //拼接得到的访问该方法的路径
                        String uri = "/user-service"+path1+path2;
                        //从注解的成员属性值中获取到访问该方法所需的角色或权限
                        String authority = role+","+permission;
                        if(authority.endsWith(",")){
                            authority = authority.substring(0, authority.length()-1);
                        }
                        //把以上信息写入数据库
                        ServiceUriAuthorityDto serviceUriAuthorityDto = new ServiceUriAuthorityDto(methodType,uri,authority);
    //                    System.out.println(serviceUriAuthorityDto);
    					//如果路径已存在,删除数据库里这些路径对应的数据
                        authServiceClient.deleteUriByUri(uri);
                        //写入路径对应的数据到数据库
                        ResponseResult<Boolean> booleanResponseResult = authServiceClient.addUri(serviceUriAuthorityDto);
                        if (booleanResponseResult.getData()){
                            System.out.println(methods[i]+"方法的访问权限已初始化。");
                        }else {
                            System.out.println(methods[i]+"方法访问权限初始化失败!");
                        }
                    }
                }
                 }) ;
        }
    }
    
    

    为了概念上更加便于理解,写入数据库的数据长这样子,它来自于这里:
    在这里插入图片描述
    在这里插入图片描述

    4.设置每次微服务启动时运行方法

    配置一个监听事件,用于在每次微服务启动时,自动运行上面定义好的方法。
    这样,每次微服务启动,系统都会自动扫描所有的controller类下面被我们自定义的注解@MyAccessControlAnnotation修饰的方法,并将访问该方法的路径和所需权限写入数据库。

    package com.example.config;
    
    import com.example.anno.MyAccessControlAnnotation;
    import com.example.config.handle.MyAccessControlAnnotationHandle;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.context.event.ApplicationStartedEvent;
    import org.springframework.context.ApplicationListener;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    
    @Slf4j
    @Component
    public class SimpleApplicationStartedEventListener implements ApplicationListener<ApplicationStartedEvent> {
    
        @Resource
        private StringRedisTemplate stringRedisTemplate;
        @Resource
        private MyAccessControlAnnotationHandle myAccessControlAnnotationHandle;
    
        @Override
        public void onApplicationEvent(ApplicationStartedEvent event) {
            //在每次微服务启动的时候调用这个方法
            myAccessControlAnnotationHandle.handle();
            log.info("已写入访问路径和权限到数据库");
        }
    }
    
    
  • 相关阅读:
    地图手绘图生成切片位置进行微调,使图片更精准地贴在地图上
    金仓数据库KingbaseES客户端编程开发框架-Hibernate(5. Hibernate注意事项)
    FTP文件的上传与下载
    ERC-3525 通过倒计时 SFT 是什么?有什么用?
    零信任安全:SPIFFE 和 SPIRE 通用身份验证的标准和实现
    自动化抢票 12306
    Linux的基础设置
    从0开始学Java:运算符(Operator)与标点符号(Separators)
    Vue项目文件导入、导出
    2022/11/20[指针] 通过函数,利用指针将数组a中前n个元素按相反顺序存放
  • 原文地址:https://blog.csdn.net/weixin_56039103/article/details/126957545