微服務下的用戶鑒權方案

上一章講了微服務下的用戶身份認證《SpringCloud Gateway 身份認證》,這次主要講如何進行鑒權。
相對上一章的身份認證代碼略有改動

Java下常用的安全框架主要有Spring Securityshiro节芥,都可提供非常強大的功能剧包,但學習成本較高。在微服務下鑒權多多少少都會對服務有一定的入侵性娄琉。
為了降低依賴,減少入侵吓歇,讓鑒權功能相對應用服務透明孽水,我們采用網(wǎng)關攔截資源請求的方式進行鑒權。

一城看、整體架構

用戶鑒權模塊位于API GateWay服務中女气,所有的API資源請求都需要從此通過。

  1. 身份認證测柠,通過則緩存用戶權限數(shù)據(jù)炼鞠,不通過返回401
  2. 用戶鑒權,比對當前訪問資源(URI和Method)是否在已緩存的用戶權限數(shù)據(jù)中轰胁,在則轉(zhuǎn)發(fā)請求給對應應用服務谒主,不在則返回403

二、實現(xiàn)步驟

1. 用戶登陸

public LoginUser login(String userName, String password){
    // 檢查密碼
    User user = userService.checkUser(userName, password);

    LoginUser loginUser = LoginUser.builder()
            .userName(userName)
            .realName(user.getRealName())
            .userToken(UUID.randomUUID().toString())
            .loginTime(new Date())
            .build();

    // 保存session
    session.saveSession(loginUser);

    // 查詢權限
    List<Permission> permissions = permissionRepository.findByUserName(userName);
    // 保存用戶權限到緩存中
    session.saveUserPermissions(userName, permissions);

    return loginUser;
}

// ...
// 緩存用戶權限到Redis
public void saveUserPermissions(String userName, List<Permission> permissions) {
    String key = String.format("login:permission:%s", userName);

    HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();
    hashOperations.putAll(key, permissions.stream().collect(
            Collectors.toMap(p -> p.getMethod().concat(":").concat(p.getUri()),
                    Permission::getName, (k1, k2) -> k2)));

    if (expireTime != null) {
        redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
    }
}
  • 用戶驗證通過后赃阀,下發(fā)userToken霎肯,保存當前登陸信息,緩存用戶授權列表
  • 緩存授權列表時榛斯,為了方便讀取使用hash方式保存為list观游,切勿直接將數(shù)組對象保存為一個object

2. 攔截請求

@Slf4j
@Component
public class AuthorizationFilter extends AbstractGatewayFilterFactory {

    @Autowired
    private Session session;

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {

            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            String uri = request.getURI().getPath();
            String method = request.getMethodValue();

            // 1.從AuthenticationFilter中獲取userName
            String key = "X-User-Name";
            if (!request.getHeaders().containsKey(key)) {
                response.setStatusCode(HttpStatus.FORBIDDEN);
                return response.setComplete();
            }

            String userName = Objects.requireNonNull(request.getHeaders().get(key)).get(0);

            // 2.驗證權限
            if (!session.checkPermissions(userName, uri, method)) {
                log.info("用戶:{}, 沒有權限", userName);
                response.setStatusCode(HttpStatus.FORBIDDEN);
                return response.setComplete();
            }

            return chain.filter(exchange);
        };
    }
}

  • 第一步從取出身份認證模塊傳遞的X-User-Name
  • 第二步去緩存中檢查是否有相應的權限
public boolean checkPermissions(String userName, String uri, String method) {
    String key = String.format("login:permission:%s", userName);
    String hashKey = String.format("%s:%s", method, uri);

    if (redisTemplate.opsForHash().hasKey(key, hashKey)){
        return  true;
    }

    String allKey = "login:permission:all";
    // 權限列表中沒有則通過
    return !redisTemplate.opsForHash().hasKey(allKey, hashKey);
}
  • 權限列表中沒有則通過 主要是放過一些沒有必要配置的公共資源,默認都可以訪問的資源
  • login:permission:all 所有配置過的權限列表需要在程序啟動時放入緩存驮俗,并需要保持數(shù)據(jù)的更新

3. 鑒權Filter配置

spring:
  cloud:
    gateway:
      routes:
        - id: cloud-user
          uri: lb://cloud-user  # 后端服務名
          predicates:
            - Path=/user/**   # 路由地址
          filters:
            - name: AuthenticationFilter  # 身份認證
            - name: AuthorizationFilter   # 用戶鑒權
            - StripPrefix=1 # 去掉前綴
  • 特別注意filter的順序懂缕,必須先做身份認證后再進行鑒權
  • 如果有較多的路由都需要配置,可使用default-filters默認Filter配置

三王凑、其它問題

在做單元測試時搪柑,如遇到如下錯誤

nested exception is java.lang.NoClassDefFoundError: javax/validation/ValidationException

請升級依賴包版本:

<!--升級validation-api的版本-->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.5.Final</version>
</dependency>
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

四吮蛹、完整代碼

https://gitee.com/hypier/barry-cloud

五、請關注我的公眾號

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拌屏,一起剝皮案震驚了整個濱河市潮针,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倚喂,老刑警劉巖每篷,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異端圈,居然都是意外死亡焦读,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門舱权,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矗晃,“玉大人,你說我怎么就攤上這事宴倍≌胖ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵鸵贬,是天一觀的道長俗他。 經(jīng)常有香客問我,道長阔逼,這世上最難降的妖魔是什么兆衅? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮嗜浮,結果婚禮上羡亩,老公的妹妹穿的比我還像新娘。我一直安慰自己危融,他們只是感情好畏铆,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著专挪,像睡著了一般及志。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寨腔,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天速侈,我揣著相機與錄音,去河邊找鬼迫卢。 笑死倚搬,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的乾蛤。 我是一名探鬼主播每界,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼捅僵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了眨层?” 一聲冷哼從身側響起庙楚,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎趴樱,沒想到半個月后馒闷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡叁征,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年纳账,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捺疼。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡疏虫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啤呼,到底是詐尸還是另有隱情卧秘,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布媳友,位于F島的核電站斯议,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏醇锚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一坯临、第九天 我趴在偏房一處隱蔽的房頂上張望焊唬。 院中可真熱鬧,春花似錦看靠、人聲如沸赶促。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸥滨。三九已至,卻和暖如春谤祖,著一層夾襖步出監(jiān)牢的瞬間婿滓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工粥喜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凸主,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓额湘,卻偏偏與公主長得像卿吐,于是被迫代替她去往敵國和親旁舰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355