基於shiro的自定義註解的擴展

基於shiro的自定義註解的擴展

根據個人上一篇文章,權限設計的雜談中,涉及到了有關於先後端分離中,頁面和api接口斷開表與表層面的關聯,另闢蹊徑從其餘角度找到方式進行關聯。這裏咱們主要採起了shiro的自定義註解的方案。本篇文章主要解決如下的問題。java

  1. 如何經過邏輯進行頁面與api接口的關聯。
  2. shiro的自身註解的用法。
  3. 如何編寫自定義註解。

如何經過邏輯進行頁面與api接口的關聯

在表與表的結構關係中,頁面和接口表最終都是與權限表進行的關聯(詳情請查看個人上一篇文章《權限設計的雜談》)。
權限實體圖
咱們如今但願用另外一種方案去替代他,實現一個低成本同時兼顧必定程度的權限控制。這裏咱們引入兩個概念。業務模塊操做類型數據庫

  • 業務模塊apache

    • 概念:將系統中的業務模塊抽象成一種數據,咱們能夠用字符串的形式去表示,例如:角色管理對應是role-manage、用戶管理對應是user-manage等等。咱們將系統中所存在的業務模塊經過「最小特權原則」進行劃分,最終造成一批可分配的數據。
    • 使用原則:api接口和頁面以及功能從本質上來講,都和業務模塊有邏輯關係,因而,咱們能夠對api接口與頁面(以及功能點)進行邏輯匹配,來判斷頁面與接口的關係。
  • 操做類型segmentfault

    • 概念:將系統中的全部的操做類型抽象成一種數據,咱們也能夠用字符串的形式去表示,例如:新增對應的是add、分配對應的是allot等等。咱們將系統中全部的操做類型根據業務模塊經過「數據許可證」進行劃分,最終造成一批可分配的數據。
    • 使用原則:頁面是展現,功能點是動做,而接口是最終動做的資源提供,經過「業務模塊」肯定了調取的資源,經過「操做類型」肯定了資源的使用方式。經過二者能夠大體無誤的判斷頁面的功能點觸發的接口是否在鑑權以內。

如今提出了這兩個概念,他們最終的實際的使用方式是什麼,咱們先從如下幾個角度去思考一下。後端

  1. 數據庫中的頁面表或的api接口表中的數據就是真實有效嗎?
  2. 頁面或接口的實際使用,是以功能存在爲前提,仍是以數據庫表中的數據存在爲前提。
  3. 權限結構中,「控制對象」的存儲只有數據庫這一種途徑嗎?

咱們從結論出發來看這幾個問題,首先「控制對象」的存儲除了在數據庫中也能夠代碼中,也能夠在配置文件中,並不必定非得在數據庫;那麼接着回答第二個問題,當數據庫存在的接口信息,而服務端並無開發這個接口的時候,數據庫的信自己就有問題,亦或者,數據庫裏新增的接口一定是服務端上已經部署的接口才能生效;接着就是第一個問題,那麼數據庫中關於「控制對象」的表中的數據並不必定是真實有效的。因此咱們能夠得出如下的解決方案api

  1. 咱們能夠在接口上用註解的形式補充「業務模塊」和「操做類型」的數據信息,這兩類信息均可以存於常量類中,
  2. 在數據庫添加建立頁面表結構和頁面功能表結構的時候,添加「業務模塊」和「操做類型」字段。
  3. 能夠將「業務模塊」和「操做類型」的信息存於數據庫的字典表中。
  4. 模塊的新增或操做的新增,一定帶來了接口的新增,那麼就會帶來一次系統部署活動,這個運維成本是沒法減小的,並不能經過表結構來減小。

業務模塊與頁面和接口的關係

可是這種方案僅適用於非強控制接口型的項目,在強控制型的接口項目仍然要將頁面與接口進行綁定,雖然這會帶來巨大的運維成本。另外也能夠經過接口路由規則進行劃分,例如:/api/page/xxxx/(僅對頁面使用),/api/mobile/xxxxx(僅對移動端使用)將僅供頁面使用的接口進行分類,這類接口僅作認證不作受權,也能夠達到目的。

shiro的自身註解的用法

經過一個理論上的思路承認以後,剩下的則是付諸技術上的實踐,咱們這邊採用的是Apache Shiro的安全框架,在Spring Boot的環境下應用。簡要說明如下幾個shiro的註解。安全

註解名 做用
@RequiresAuthentication 做用於的類、方法、實例上。調用時,當前的subject是必須通過了認證的。
@RequiresGuest 做用於的類、方法、實例上。調用時,subject能夠是guest狀態。
@RequiresPermissions 做用於的類、方法、實例上。調用時,須要判斷suject中是否包含當前接口中的Permission(權限信息)。
@RequiresRoles 做用於的類、方法、實例上。調用時,須要判斷subject中是否包含當前接口中的Role(角色信息)。
@RequiresUser 做用於的類、方法、實例上。調用時,須要判斷subject中是否當前應用中的用戶。
/**
     * 1.當前接口須要通過"認證"過程
     * @return
     */
    @RequestMapping(value = "/info",method = RequestMethod.GET)
    @RequiresAuthentication
    public String test(){
        return "恭喜你,拿到了參數信息";
    }
    
    /**
     * 2.1.當前接口須要通過權限校驗(需包含 角色的查詢 或 菜單的查詢)
     * @return
     */
    @RequestMapping(value = "/info",method = RequestMethod.GET)
    @RequiresPermissions(value={"role:search","menu":"search"},logical=Logical.OR)
    public String test(){
        return "恭喜你,拿到了參數信息";
    }
    
    /**
     * 2.2.當前接口須要通過權限校驗(需包含 角色的查詢 與 菜單的查詢)
     * @return
     */
    @RequestMapping(value = "/info",method = RequestMethod.GET)
    @RequiresPermissions(value={"role:search","menu":"search"},logical=Logical.OR)
    public String test(){
        return "恭喜你,拿到了參數信息";
    }
    
    /**
     * 3.1.當前接口須要通過角色校驗(需包含admin的角色)
     * @return
     */
    @RequestMapping(value = "/info",method = RequestMethod.GET)
    @RequiresRoles(value={"admin"})
    public String test(){
        return "恭喜你,拿到了參數信息";
    }
    
    /**
     * 3.2.當前接口須要通過角色與權限的校驗(需包含admin的角色,以及角色的查詢 或 菜單的查詢)
     * @return
     */
    @RequestMapping(value = "/info",method = RequestMethod.GET)
    @RequiresRoles(value={"admin"})
    @RequiresPermissions(value={"role:search","menu":"search"},logical=Logical.OR)
    public String test(){
        return "恭喜你,拿到了參數信息";
    }

在咱們的實際使用過程當中,實際上只須要使用@RequiresPermissions和@RequiresAuthentication就能夠了這一個註解就能夠了,在上一小節的結尾,咱們採起了業務模塊與操做的結合方案來解耦頁面和api接口的關係,和apache Shiro的這種方式正好一致。可是@RequiresRoles這個咱們儘量不採用,由於角色的組合形式太多,角色名沒有辦法在接口中具象惟一化(很難指定接口歸某個角色調用,可是必定能知道接口歸屬於某些業務模塊的某些操做。)app

如今咱們來回顧一下整個運轉的流程。框架

shiro權限的驗證流程

如何編寫自定義註解

可是僅僅是擁有shiro中的這5個註解確定是不夠使用的。在實際的使用過程當中,根據需求,咱們會在權限認證中加入咱們本身特有的業務邏輯的,咱們爲了便捷則能夠採用自定義註解的方式進行使用。這種方法不只僅適用於Apache Shiro,不少其餘的框架如:Hibernate Validator、SpringMVC、甚至咱們能夠寫一套校驗體系,在aop中去驗證權限,這都是沒問題的。因此自定義註解的做用很廣。可是在這裏,我僅僅基於shiro的來實現適用於它的自定義註解。前後端分離

  • 定義註解類
/**
 * 用於認證的接口的註解,組合形式默認是「或」的關係
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
    /**
     * 業務模塊
     * @return
     */
    String[] module();
    /**
     * 操做類型
     */
    String[] action();

}
  • 定義註解的處理類
/**
 * Auth註解的操做類
 */
public class AuthHandler extends AuthorizingAnnotationHandler {


    public AuthHandler() {
        //寫入註解
        super(Auth.class);
    }

    @Override
    public void assertAuthorized(Annotation a) throws AuthorizationException {
        if (a instanceof Auth) {
            Auth annotation = (Auth) a;
            String[] module = annotation.module();
            String[] action = annotation.action();
            //1.獲取當前主題
            Subject subject = this.getSubject();
            //2.驗證是否包含當前接口的權限有一個經過則經過
            boolean hasAtLeastOnePermission = false;
            for(String m:module){
                for(String ac:action){
                    //使用hutool的字符串工具類
                    String permission = StrFormatter.format("{}:{}",m,ac);
                    if(subject.isPermitted(permission)){
                        hasAtLeastOnePermission=true;
                        break;
                    }
                }
            }
            if(!hasAtLeastOnePermission){
                throw new AuthorizationException("沒有訪問此接口的權限");
            }

        }
    }
}
  • 定義shiro攔截處理類
/**
 * 攔截器
 */
public class AuthMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {


    public AuthMethodInterceptor() {
        super(new AuthHandler());
    }

    public AuthMethodInterceptor(AnnotationResolver resolver) {
        super(new AuthHandler(), resolver);
    }

    @Override
    public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
        // 驗證權限
        try {
            ((AuthHandler) this.getHandler()).assertAuthorized(getAnnotation(mi));
        } catch (AuthorizationException ae) {
            if (ae.getCause() == null) {
                ae.initCause(new AuthorizationException("當前的方法沒有經過鑑權: " + mi.getMethod()));
            }
            throw ae;
        }
    }
}
  • 定義shiro的aop切面類
/**
 * shiro的aop切面
 */
public class AuthAopInterceptor extends AopAllianceAnnotationsAuthorizingMethodInterceptor {
    public AuthAopInterceptor() {
        super();
        // 添加自定義的註解攔截器
        this.methodInterceptors.add(new AuthMethodInterceptor(new SpringAnnotationResolver()));
    }
}
  • 定義shiro的自定義註解啓動類
/**
 * 啓動自定義註解
 */
public class ShiroAdvisor extends AuthorizationAttributeSourceAdvisor {

    public ShiroAdvisor() {
        // 這裏能夠添加多個
        setAdvice(new AuthAopInterceptor());
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public boolean matches(Method method, Class targetClass) {
        Method m = method;
        if (targetClass != null) {
            try {
                m = targetClass.getMethod(m.getName(), m.getParameterTypes());
                return this.isFrameAnnotation(m);
            } catch (NoSuchMethodException ignored) {

            }
        }
        return super.matches(method, targetClass);
    }

    private boolean isFrameAnnotation(Method method) {
        return null != AnnotationUtils.findAnnotation(method, Auth.class);
    }
}
整體的思路順序:定義 註解類(定義業務可以使用的變量)->定義 註解處理類(經過註解中的變量作業務邏輯處理)->定義 註解的攔截器->定義 aop的切面類->最後定義shiro的自定義註解啓用類。其餘的自定義的註解的編寫思路和這個也是相似的。
相關文章
相關標籤/搜索