目录

1. 介绍

2. Nacos SPI 鉴权机制

3. 后台管理 / HTTP 接口鉴权

4. 客户端 / GRPC 接口鉴权


1. 介绍

鉴权功能默认没有开启,开启后的效果就是 Nacos 的接口需要用户登录并且具有权限才能调用该接口。例如注册实例、发布配置等。

鉴权也就是 我是谁、我能干什么

2. Nacos SPI 鉴权机制

默认未开启鉴权,需要启用。

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_用户登录

 

Nacos 鉴权系统使用 SPI 机制实现,这意味着用户可以自己实现扩展一个鉴权系统。Nacos 默认也提供了两个默认实现。

鉴权 SPI 接口是 com.alibaba.nacos.plugin.auth.spi.server.AuthPluginService,所有鉴权类都实现此接口。

注释里对该接口的方法做了解释说明

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_springcloud_02

 

下来看看默认实现 NacosAuthPluginService 的鉴权源码

com.alibaba.nacos.plugin.auth.impl.NacosAuthPluginService

以下只看这两个关键方法

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_springcloud_03

 

validateIdentity 验证身份

下面只看关键代码:用户登录,用户登录的过程就是拿到token,然后校验token,校验通过就从token中获取到用户

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_java_04

 

validateIdentity 验证权限

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_springcloud_05

 

3. 后台管理 / HTTP 接口鉴权

首先使用 @Secured 定义了受保护资源,然后使用 com.alibaba.nacos.core.auth.AuthFilter 进行解析处理

AuthFilter 是一个 Servlet 的 Filter,拦截了全部请求

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_用户登录_06

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_HTTP_07

 

接下来看看 AuthFilter 的 filter 如何实现

// com.alibaba.nacos.core.auth.AuthFilter

public class AuthFilter implements Filter {
    
    private final AuthConfigs authConfigs;
    
    private final ControllerMethodsCache methodsCache;
    
    private final HttpProtocolAuthService protocolAuthService;
    
    public AuthFilter(AuthConfigs authConfigs, ControllerMethodsCache methodsCache) {
        this.authConfigs = authConfigs;
        this.methodsCache = methodsCache;
        this.protocolAuthService = new HttpProtocolAuthService(authConfigs);
        this.protocolAuthService.initialize();
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        if (!authConfigs.isAuthEnabled()) {
            // 没启用鉴权,本次过滤器直接通过
            chain.doFilter(request, response);
            return;
        }
        
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        
        // 省略部分代码
        
        try {
            
            // 获取到当前请求的接口方法
            Method method = methodsCache.getMethod(req);
            
            if (method == null) {
                // 方法不存在,说明请求的是静态资源(.css,.png等),本次过滤器直接通过
                chain.doFilter(request, response);
                return;
            }
            
            if (method.isAnnotationPresent(Secured.class) && authConfigs.isAuthEnabled()) {
                // 如果当前方法上放了注解 Secured 并且 启用了鉴权功能(上面的application.properties改true的那个配置项) 就进来
                
                // 获取到 注解 Secured
                Secured secured = method.getAnnotation(Secured.class);
                if (!protocolAuthService.enableAuth(secured)) {
                    // 默认配置永远启用 auth,所以默认配置这里进不来
                    chain.doFilter(request, response);
                    return;
                }
                
                // 解析请求中的资源(从请求中到namespaceId、group、name等信息构建成一个 Resource 对象)
                Resource resource = protocolAuthService.parseResource(req, secured);
                
                // 从请求中获取到需要的 身份相关字段与值 (Authorization、accessToken、username、password)
                IdentityContext identityContext = protocolAuthService.parseIdentity(req);
                
                // 验证身份
                boolean result = protocolAuthService.validateIdentity(identityContext, resource);
                if (!result) {
                    // 验证失败抛出异常
                    throw new AccessException("Validate Identity failed.");
                }
                
                injectIdentityId(req, identityContext);
                String action = secured.action().toString();
                
                // 验证权限
                result = protocolAuthService.validateAuthority(identityContext, new Permission(resource, action));
                if (!result) {
                    // 验证失败抛出异常
                    throw new AccessException("Validate Authority failed.");
                }
            }
            chain.doFilter(request, response);
        } catch (AccessException e) {
            // 省略错误处理
        }
    }  
}

整体主流程概括下就是:获取到请求的数据、验证身份、验证权限。

鉴权功能的实现就是 验证身份验证权限 这两个功能的实现

  1. 验证身份 protocolAuthService.validateIdentity()
  2. 验证权限 protocolAuthService.validateAuthority()

看看这两个方法如何实现

// com.alibaba.nacos.auth.AbstractProtocolAuthService

public abstract class AbstractProtocolAuthService<R> implements ProtocolAuthService<R> {
    
    @Override
    public boolean validateIdentity(IdentityContext identityContext, Resource resource) throws AccessException {
        Optional<AuthPluginService> authPluginService = AuthPluginManager.getInstance()
                .findAuthServiceSpiImpl(authConfigs.getNacosAuthSystemType());
        if (authPluginService.isPresent()) {
            return authPluginService.get().validateIdentity(identityContext, resource);
        }
        return true;
    }
    
    
    @Override
    public boolean validateAuthority(IdentityContext identityContext, Permission permission) throws AccessException {
        Optional<AuthPluginService> authPluginService = AuthPluginManager.getInstance()
                .findAuthServiceSpiImpl(authConfigs.getNacosAuthSystemType());
        if (authPluginService.isPresent()) {
            return authPluginService.get().validateAuthority(identityContext, permission);
        }
        return true;
    }

}

解释一下

Optional<AuthPluginService> authPluginService = AuthPluginManager.getInstance()
                .findAuthServiceSpiImpl(authConfigs.getNacosAuthSystemType());

authConfigs.getNacosAuthSystemType() 为获取到当前系统配置的 授权类型

因为鉴权系统是 SPI 机制的,所以意味着用户可以自己扩展,而默认是 Nacos 默认的实现,可以通过配置文件更改为自己实现的。

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_java_08

 

此时拿到了配置的授权类型 字符串 nacos

然后需要找到这个 spi 实现类

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_用户登录_09

 

可以发现 SPI 接口 AuthPluginService 实现类 Nacos 实现了 2 个

LdapAuthPluginService、NacosAuthPluginService(默认)

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_java_10

 

就是根据这里的 authServiceName 找到了这个鉴权实现类

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_springcloud_11

找到该 SPI 鉴权类后,就调用该类的方法来完成鉴权逻辑了。

4. 客户端 / GRPC 接口鉴权

客户端需要配置账号才能有权限注册服务,否则报错403 无权限

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_HTTP_12

 

SpringCloud nacos 的配置

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_HTTP_13

 

请求 GRPC 接口需要 传入 accessToken,accessToken 从哪里来?就是下面这段代码:

在创建 NamingFactory.createNamingService(Properties properties) 里初始化过程中,最终调用了以下 initSecurityProxy 方法

该方法内容:

  1. 创建一个可调度的1个线程的线程池
  2. 根据配置的 username password 请求 nacos server 做一次登录
  3. 可调度线程池 每5秒 定时调度任务 登录一次,也就是每隔 5 秒就请求登录接口。

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_用户登录_14

 

登录方法做的就是 将 accessToken 存起来

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_nacos_15

 

由于 Nacos Client 2.x 版本之后使用的是 GRPC 请求了,所以不走 HTTP 了,那么鉴权如何实现呢

答案就是 过滤器,没错还是过滤器,不过不是 Servlet 的AuthFilter了,而是专门给 GRPC 做的过滤器。

RemoteRequestAuthFilter

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_用户登录_16

 

GRPC 过滤器就是在每次接收到 GRPC 请求之后,先调用过滤器来实现的。

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_用户登录_17

观察代码,发现 filter 里面的代码也是判断了 Secured 注解,判断逻辑和之前的一样

观察注册实例 GRPC 接口,也有 Secured 注解

nacos 鉴权 springboot 怎么连接 nacos开启鉴权_HTTP_18

 

此次就流程就全部走完了。