亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

前面的文章中介紹了網(wǎng)關(guān)集成Spring Security實(shí)現(xiàn)網(wǎng)關(guān)層面的統(tǒng)一的認(rèn)證鑒權(quán)。

  1. 鑒權(quán)放在各個微服務(wù)中如何做?
  2. feign的調(diào)用如何做到的鑒權(quán)?

今天針對以上兩個問題深入聊聊如何通過三個注解解決。

前面的幾篇文章陳某都是將鑒權(quán)和認(rèn)證統(tǒng)一的放在了網(wǎng)關(guān)層面,架構(gòu)如下:

 

圖片

 

微服務(wù)中的鑒權(quán)還有另外一種思路:將鑒權(quán)交給下游的各個微服務(wù),網(wǎng)關(guān)層面只做路由轉(zhuǎn)發(fā)。

這種思路其實(shí)實(shí)現(xiàn)起來也是很簡單,下面針對網(wǎng)關(guān)層面鑒權(quán)的代碼改造一下即可完成:實(shí)戰(zhàn)干貨!Spring Cloud Gateway 整合 OAuth2.0 實(shí)現(xiàn)分布式統(tǒng)一認(rèn)證授權(quán)!

1. 干掉鑒權(quán)管理器

在網(wǎng)關(guān)統(tǒng)一鑒權(quán)實(shí)際是依賴的鑒權(quán)管理器ReactiveAuthorizationManager,所有的請求都需要經(jīng)過鑒權(quán)管理器的去對登錄用戶的權(quán)限進(jìn)行鑒權(quán)。

這個鑒權(quán)管理器在網(wǎng)關(guān)鑒權(quán)的文章中也有介紹,在陳某的《Spring Cloud Alibaba 實(shí)戰(zhàn)》中配置攔截也很簡單,如下:

 

圖片

 

除了配置的白名單,其他的請求一律都要被網(wǎng)關(guān)的鑒權(quán)管理器攔截鑒權(quán),只有鑒權(quán)通過才能放行路由轉(zhuǎn)發(fā)給下游服務(wù)。

看到這里思路是不是很清楚了,想要將鑒權(quán)交給下游服務(wù),只需要在網(wǎng)關(guān)層面直接放行,不走鑒權(quán)管理器,代碼如下:

http
 ....
 //白名單直接放行
  .pathMatchers(ArrayUtil.toArray(whiteUrls.getUrls(), String.class)).permitAll()
 //其他的任何請求直接放行
  .anyExchange().permitAll()
  .....

2. 定義三個注解

經(jīng)過第①步,鑒權(quán)已經(jīng)下放給下游服務(wù)了,那么下游服務(wù)如何進(jìn)行攔截鑒權(quán)呢?

其實(shí)Spring Security 提供了3個注解用于控制權(quán)限,如下:

  1. @Secured
  2. @PreAuthorize
  3. @PostAuthorize

關(guān)于這三個注解就不再詳細(xì)介紹了,有興趣的可以去查閱官方文檔。

陳某這里并不打算使用的內(nèi)置的三個注解實(shí)現(xiàn),而是自定義了三個注解,如下:

1).@RequiresLogin

見名知意,只有用戶登錄才能放行,代碼如下:

/**
 * @author 公眾號:碼猿技術(shù)專欄
 * @url: www.JAVA-family.cn
 * @description 登錄認(rèn)證的注解,標(biāo)注在controller方法上,一定要是登錄才能的訪問的接口
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresLogin {
}

2).@RequiresPermissions

見名知意,只有擁有指定權(quán)限才能放行,代碼如下:

/**
 * @author 公眾號:碼猿技術(shù)專欄
 * @url: www.java-family.cn
 * @description 標(biāo)注在controller方法上,確保擁有指定權(quán)限才能訪問該接口
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresPermissions {
    /**
     * 需要校驗(yàn)的權(quán)限碼
     */
    String[] value() default {};

    /**
     * 驗(yàn)證模式:AND | OR,默認(rèn)AND
     */
    Logical logical() default Logical.AND;
}

3).@RequiresRoles

見名知意,只有擁有指定角色才能放行,代碼如下:

/**
 * @author 公眾號:碼猿技術(shù)專欄
 * @url: www.java-family.cn
 * @description 標(biāo)注在controller方法上,確保擁有指定的角色才能訪問該接口
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresRoles {
    /**
     * 需要校驗(yàn)的角色標(biāo)識,默認(rèn)超管和管理員
     */
    String[] value() default {OAuthConstant.ROLE_ROOT_CODE,OAuthConstant.ROLE_ADMIN_CODE};

    /**
     * 驗(yàn)證邏輯:AND | OR,默認(rèn)AND
     */
    Logical logical() default Logical.AND;
}

以上三個注解的含義想必都很好理解,這里就不再解釋了....

3. 注解切面定義

注解有了,那么如何去攔截呢?這里陳某定義了一個切面進(jìn)行攔截,關(guān)鍵代碼如下:

/**
 * @author 公眾號:碼猿技術(shù)專欄
 * @url: www.java-family.cn
 * @description @RequiresLogin,@RequiresPermissions,@RequiresRoles 注解的切面
 */
@Aspect
@Component
public class PreAuthorizeAspect {
    /**
     * 構(gòu)建
     */
    public PreAuthorizeAspect() {
    }

    /**
     * 定義AOP簽名 (切入所有使用鑒權(quán)注解的方法)
     */
    public static final String POINTCUT_SIGN = " @annotation(com.mugu.blog.common.annotation.RequiresLogin) || "
            + "@annotation(com.mugu.blog.common.annotation.RequiresPermissions) || "
            + "@annotation(com.mugu.blog.common.annotation.RequiresRoles)";

    /**
     * 聲明AOP簽名
     */
    @Pointcut(POINTCUT_SIGN)
    public void pointcut() {
    }

    /**
     * 環(huán)繞切入
     *
     * @param joinPoint 切面對象
     * @return 底層方法執(zhí)行后的返回值
     * @throws Throwable 底層方法拋出的異常
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 注解鑒權(quán)
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        checkMethodAnnotation(signature.getMethod());
        try {
            // 執(zhí)行原有邏輯
            Object obj = joinPoint.proceed();
            return obj;
        } catch (Throwable e) {
            throw e;
        }
    }

    /**
     * 對一個Method對象進(jìn)行注解檢查
     */
    public void checkMethodAnnotation(Method method) {
        // 校驗(yàn) @RequiresLogin 注解
        RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
        if (requiresLogin != null) {
            doCheckLogin();
        }

        // 校驗(yàn) @RequiresRoles 注解
        RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class);
        if (requiresRoles != null) {
            doCheckRole(requiresRoles);
        }

        // 校驗(yàn) @RequiresPermissions 注解
        RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
        if (requiresPermissions != null) {
            doCheckPermissions(requiresPermissions);
        }
    }


    /**
     * 校驗(yàn)有無登錄
     */
    private void doCheckLogin() {
        LoginVal loginVal = SecurityContextHolder.get();
        if (Objects.isNull(loginVal))
            throw new ServiceException(ResultCode.INVALID_TOKEN.getCode(), ResultCode.INVALID_TOKEN.getMsg());
    }

    /**
     * 校驗(yàn)有無對應(yīng)的角色
     */
    private void doCheckRole(RequiresRoles requiresRoles){
        String[] roles = requiresRoles.value();
        LoginVal loginVal = OauthUtils.getCurrentUser();

        //該登錄用戶對應(yīng)的角色
        String[] authorities = loginVal.getAuthorities();
        boolean match=false;

        //and 邏輯
        if (requiresRoles.logical()==Logical.AND){
            match = Arrays.stream(authorities).filter(StrUtil::isNotBlank).allMatch(item -> CollectionUtil.contains(Arrays.asList(roles), item));
        }else{  //OR 邏輯
            match = Arrays.stream(authorities).filter(StrUtil::isNotBlank).anyMatch(item -> CollectionUtil.contains(Arrays.asList(roles), item));
        }

        if (!match)
            throw new ServiceException(ResultCode.NO_PERMISSION.getCode(), ResultCode.NO_PERMISSION.getMsg());
    }

    /**
     * TODO 自己實(shí)現(xiàn),由于并未集成前端的菜單權(quán)限,根據(jù)業(yè)務(wù)需求自己實(shí)現(xiàn)
     */
    private void doCheckPermissions(RequiresPermissions requiresPermissions){

    }
}

其實(shí)這中間的邏輯非常簡單,就是解析的Token中的權(quán)限、角色然后和注解中的指定的進(jìn)行比對。

@RequiresPermissions這個注解的邏輯陳某并未實(shí)現(xiàn),自己根據(jù)業(yè)務(wù)模仿著完成,算是一道思考題了....

4. 注解使用

比如《Spring Cloud Alibaba 實(shí)戰(zhàn)》項(xiàng)目中有一個添加文章的接口,只有超管和管理員的角色才能添加,那么可以使用@RequiresRoles注解進(jìn)行標(biāo)注,如下:

@RequiresRoles
@AvoidRepeatableCommit
@ApiOperation("添加文章")
@PostMApping("/add")
public ResultMsg<Void> add(@RequestBody @Valid ArticleAddReq req){
 .......
}

效果這里就不演示了,實(shí)際的效果:非超管和管理員角色用戶登錄訪問,將會直接被攔截,返回?zé)o權(quán)限。

注意:這里僅僅解決了下游服務(wù)鑒權(quán)的問題,那么feign調(diào)用是否也適用?

當(dāng)然適用,這里使用的是切面方式,feign內(nèi)部其實(shí)使用的是http方式調(diào)用,對于接口來說一樣適用。

比如《Spring Cloud Alibaba 實(shí)戰(zhàn)》項(xiàng)目中獲取文章列表的接口,其中會通過feign的方式調(diào)用評論服務(wù)中的接口獲取文章評論總數(shù),這里一旦加上了@RequiresRoles,那么調(diào)用將會失敗,代碼如下:

@RequiresRoles
@ApiOperation(value = "批量獲取文章總數(shù)")
@PostMapping(value = "/list/total")
public ResultMsg<List<TotalVo>> listTotal(@RequestBody @Valid List<CommentListReq> param){
....
}

總結(jié)

本文主要介紹了微服務(wù)中如何將鑒權(quán)下放到微服務(wù)中,也是為了解決讀者的疑惑,實(shí)際生產(chǎn)中除非業(yè)務(wù)需要,陳某還是建議將鑒權(quán)統(tǒng)一放到網(wǎng)關(guān)中。

分享到:
標(biāo)簽:微服
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定