spring AOP + 自定義註解實現權限控制小例子

今天看了一下黑馬程序員的視頻,上面講到一個使用spring AOP + 自定義註解的方式來實現權限控制的一個小例子,個人覺得還是可以借鑑,整理出來與大家分享。

需求:service層有一些方法,這些方法需要不同的權限才能訪問。

實現方案:自定義一個PrivilegeInfo的註解,使用這個註解爲service層中的方法進行權限配置,在aop中根據PrivilegeInfo註解的值,判斷用戶是否擁有訪問目標方法的權限,有則訪問目標方法,沒有則給出提示。

關鍵技術:自定義註解及註解解析,spring aop

最終實現後的目錄結構:

這裏寫圖片描述

具體步驟:
下面我們來具體實現這個需求。
首先來實現這個自定義註解,爲了簡單起見,我們演示的這個註解,只是給了一個權限名的屬性。

package privilege.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 權限註解
 * @author Minhellic
 *
 */
@Target(ElementType.METHOD)//這個註解是應用在方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface PrivilegeInfo {
    /**
     * 權限的名稱
     * @return
     */
    String value() default "";
}

爲這個自定義的註解,寫一個解析器,主要是用於返回目標方法上的註解PrivilegeInfo設置的value值

package privilege.annotation;

import java.lang.reflect.Method;

/**
 * 權限註解解析器
 * 這個解析器的主要功能,是解析目標方法上如果有PrivilegeInfo註解,那麼解析出這個註解中的value值(權限的值)
 * @author Minhellic
 *
 */
public class PrivilegeAnnotationParse {
    /**
     * 解析註解
     * @param targetClass 目標類的class形式
     * @param methodName 在客戶端調用哪個方法,methodName就代表哪個方法 
     * @return
     * @throws Exception 
     */
    public static String parse(Class targetClass, String methodName) throws Exception {
        String methodAccess = "";
        /*
         * 爲簡單起見,這裏考慮該方法沒有參數
         */
        Method method = targetClass.getMethod(methodName);
        //判斷方法上是否有Privilege註解
        if (method.isAnnotationPresent(PrivilegeInfo.class)) {
            //得到方法上的註解
            PrivilegeInfo privilegeInfo = method.getAnnotation(PrivilegeInfo.class);
            methodAccess = privilegeInfo.value();
        }
        return methodAccess;
    }
}

自定義的註解和解析器有了,我們把對應的Service層寫出來,並在需要使用這個註解配置權限的方法上,添加上這個註解。我們知道Service層是由接口和實現類組成,這是規範,雖然對我們這次的演示沒有什麼作用,但我們還是遵守一下:

接口源碼:

package privilege.service;

/**
 * 用戶業務接口
 * @author Minhellic
 *
 */
public interface FirmService {
    /**
     * 在需要權限的目標方法上,使用PrivilegeInfo註解,配置權限爲save
     */
    public void save();
    /**
     * 在需要權限的目標方法上,使用PrivilegeInfo註解,配置權限爲update
     */
    public void update();
    /**
     * 不需要權限的目標方法上,則不添加PrivilegeInfo註解
     * 在切面中,默認用戶擁有權限
     */
    public void get();
}

實現類源碼:

package privilege.service.impl;

import privilege.annotation.PrivilegeInfo;
import privilege.service.FirmService;

/**
 * 用戶業務實現
 * @author Minhellic
 *
 */
public class FirmServiceImpl implements FirmService {

    /**
     * 在需要權限的目標方法上,使用PrivilegeInfo註解,配置權限
     */
    @Override
    @PrivilegeInfo("save")
    public void save() {
        System.out.println("FirmServiceImpl.save()");

    }

    /**
     * 在需要權限的目標方法上,使用PrivilegeInfo註解,配置權限
     */
    @Override
    @PrivilegeInfo("update")
    public void update() {
        System.out.println("FirmServiceImpl.update()");

    }

    /**
     * 不需要權限的目標方法上,則不添加PrivilegeInfo註解
     * 在切面中,默認用戶擁有權限
     */
    @Override
    public void get() {
        System.out.println("FirmServiceImpl.get()");

    }
}

爲了更好地管理權限,我們專門建立一個權限類,當然,爲了簡單,這裏還是隻封裝了權限的名稱

package privilege.userprivilege;

/**
 * 封裝用戶權限
 * 爲簡單,只封裝了權限的名稱
 * @author Minhellic
 *
 */
public class FirmPrivilege {
    /**
     * 用戶權限的名稱
     */
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public FirmPrivilege(String value) {
        this.value = value;
    }

    public FirmPrivilege() {
    }

}

現在Service層和註解都已經有了,就需要寫切面的代碼了。
在切面中,我們使用環繞通知,在調用目標方法之前,我們先用目標方法上的PrivilegeInfo註解配置的權限,與用戶擁有的權限進行匹配,如果匹配成功,則認爲用戶擁有這個目標方法的權限,則調用目標方法,否則,給出提示信息,不調用目標方法。
這裏之所以不使用前置通知的方式來匹配權限,就是因爲前置通知雖然也可以檢查,但是無論檢查是否通過,目標方法都會被調用,起不到根據權限來控制目標方法調用的目的。

package privilege.aspect;

import java.util.List;

import org.aspectj.lang.ProceedingJoinPoint;

import privilege.annotation.PrivilegeAnnotationParse;
import privilege.userprivilege.FirmPrivilege;

/**
 * 權限檢查切面
 * 根據用戶原有的權限,與目標方法的權限配置進行匹配,
 * 如果目標方法需要的權限在用戶原有的權限以內,則調用目標方法
 * 如果不匹配,則不調用目標方法
 * @author Minhellic
 *
 */
public class PrivilegeAspect {
    /**
     * 用戶本身的權限
     */
    private List<FirmPrivilege> privileges;

    public List<FirmPrivilege> getPrivileges() {
        return privileges;
    }

    public void setPrivileges(List<FirmPrivilege> privileges) {
        this.privileges = privileges;
    }

    /**
     * aop中的環繞通知
     * 在這個方法中檢查用戶的權限和目標方法的需要的權限是否匹配
     * 如果匹配則調用目標方法,不匹配則不調用
     * @param joinPoint 連接點
     * @throws Throwable
     */
    public void isAccessMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        /**
         * 1.獲取訪問目標方法應該具備的權限
         *  爲解析目標方法的PrivilegeInfo註解,根據我們定義的解析器,需要得到:目標類的class形式 方法的名稱
         */
        Class targetClass = joinPoint.getTarget().getClass();
        String methodName = joinPoint.getSignature().getName();
        //得到該方法的訪問權限
        String methodAccess = PrivilegeAnnotationParse.parse(targetClass, methodName);
        /*
         * 2.遍歷用戶的權限,看是否擁有目標方法對應的權限
         */
        boolean isAccessed = false;
        for (FirmPrivilege firmPrivilege : privileges) {
            /*
             * 如果目標方法沒有使用PrivilegeInfo註解,則解析出來的權限字符串就爲空字符串
             * 則默認用戶擁有這個權限
             */
            if ("".equals(methodAccess)) {
                isAccessed = true;
                break;
            }
            /*
             * 用戶原有權限列表中有的權限與目標方法上PrivilegeInfo註解配置的權限進行匹配
             */
            if (firmPrivilege.getValue() != null && 
                    firmPrivilege.getValue().equalsIgnoreCase(methodAccess)) {
                isAccessed = true;
                break;
            }
        }
        /*
         * 3.如果用戶擁有權限,則調用目標方法 ,如果沒有,則不調用目標方法,只給出提示
         */
        if (isAccessed) {
            joinPoint.proceed();//調用目標方法
        } else {
            System.out.println("你沒有權限");
        }
    }
}

最後,配置好spring的配置文件,要使用spring aop,配置文件中,必須包含有aop的命名空間,並引入相應的xsd

這裏寫圖片描述

配置文件的源碼爲:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/aop 
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="firmService" class="privilege.service.impl.FirmServiceImpl"></bean>
    <bean id="privilegeAspect" class="privilege.aspect.PrivilegeAspect"></bean>

    <!-- 配置切面 -->
    <aop:config>
        <!-- 
            切入點表達式,確認目標類 
            privilege.service.impl包中的所有類中的所有方法
        -->
        <aop:pointcut expression="execution(* privilege.service.impl.*.*(..))" id="perform"/>
        <!-- ref指向的對象就是切面 -->
        <aop:aspect ref="privilegeAspect">
            <!-- 環繞通知 -->
            <aop:around method="isAccessMethod" pointcut-ref="perform"/>
        </aop:aspect>
    </aop:config>

</beans>

所有的工作都已做好,我們來測試一下:

package privilege.test;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import privilege.aspect.PrivilegeAspect;
import privilege.service.FirmService;
import privilege.userprivilege.FirmPrivilege;

/**
 * aop+註解權限控制測試類
 * 
 * @author Minhellic
 *
 */
public class PrivilegeTest {
    /**
     * 客戶端直接調用這個Service的方法,而不需要關心權限問題
     */
    private FirmService firmService;

    /**
     * 在初始化方法中,初始化firmService
     * 同時爲用戶賦上原始權限,這個在項目中,會使用別的方式實現,這裏只是模擬,就不搞那麼複雜了
     */
    @Before
    public void init() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        firmService = (FirmService) context.getBean("firmService");

        /*
         * 給用戶添加默認權限
         */
        PrivilegeAspect privilegeAspect = (PrivilegeAspect) context.getBean("privilegeAspect");
        List<FirmPrivilege> privileges = new ArrayList<FirmPrivilege>();
        //privileges.add(new FirmPrivilege("save"));
        privileges.add(new FirmPrivilege("update"));
        privilegeAspect.setPrivileges(privileges);
    }

    /**
     * 客戶端直接調用Service中的方法,而不需要關心權限問題,會有切面去做
     */
    @Test
    public void test() {
        firmService.save();
        firmService.update();
        firmService.get();
    }
}

運行test方法,根據控制檯的輸出結果,就可以看到權限控制是起到了作用,因爲用戶初始權限中的save權限被註釋掉,則用戶不會擁有save權限,調用save方法時,提示沒有權限。
從上面的測試方法可以看出,使用了aop之後,我們只需要關心主要業務,而不需要再分心去管理權限問題。

這裏寫圖片描述

這篇博客,雖是我本人整理,但所有的思路及實現方式,都來源於黑馬程序員的視頻,算是半原創吧。如寫得很爛,歡迎大神們噴,但請不要辱罵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章