1. 程式人生 > 程式設計 >Spring Boot 通過AOP和自定義註解實現許可權控制

Spring Boot 通過AOP和自定義註解實現許可權控制

相逢便是 ,路過點個 ^.^


原始碼:https://github.com/yulc-coding/java-note/tree/master/aop

思路

  • 自定義許可權註解
  • 在需要驗證的介面上加上註解,並設定具體許可權值
  • 資料庫許可權表中加入對應介面需要的許可權
  • 使用者登入時,獲取當前使用者的所有許可權列表放入Redis快取中
  • 定義AOP,將切入點設定為自定義的許可權
  • AOP中獲取介面註解的許可權值,和Redis中的資料校驗使用者是否存在該許可權,如果Redis中沒有,則從資料庫獲取使用者許可權列表,再校驗

pom檔案 引入AOP

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- AOP 切面-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
複製程式碼

自定義註解 VisitPermission

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface VisitPermission {
    /**
     * 用於配置具體介面的許可權值
     * 在資料庫中新增對應的記錄
     * 使用者登入時,將使用者所有的許可權列表放入redis中
     * 使用者訪問介面時,將對應介面的值和redis中的匹配看是否有訪問許可權
     * 使用者退出登入時,清空redis中對應的許可權快取
     */
    String value() default ""
; } 複製程式碼

需要設定許可權的介面上加入註解 @VisitPermission(value)

@RestController
@RequestMapping("/permission")
public class PermissionController {
    /**
     * 配置許可權註解 @VisitPermission("permission-test")
     * 只用擁有該許可權的使用者才能訪問,否則提示非法操作
     */
    @VisitPermission("permission-test")
    @GetMapping("/test")
    public String test
() { System.out.println("================== step 3: doing =================="); return "success"; } } 複製程式碼

定義許可權AOP

設定切入點為@annotation(VisitPermission)
獲取請求中的token,校驗是否token是否過期或合法
獲取註解中的許可權值,校驗當前使用者是否有訪問許可權
MongoDB 記錄訪問日誌(IP、引數、介面、耗時等)

@Aspect
@Component
public class PermissionAspect {

    /**
     * 切入點
     * 切入點為包路徑下的:execution(public * org.ylc.note.aop.controller..*(..)):
     * org.ylc.note.aop.Controller包下任意類任意返回值的 public 的方法
     * <p>
     * 切入點為註解的: @annotation(VisitPermission)
     * 存在 VisitPermission 註解的方法
     */
    @Pointcut("@annotation(org.ylc.note.aop.annotation.VisitPermission)")
    private void permission() {

    }

    /**
     * 目標方法呼叫之前執行
     */
    @Before("permission()")
    public void doBefore() {
        System.out.println("================== step 2: before ==================");
    }

    /**
     * 目標方法呼叫之後執行
     */
    @After("permission()")
    public void doAfter() {
        System.out.println("================== step 4: after ==================");
    }

    /**
     * 環繞
     * 會將目標方法封裝起來
     * 具體驗證業務資料
     */
    @Around("permission()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("================== step 1: around ==================");
        long startTime = System.currentTimeMillis();
        /*
         * 獲取當前http請求中的token
         * 解析token :
         * 1、token是否存在
         * 2、token格式是否正確
         * 3、token是否已過期(解析資訊或者redis中是否存在)
         * */
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)) {
            throw new RuntimeException("非法請求,無效token");
        }
        // 校驗token的業務邏輯
        // ...

        /*
         * 獲取註解的值,並進行許可權驗證:
         * redis 中是否存在對應的許可權
         * redis 中沒有則從資料庫中獲取許可權
         * 資料空中沒有,拋異常,非法請求,沒有許可權
         * */
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        VisitPermission visitPermission = method.getAnnotation(VisitPermission.class);
        String value = visitPermission.value();
        // 校驗許可權的業務邏輯
        // List<Object> permissions = redis.get(permission)
        // db.getPermission
        // permissions.contains(value)
        // ...
        System.out.println(value);
        
        // 執行具體方法
        Object result = proceedingJoinPoint.proceed();

        long endTime = System.currentTimeMillis();

        /*
         * 記錄相關執行結果
         * 可以存入MongoDB 後期做資料分析
         * */
        // 列印請求 url
        System.out.println("URL            : " + request.getRequestURL().toString());
        // 列印 Http method
        System.out.println("HTTP Method    : " + request.getMethod());
        // 列印呼叫 controller 的全路徑以及執行方法
        System.out.println("controller     : " + proceedingJoinPoint.getSignature().getDeclaringTypeName());
        // 呼叫方法
        System.out.println("Method         : " + proceedingJoinPoint.getSignature().getName());
        // 執行耗時
        System.out.println("cost-time      : " + (endTime - startTime) + " ms");

        return result;
    }
}
複製程式碼

單元測試

package org.ylc.note.aop;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.ylc.note.aop.controller.PermissionController;

@SpringBootTest
class AopApplicationTests {

    @Autowired
    private PermissionController permissionController;

    private MockMvc mvc;

    @BeforeEach
    void setupMockMvc() {
        mvc = MockMvcBuilders.standaloneSetup(permissionController).build();
    }

    @Test
    void apiTest() throws Exception {
        MvcResult result = mvc.perform(MockMvcRequestBuilders.get("/permission/test")
                .accept(MediaType.APPLICATION_JSON)
                .header("token","9527"))
                .andReturn();
        System.out.println("api test result : " + result.getResponse().getContentAsString());
    }

}

複製程式碼