SpringCloud 微服務(wù)實現(xiàn)數(shù)據(jù)權(quán)限控制

前章講了如何進(jìn)行用戶權(quán)限驗證《SpringCloud下的用戶鑒權(quán)方案》了讨,它是微服務(wù)下統(tǒng)一資源訪問權(quán)限的控制楔敌,就像一道墻保護(hù)著SpringCloud集群下的各個業(yè)務(wù)應(yīng)用服務(wù)贫奠。而本章要講的是權(quán)限控制的另一個層面數(shù)據(jù)權(quán)限温数,意思是控制可訪問數(shù)據(jù)資源的數(shù)量。

舉個例子:

有一批業(yè)務(wù)員跟進(jìn)全國的銷售訂單也祠。他們被按城市進(jìn)行劃分乒躺,一個業(yè)務(wù)員跟進(jìn)3個城市的訂單,為了保護(hù)公司的業(yè)務(wù)數(shù)據(jù)不能被所有人都掌握扭屁,故每個業(yè)務(wù)員只能看到自己負(fù)責(zé)城市的訂單數(shù)據(jù)算谈。所以從系統(tǒng)來講每個業(yè)務(wù)員都有訪問銷售訂單的功能,然后再需要配置每個業(yè)務(wù)員負(fù)責(zé)的城市疯搅,以此對訂單數(shù)據(jù)進(jìn)行篩選濒生。

要實現(xiàn)此功能有很多方法,如果系統(tǒng)中多個地方都需要類似的需求幔欧,那我們就可以將其提出來做成一個通用的功能罪治。這里我介紹一個相對簡單的解決方案,以供參考礁蔗。

一觉义、 整體架構(gòu)

image

數(shù)據(jù)權(quán)限為作一個注解的形式掛在每一個需要數(shù)據(jù)權(quán)限控制的Controller上,由于和具體的程序邏輯有關(guān)故有一定的入侵性浴井,且需要數(shù)據(jù)庫配合使用晒骇。

二、 實現(xiàn)流程

image
  1. 瀏覽器傳帶查詢權(quán)限范圍參數(shù)訪問Controller磺浙,如cities
POST http://127.0.0.1:8000/order/query
accept: */*
Content-Type: application/json
token: 1e2b2298-8274-4599-a26f-a799167cc82f

{"cities":["cq","cd","bj"],"userName":"string"}
  1. 通過注解攔截權(quán)限范圍參數(shù)洪囤,并根據(jù)預(yù)授權(quán)范圍比較,回寫在授權(quán)范圍內(nèi)的權(quán)限范圍參數(shù)

    cities = ["cq","cd"]
    
  2. 通過參數(shù)傳遞到DAO層,在SQL語句中拼裝出查詢條件,實現(xiàn)數(shù)據(jù)的過濾

    select * from order where city in ('cq','cd')
    

三击吱、 實現(xiàn)步驟

1. 注解實現(xiàn)

注解的完整代碼叁幢,請詳見源代碼

1)創(chuàng)建注解

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface ScopeAuth {

    String token() default "AUTH_TOKEN";
    String scope() default "";
    String[] scopes() default {};
}

此注解為運行時RetentionPolicy.RUNTIME作用在方法上ElementType.METHOD

token:獲取識別唯一用戶的標(biāo)識袒炉,與用戶數(shù)據(jù)權(quán)限存儲有關(guān)

scopescopes:預(yù)請求的數(shù)據(jù)權(quán)限范圍

2) AOP實現(xiàn)注解

public class ScopeAuthAdvice {
  
    @Around("@annotation(scopeAuth)")
    public Object before(ProceedingJoinPoint thisJoinPoint, ScopeAuth scopeAuth) throws Throwable {
        // ... 省略過程
        // 獲取token
        String authToken = getToken(args, scopeAuth.token(), methodSignature.getMethod());
            // 回寫范圍參數(shù)
        setScope(scopeAuth.scope(), methodSignature, args, authToken);
        
        return thisJoinPoint.proceed();
    }

    /**
     * 設(shè)置范圍
     */
    private void setScope(String scope, MethodSignature methodSignature, Object[] args, String authToken) {
        // 獲取請求范圍
        Set<String> requestScope = getRequestScope(args, scope, methodSignature.getMethod());
        ScopeAuthAdapter adapter = new ScopeAuthAdapter(supplier);
        // 已授權(quán)范圍
        Set<String> authorizedScope = adapter.identifyPermissionScope(authToken, requestScope);
        // 回寫新范圍
        setRequestScope(args, scope, authorizedScope, methodSignature.getMethod());
    }

    /**
     * 回寫請求范圍
     */
    private void setRequestScope(Object[] args, String scopeName, Collection<String> scopeValues, Method method) {
        // 解析 SPEL 表達(dá)式
        if (scopeName.indexOf(SPEL_FLAG) == 0) {
            ParseSPEL.setMethodValue(scopeName, scopeValues, method, args);
        }
    }
}

此為演示代碼省略了過程,主要功能為通過token拿到預(yù)先授權(quán)的數(shù)據(jù)范圍,再與本次請求的范圍做交集府怯,最后回寫回原參數(shù)。

過程中用到了較多的SPEL表達(dá)式防楷,用于計算表達(dá)式結(jié)果牺丙,具體請參考ParseSPEL文件

3)權(quán)限范圍交集計算

public class ScopeAuthAdapter {

    private final AuthQuerySupplier supplier;

    public ScopeAuthAdapter(AuthQuerySupplier supplier) {
        this.supplier = supplier;
    }

    /**
     * 驗證權(quán)限范圍
     * @param token
     * @param requestScope
     * @return
     */
    public Set<String> identifyPermissionScope(String token, Set<String> requestScope) {
        Set<String> authorizeScope = supplier.queryScope(token);

        String ALL_SCOPE = "AUTH_ALL";
        String USER_ALL = "USER_ALL";

        if (authorizeScope == null) {
            return null;
        }

        if (authorizeScope.contains(ALL_SCOPE)) {
            // 如果是全開放則返回請求范圍
            return requestScope;
        }

        if (requestScope == null) {
            return null;
        }

        if (requestScope.contains(USER_ALL)){
            // 所有授權(quán)的范圍
            return authorizeScope;
        }

        // 移除不同的元素
        requestScope.retainAll(authorizeScope);

        return requestScope;
    }
}

此處為了方便設(shè)置,有兩個關(guān)鍵字范圍

  • AUTH_ALL:預(yù)設(shè)所有范圍复局,全開放的意思赘被,為數(shù)據(jù)庫預(yù)先設(shè)置值是整,請求傳什么值都通過
  • USER_ALL:請求所有授權(quán)的范圍,請求時傳此值則會以數(shù)據(jù)庫預(yù)設(shè)值為準(zhǔn)

4) spring.factories自動導(dǎo)入類配置

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector=\
  fun.barryhome.cloud.annotation.ScopeAuthAdvice

如果注解功能是單獨項目存在民假,在使用時有可能會存在找不到引入文件的問題,可通過此配置文件自動載入需要初始化的類

2. 注解使用

@ScopeAuth(scopes = {"#orderDTO.cities"}, token = "#request.getHeader(\"X-User-Name\")")
@PostMapping(value = "/query")
public String query(@RequestBody OrderDTO orderDTO, HttpServletRequest request) {
    return Arrays.toString(orderDTO.getCities());
}

在需要使用數(shù)據(jù)權(quán)限的controller方法上增加@ScopeAuth注解

scopes = {"#orderDTO.cities"}:表示取輸入?yún)?shù)orderDTOcities值龙优,這里是表達(dá)式必須加#

實際開發(fā)過程中羊异,需要將orderDTO.getCities()帶入后續(xù)邏輯中,在DAO層將此拼裝在SQL中彤断,以實現(xiàn)數(shù)據(jù)過濾功能

3. 實現(xiàn)AuthStoreSupplier

AuthStoreSupplier接口為數(shù)據(jù)權(quán)限的存儲接口野舶,與AuthQuerySupplier配合使用,可按實際情況實現(xiàn)

此接口為非必要接口宰衙,可由數(shù)據(jù)庫或Redis存儲(推薦)平道,一般在登錄的同時保存在Redis

4. 實現(xiàn)AuthQuerySupplier

AuthQuerySupplier接口為數(shù)據(jù)權(quán)限查詢接口,可按存儲方法進(jìn)行查詢供炼,推薦使用Redis

@Component
public class RedisAuthQuerySupplier implements AuthQuerySupplier {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 查詢范圍
     */
    @Override
    public Set<String> queryScope(String key) {
        String AUTH_USER_KEY = "auth:logic:user:%s";
        String redisKey = String.format(AUTH_USER_KEY, key);

        List<String> range = redisTemplate.opsForList().range(redisKey, 0, -1);

        if (range != null) {
            return new HashSet<>(range);
        } else {
            return null;
        }
    }
}

在分布式結(jié)構(gòu)里一屋,也可將此實現(xiàn)提出到權(quán)限模塊,采用遠(yuǎn)程調(diào)用方式袋哼,進(jìn)一步解耦

5. 開啟數(shù)據(jù)權(quán)限

@EnableScopeAuth
@EnableDiscoveryClient
@SpringBootApplication
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

四冀墨、 綜述

至此數(shù)據(jù)權(quán)限功能就實現(xiàn)了。在微服務(wù)器架構(gòu)中為了實現(xiàn)功能的復(fù)用涛贯,將注解的創(chuàng)建AuthQuerySupplier的實現(xiàn)提取到公共模塊中诽嘉,那么在具體的使用模塊就簡單得多了。只需增加@ScopeAuth注解弟翘,配置好查詢方法就可以使用虫腋。

五、源代碼

文中代碼由于篇幅原因有一定省略并不是完整邏輯稀余,如有興趣請F(tuán)ork源代碼
https://gitee.com/hypier/barry-cloud/tree/master/cloud-auth-logic

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末悦冀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子滚躯,更是在濱河造成了極大的恐慌雏门,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掸掏,死亡現(xiàn)場離奇詭異茁影,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)丧凤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門募闲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人愿待,你說我怎么就攤上這事浩螺⊙セ迹” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵要出,是天一觀的道長鸳君。 經(jīng)常有香客問我,道長患蹂,這世上最難降的妖魔是什么或颊? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮传于,結(jié)果婚禮上囱挑,老公的妹妹穿的比我還像新娘。我一直安慰自己沼溜,他們只是感情好平挑,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著系草,像睡著了一般通熄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悄但,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天棠隐,我揣著相機(jī)與錄音,去河邊找鬼檐嚣。 笑死助泽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嚎京。 我是一名探鬼主播嗡贺,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鞍帝!你這毒婦竟也來了诫睬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤帕涌,失蹤者是張志新(化名)和其女友劉穎摄凡,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚓曼,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡亲澡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了纫版。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片床绪。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出癞己,到底是詐尸還是另有隱情膀斋,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布痹雅,位于F島的核電站仰担,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏练慕。R本人自食惡果不足惜惰匙,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铃将。 院中可真熱鬧,春花似錦哑梳、人聲如沸劲阎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悯仙。三九已至,卻和暖如春吠卷,著一層夾襖步出監(jiān)牢的瞬間锡垄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工祭隔, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留货岭,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓疾渴,卻偏偏與公主長得像千贯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子搞坝,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345