關於Abp Vnext 許可權授權的問題

2023-06-16 18:01:17

一.問題

最近收到一位朋友的求助,說他專案上的許可權授權出現了問題,現象是在基礎服務授權角色:RC 許可權:X.Default,在基礎服務使用RC角色的使用者登入能存取到許可權X.Default資源,而在X服務存取不到。重啟X服務後就可以存取。

專案框架:ABP Vnext 6.0版本

資料庫:共用一個

微服務架構如下:

 
 

請求/api/abp/application-configuration介面

基礎服務

"auth": {
    "Policies": {
      "X.Default": true
    }
    "grantedPolicies": {
     "X.Default": true
    }
  },

 X服務

"auth": {
    "Policies": {
      "X.Default": true
    }
    "grantedPolicies": {
    }
  },

 

二.分析原因

1.X服務許可權資源已載入,沒有獲取到x.Default的授權

2.基礎服務許可權資源已載入並且獲取到x.Default的授權

3.共用庫AbpPermissionGrants表中存在記錄:ProviderKey:R  ProviderName:RX  Name: x.Default。 說明該角色已經授權了X.Default

4.重啟X服務後再次請求獲取到X.Default的授權

由上可推測X服務獲取不到X.Default授權的原因大概是因為快取。

怎麼驗證猜測,把紀錄檔等級調為Debug,再次請求檢視紀錄檔

2023-06-15 11:14:53.087 +08:00 [DBG] PermissionStore.GetCacheItemAsync: pn:RX,pk:R,n:X.Default......
2023-06-15 11:14:53.088 +08:00 [DBG] Found in the cache: pn:RX,pk:R,n:X.Default......

問題確定。

 

三.問題本質

要了解問題本質我們先來簡單梳理一遍許可權授權驗證流程

不管是走中介軟體還是攔截器,驗證授權最終都是呼叫了AbpAuthrizaionService.AuthorzieAsync()方法,結合紀錄檔,我們來看看PermissionStore.IsGrantedAsync()方法

public virtual async Task<bool> IsGrantedAsync(string name, string providerName, string providerKey)
    {
        return (await GetCacheItemAsync(name, providerName, providerKey)).IsGranted;
    }
  
  protected virtual async Task<PermissionGrantCacheItem> GetCacheItemAsync(
        string name, // X.Default
        string providerName,  // RX
        string providerKey)   //R
    {
        var cacheKey = CalculateCacheKey(name, providerName, providerKey); //計算快取key=pn:RX,pk:R,n:X.Default

        Logger.LogDebug($"PermissionStore.GetCacheItemAsync: {cacheKey}");

        var cacheItem = await Cache.GetAsync(cacheKey); //獲取快取

        if (cacheItem != null)
        {
            Logger.LogDebug($"Found in the cache: {cacheKey}");
            return cacheItem;   //存在則返回
        }

        Logger.LogDebug($"Not found in the cache: {cacheKey}");

        cacheItem = new PermissionGrantCacheItem(false);

        await SetCacheItemsAsync(providerName, providerKey, name, cacheItem); //不存在快取則查資料庫後將結果快取

        return cacheItem;
    }
 protected virtual async Task SetCacheItemsAsync(
        string providerName,
        string providerKey,
        string currentName,
        PermissionGrantCacheItem currentCacheItem)
    {
        var permissions = PermissionDefinitionManager.GetPermissions(); //獲取該服務載入的許可權資源

        Logger.LogDebug($"Getting all granted permissions from the repository for this provider name,key: {providerName},{providerKey}");

        var grantedPermissionsHashSet = new HashSet<string>(
            (await PermissionGrantRepository.GetListAsync(providerName, providerKey)).Select(p => p.Name)  //從資料庫查詢已授權的許可權資源
        );   

        Logger.LogDebug($"Setting the cache items. Count: {permissions.Count}");

        var cacheItems = new List<KeyValuePair<string, PermissionGrantCacheItem>>(); //許可權授權結果快取集合

        foreach (var permission in permissions)
        {
            var isGranted = grantedPermissionsHashSet.Contains(permission.Name); //存在授權列表中則已授權,否則未授權

            cacheItems.Add(new KeyValuePair<string, PermissionGrantCacheItem>(   //把結果加進集合
                CalculateCacheKey(permission.Name, providerName, providerKey),
                new PermissionGrantCacheItem(isGranted))
            );

            if (permission.Name == currentName)
            {
                currentCacheItem.IsGranted = isGranted;
            }
        }

        await Cache.SetManyAsync(cacheItems); //設定快取

        Logger.LogDebug($"Finished setting the cache items. Count: {permissions.Count}");
    }

 第一次請求X服務:load了一遍許可權資源,並把X.Default標記為false快取了起來,後面再授權角色RX 資源X.Default,因為快取的存在再次獲取還是未授權。重啟服務後正常

而基礎服務之所以能實時更新是因為許可權管理模型就在這裡, PermissionGrantCacheItemInvalidator 訂閱了PermissionGrant變更的本地事件會清空快取。

 

四.解決方案

一.授權驗證都走基礎服務

1.參照Volo.Abp.AspNetCore.Mvc.Client nutget

2.新增AbpAspNetCoreMvcClientModule模組

3.新增設定

"RemoteServices": {
    "AbpMvcClient": {
      "BaseUrl": "http://localhost:XXXX", //配基礎服務或閘道器
    }
  }

RemotePermissionChecker會取代PermissionChecker,請求遠端服務進行許可權驗證,並將結果快取起來,有效時間是300s(寫死,todo:未來可配)

  configuration = await Cache.GetOrAddAsync(
            cacheKey,
            async () => await ApplicationConfigurationAppService.GetAsync(),
            () => new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(300) //TODO: Should be configurable.
            }
        );

 

更改後的授權驗證流程 

二 使用Redis快取替換應用快取

 

 

備註:key要一致

 

最後朋友採用了方案二解決了問題,理由是實時。如果你有更好的解決方案請在評論告知我感謝!!!!