3 个注解,优雅的实现微服务鉴权(三)

love374
发布于 2022-7-8 17:29
浏览
0收藏

 

3. 注解切面定义
注解有了,那么如何去拦截呢?这里陈某定义了一个切面进行拦截,关键代码如下:

/**
 * @author 公众号:码猿技术专栏
 * @url: www.java-family.cn
 * @description @RequiresLogin,@RequiresPermissions,@RequiresRoles 注解的切面
 */
@Aspect
@Component
public class PreAuthorizeAspect {
    /**
     * 构建
     */
    public PreAuthorizeAspect() {
    }

    /**
     * 定义AOP签名 (切入所有使用鉴权注解的方法)
     */
    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() {
    }

    /**
     * 环绕切入
     *
     * @param joinPoint 切面对象
     * @return 底层方法执行后的返回值
     * @throws Throwable 底层方法抛出的异常
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 注解鉴权
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        checkMethodAnnotation(signature.getMethod());
        try {
            // 执行原有逻辑
            Object obj = joinPoint.proceed();
            return obj;
        } catch (Throwable e) {
            throw e;
        }
    }

    /**
     * 对一个Method对象进行注解检查
     */
    public void checkMethodAnnotation(Method method) {
        // 校验 @RequiresLogin 注解
        RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
        if (requiresLogin != null) {
            doCheckLogin();
        }

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

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


    /**
     * 校验有无登录
     */
    private void doCheckLogin() {
        LoginVal loginVal = SecurityContextHolder.get();
        if (Objects.isNull(loginVal))
            throw new ServiceException(ResultCode.INVALID_TOKEN.getCode(), ResultCode.INVALID_TOKEN.getMsg());
    }

    /**
     * 校验有无对应的角色
     */
    private void doCheckRole(RequiresRoles requiresRoles){
        String[] roles = requiresRoles.value();
        LoginVal loginVal = OauthUtils.getCurrentUser();

        //该登录用户对应的角色
        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 自己实现,由于并未集成前端的菜单权限,根据业务需求自己实现
     */
    private void doCheckPermissions(RequiresPermissions requiresPermissions){

    }
}

 

其实这中间的逻辑非常简单,就是解析的Token中的权限、角色然后和注解中的指定的进行比对。

“@RequiresPermissions这个注解的逻辑陈某并未实现,自己根据业务模仿着完成,算是一道思考题了....


4. 注解使用
比如《Spring Cloud Alibaba 实战》项目中有一个添加文章的接口,只有超管和管理员的角色才能添加,那么可以使用@RequiresRoles注解进行标注,如下:

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

效果这里就不演示了,实际的效果:非超管和管理员角色用户登录访问,将会直接被拦截,返回无权限。

注意:这里仅仅解决了下游服务鉴权的问题,那么feign调用是否也适用?

当然适用,这里使用的是切面方式,feign内部其实使用的是http方式调用,对于接口来说一样适用。

比如《Spring Cloud Alibaba 实战》项目中获取文章列表的接口,其中会通过feign的方式调用评论服务中的接口获取文章评论总数,这里一旦加上了@RequiresRoles,那么调用将会失败,代码如下:

@RequiresRoles
@ApiOperation(value = "批量获取文章总数")
@PostMapping(value = "/list/total")
public ResultMsg<List<TotalVo>> listTotal(@RequestBody @Valid List<CommentListReq> param){
....
}

总结
本文主要介绍了微服务中如何将鉴权下放到微服务中,也是为了解决读者的疑惑,实际生产中除非业务需要,陈某还是建议将鉴权统一放到网关中。

 

文章转自公众号:码猿技术专栏

标签
已于2022-7-8 17:29:53修改
收藏
回复
举报
回复
    相关推荐