Spring Boot 經過AOP和自定義註解實現權限控制

相逢即是 ,路過點個 ^.^java


源碼:https://github.com/yulc-coding/java-note/tree/master/aopgit

思路

  • 自定義權限註解
  • 在須要驗證的接口上加上註解,並設置具體權限值
  • 數據庫權限表中加入對應接口須要的權限
  • 用戶登陸時,獲取當前用戶的全部權限列表放入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、參數、接口、耗時等)github

@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 :
         * 一、token是否存在
         * 二、token格式是否正確
         * 三、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());
    }

}

複製代碼

求關注

相關文章
相關標籤/搜索