JAVA - through custom annotations, each time the program starts, the annotated method is automatically scanned, its path and the permissions required to access the path are obtained and written to the database

JAVA - through custom annotations, each time the program starts, the annotated method is automatically scanned, its path and the permissions required to access the path are obtained and written to the database

1. Demand background

In the spring cloud microservice project, in order to achieve authentication, there are the following two solutions:

  • Option 1: The relationship with the gateway is not particularly great. auth-service performs login authentication; each microservice performs authentication by itself.
    At most, the gateway just does some jwt-token legitimacy verification (judging whether it is forged).

  • Option 2: It has a lot to do with the gateway. auth-service performs login authentication; gate gateway performs authentication; each micro-service does not need to perform authentication (no need to "lead" spring-security).

Option 2 is selected here:
the advantages of Option 2 are:

  • There are fewer "participants" in the entire authentication and authentication work, which is more conducive to bug location and coding;
  • The calls between microservices are simplified, and there is no need to pass jwt-tokens between each other.

But the "price" of option two:

In the past, what permission was required to access a certain path was configured in the configuration class of spring-security, or marked with annotations. Now you need to save the path-access relationship in the database

Then we are required to insert a piece of data in the path-access right table every time we write a controller method to indicate the permission required to access the path of the method

Therefore, it is decided to use the custom annotation method, and add annotations on the controller method that needs to be controlled by permissions:
use the value of the annotation to indicate the permissions required to access the path.
These annotations are automatically obtained each time the microservice starts, and the information is written into the path-access right table.

2. Implementation steps

1. Custom annotations

Create a new custom annotation in the microservice project that needs to be scanned, because the annotation defined here is used for authentication, so name the annotation 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. Use annotations

Add the above-defined annotations to the method headers that require permission control.
As shown in the figure, the access control
role = "ROLE_ADMIN" is added to the method of querying all users in user-service, that is, the role ROLE_ADMIN is required to access this method. The access control role = "ROLE_QUERY" is added to the
method of querying user information based on id, that is, the entire code of the role ROLE_QUERY is required to access this method :
insert image description here



insert image description here

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;

/**
 * <p>
 * 用户表 前端控制器
 * </p>
 *
 * @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. Use reflection to obtain and process annotation-related information

Define a method handle().
Scan methods decorated with custom annotations.
Write the path of the access method and the permissions set in the annotation to the database.

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]+"方法访问权限初始化失败!");
                    }
                }
            }
             }) ;
    }
}

To make it easier to understand conceptually, the data written to the database looks like this, and it comes from here:
insert image description here
insert image description here

4. Set the running method every time the microservice starts

Configure a listening event to automatically run the method defined above every time the microservice starts.
In this way, every time the microservice starts, the system will automatically scan all the methods modified by our custom annotation @MyAccessControlAnnotation under the controller class, and write the path and required permissions to access the method into the database.

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("已写入访问路径和权限到数据库");
    }
}

Guess you like

Origin blog.csdn.net/weixin_56039103/article/details/126957545