nacos源码之Auth(权限)模块-1(授权过滤器与控制器缓存)

下一篇:《nacos源码之Auth(权限)模块-2(权限管理与权限配置)》


Nacos的Auth模块

  • Nacos的Auth模块
    • 授权过滤器(权限核心注解)
      • 注解实现讲解
      • 权限操作类型
    • 控制器缓存
      • 获取方法
      • 初始化两个对象表
    • Naco读后感



上一篇《nacos源码构建与总览》浏览器还挺多,接下来模块的精讲来了

《nacos源码构建与总览》

Nacos的Auth模块

auth模块管理了nacos的权限,该权限系统设计面向租户模式,功能比较简单,不适用于业务系统。

授权过滤器(权限核心注解)

源码如下:

package com.alibaba.nacos.auth.annotation;
@Retention(RetentionPolicy.RUNTIME)
public @interface Secured {
     
	// 操作类型(nacos只定义了读和写两种操作类型)
    ActionTypes action() default ActionTypes.READ;
    // 操作的资源
    String resource() default StringUtils.EMPTY;
    Class<? extends ResourceParser> parser() default DefaultResourceParser.class;
}

nacos 权限模块只定义了两种操作类型,。但从这个角度也无法胜任业务系统的权限框架,但是对于服务发现,配置管理视乎也够用了。

在一个通用的权限框架中,权限框架只需要管理权限资源的操作类型是否通过即可,这一点Nacos是相同的。

注解实现讲解

这里就是整个权限模块的核心了。

该注解的实现并不在auth包下,但是注解的实现也是属于权限模块的,因此在本章节讲解。

看这个代码之前,先强调一些知识,帮助理解nacos的权限实现 :

  1. nacos使用spring mvc架构。因此它是servlet,并不是netty的非阻塞框架。
  2. nacos注解使用过滤器实现。

nettyservlet并没有优略之分,它们两个亦不是水火不容,使用netty还是使用servlet,需要根据解决问题的场景参考,它们本身就是为服务实现提出的不同的解决方案。

由于代码篇幅过长,带有备注的代码已经提交到Github ,请见 :https://github.com/keepgoon/nacos/blob/develop/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java

核心片段:

             // 获取缓存方法
            Method method = methodsCache.getMethod(req);
            // 没有匹配到方法,说明未被权限模块扫描,直接放行
            if (method == null) {
     
                chain.doFilter(request, response);
                return;
            }
            // 如果含有权限注解才去进行权限校验
            if (method.isAnnotationPresent(Secured.class) && authConfigs.isAuthEnabled()) {
     

                Secured secured = method.getAnnotation(Secured.class);
                String action = secured.action().toString();
                String resource = secured.resource();
                
                if (StringUtils.isBlank(resource)) {
     
                    ResourceParser parser = getResourceParser(secured.parser());
                    resource = parser.parseName(req);
                }
                if (StringUtils.isBlank(resource)) {
     
                  // 如果没有匹配到资源直接报权限异常
                    throw new AccessException("resource name invalid!");
                }
                // 进行授权校验,不通过就抛出AccessException
                authManager.auth(new Permission(resource, action), authManager.login(req));
                
            }

注解的核心实现在集成的doFilter方法中实现,随便吐槽一下,这里面的代码似乎并不是特别优雅。

小知识来了 :

授权过滤器中使用了ConcurrentHashMap,这个集合并发度是比较牛的。这里不讲多线程,粗略的说一下原理,HashTable使用的是 synchronized关键字,是对整个对象加锁,而 ConcurrentHashMap采用了分段锁。有兴趣的可以评论区留言

实现逻辑:

  1. 首先判断是否开启权限控制。这个功能在权限配置中实现,具体见下面的权限配置
  2. 然后判断了是否开启白名单。处理方式也很简单,直接交给其他过滤器解决。但是还是加了些判断,判断请求头里是否含有User-Agent属性,有的话才去交给另一个过滤器执行。
  3. 第三个判断就判断了是否含有授权Key,如果请求头里面含有授权Key,就和配置的授权信息比较,如果相同就交给其他过滤器执行。
  4. 如果啥都没有,就会执行到403。好歹你得给点东西证明一下你是谁吧。要不然不能过。
  5. 接下来就是权限控制了。先去请求缓存中获取请求,判断一下方法上是否含有权限注解(Secured),如果没有权限注解自然是直接放行。
  6. 如果使用了权限注解,获取操作类型,和资源类型,然后比较权限,符合就服务,不符合就抛异常,具体实现在AuthManager(授权管理)中实现。
  7. 如果AuthManager(授权管理) 权限校验没有通过就抛出AccessException
  8. 如果捕捉到AccessException,请求结束返回 403


权限操作类型

如上所述:nacos只有读和写两种操作类型,定义如下 :

package com.alibaba.nacos.auth.common;

public enum ActionTypes {
     
    /**
     * Read.
     */
    READ("r"),
    /**
     * Write.
     */
    WRITE("w");
    private String action;
}

控制器缓存

控制器缓存用来解决多次反射的问题,在Nacos启动时扫描所有控制器,将请求路径与控制器方法匹配。

内部实现了两个Map,map使用 ConcurrentHashMap 实现,保证了高并发的效率,有了该缓存也减少了反射的次数,提供了Nacos的性能。

内部两个Map定义如下:

    // 请求与方法的对照表
    private ConcurrentMap<RequestMappingInfo, Method> methods = new ConcurrentHashMap<>();
    // 请求地址与请求方法的匹配集合
    private final ConcurrentMap<String, List<RequestMappingInfo>> urlLookup = new ConcurrentHashMap<>();
  1. methods是请求与方法的对应表,一个请求对应一个方法。
  2. urlLookup是地址与请求列表的对应表,key是地址,格式为POST-->com.alibaba.nacos.core.controller/nacos/v1/install,正常情况下一个地址对应一个请求,但是可能存在一个请求地址对应多个请求

获取方法

在权限过滤器中,在执行注解逻辑中,首先需要根据请求,从缓存控制器中找到匹配的方法。

Method method = methodsCache.getMethod(req);

获取方法实现逻辑如下:

  1. 获取请求路径,组装出urlLookup关系表中的Key。
  2. 根据拼接出来的请求路径获取到匹配的请求列表。
  3. 匹配列表筛选出最佳匹配的请求对象RequestMappingInfo
  4. 根据请求对象找到对应的Method,并且返回。

初始化两个对象表

初始化方法:

	/**
     * init初始化反射方法
     *
     * @param packageName package name
     */
    public void initClassMethod(String packageName) {
     
        Reflections reflections = new Reflections(packageName);
        Set<Class<?>> classesList = reflections.getTypesAnnotatedWith(RequestMapping.class);
        
        for (Class clazz : classesList) {
     
            initClassMethod(clazz);
        }
    }

反射Nacos使用了 reflections技术,Maven依赖如下:

<!-- https://github.com/ronmamo/reflections -->
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.12</version>
</dependency>

代码篇幅过长,已提交至Github,完整代码请见 (已经添加了注解):https://github.com/keepgoon/nacos/blob/develop/core/src/main/java/com/alibaba/nacos/core/code/ControllerMethodsCache.java

Naco读后感

使用对象缓存,减少了反射次数,反射比较耗费性能,这样做大大提高了性能,这种方式效率是高于Sping AOP的,因为并不是每次都需要做反射。

ConcurrentHashMap保证了多线程的效率。分段锁要比对象锁效率要高。

使用别人造好的轮子reflections,减少了反射的代码量和使用难度。

SRP(单一职责原则),单一职责原则不但可以降低复杂度,帮助编程人员理清逻辑,还可以很大程度提高代码可读性,既见既所得。


下一篇:《nacos源码之Auth(权限)模块-2(权限管理与权限配置)》

你可能感兴趣的:(#,nacos,nacos,nacos权限控制,spring,cloud)