文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

只要三个注解,优雅的实现微服务鉴权!

2024-11-30 15:39

关注
  1. 鉴权放在各个微服务中如何做?
  2. feign的调用如何做到的鉴权?

今天针对以上两个问题深入聊聊如何通过三个注解解决。

前面的几篇文章陈某都是将鉴权和认证统一的放在了网关层面,架构如下:

微服务中的鉴权还有另外一种思路:将鉴权交给下游的各个微服务,网关层面只做路由转发。

这种思路其实实现起来也是很简单,下面针对网关层面鉴权的代码改造一下即可完成:实战干货!Spring Cloud Gateway 整合 OAuth2.0 实现分布式统一认证授权!

1. 干掉鉴权管理器

在网关统一鉴权实际是依赖的鉴权管理器ReactiveAuthorizationManager,所有的请求都需要经过鉴权管理器的去对登录用户的权限进行鉴权。

这个鉴权管理器在网关鉴权的文章中也有介绍,在陈某的《Spring Cloud Alibaba 实战》中配置拦截也很简单,如下:

除了配置的白名单,其他的请求一律都要被网关的鉴权管理器拦截鉴权,只有鉴权通过才能放行路由转发给下游服务。

看到这里思路是不是很清楚了,想要将鉴权交给下游服务,只需要在网关层面直接放行,不走鉴权管理器,代码如下:

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

2. 定义三个注解

经过第①步,鉴权已经下放给下游服务了,那么下游服务如何进行拦截鉴权呢?

其实Spring Security 提供了3个注解用于控制权限,如下:

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

关于这三个注解就不再详细介绍了,有兴趣的可以去查阅官方文档。

陈某这里并不打算使用的内置的三个注解实现,而是自定义了三个注解,如下:

1).@RequiresLogin

见名知意,只有用户登录才能放行,代码如下:


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresLogin {
}

2).@RequiresPermissions

见名知意,只有拥有指定权限才能放行,代码如下:


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresPermissions {

String[] value() default {};


Logical logical() default Logical.AND;
}

3).@RequiresRoles

见名知意,只有拥有指定角色才能放行,代码如下:


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresRoles {

String[] value() default {OAuthConstant.ROLE_ROOT_CODE,OAuthConstant.ROLE_ADMIN_CODE};


Logical logical() default Logical.AND;
}

以上三个注解的含义想必都很好理解,这里就不再解释了....

3. 注解切面定义

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


@Aspect
@Component
public class PreAuthorizeAspect {

public PreAuthorizeAspect() {
}


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)";


@Pointcut(POINTCUT_SIGN)
public void pointcut() {
}


@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;
}
}


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());
}


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){
....
}

总结

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

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯