本文基于dubbo 2.7.5版本代码


文章目录

  • 一、配置令牌
  • 二、原理
  • 1、服务端令牌的生成
  • 2、消费端保存令牌
  • 3、令牌验证


dubbo提供了令牌验证功能。下面是官网对令牌的介绍:

通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者。

从这段介绍可以看出,令牌要求消费端必须通过注册中心获取令牌,否则无法访问服务。这样注册中心就有了管理令牌的功能,注册中心可以确定哪些消费者获得令牌,哪些消费者无法获得令牌,还可以使令牌失效。下面以zookeeper为例,简单介绍一下如何实现注册中心的管理功能。

  • 服务端启动的时候会监听注册中心的“/dubbo/接口名/configurators”节点,该节点可以存储服务端的配置信息,如果需要修改令牌,可以修改该节点数据。修改后,服务端的监听器OverrideListener收到修改后的配置,服务端会重新发布服务,并且重新注册信息。
  • 消费端启动的时候会监听注册中心的“/dubbo/接口名/providers”节点,该节点存储服务提供者的信息,当提供者启动完毕或者重新发布服务,都会修改该节点。消费端监听到该监听变化后,通知监听器ReferenceConfigurationListener,该监听器会重新创建与服务端的连接,重新注册消费者信息。

那么我们怎么配置令牌?

一、配置令牌

配置令牌可以只使单个服务生效,也可以使服务端所有的服务都生效。

  • @Service(token=“true”)或者@Service(token=“123456”),前者dubbo生成随机token,后者指定了token为“123456”;
  • dubbo.provider.token="true"或者dubbo.provider.token=“123456”。可以使服务端所有的服务都生效。

二、原理

1、服务端令牌的生成

服务端启动时,dubbo调用ServiceConfig暴露服务会执行如下代码:

if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(TOKEN_KEY, token);
            }
        }

从上面这段代码可以看出,如果配置了token,则使用配置值,如果没有配置,则使用随机UUID值。token值放入map中后,dubbo将其发布到注册中心providers节点下。这样消费端就可以获取到令牌了。

2、消费端保存令牌

消费端启动后,从providers节点下得到服务提供者配置信息,其中就包含了token值,消费端将token值保存在本地。每次访问服务时,将该值放在报文中发送过去。比如使用dubbo协议的话,token放在attachments里面。

3、令牌验证

服务端配置了token值后,dubbo会引入过滤器TokenFilter校验token。

//从group值可以看出,该过滤器只在服务端生效
@Activate(group = CommonConstants.PROVIDER, value = TOKEN_KEY)
public class TokenFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation inv)
            throws RpcException {
        //获取服务端配置的token值
        String token = invoker.getUrl().getParameter(TOKEN_KEY);
        if (ConfigUtils.isNotEmpty(token)) {
            Class<?> serviceType = invoker.getInterface();
            Map<String, String> attachments = inv.getAttachments();
            //从请求数据里面得到token,也就是消费端发送过来的token
            String remoteToken = (attachments == null ? null : attachments.get(TOKEN_KEY));
            //两个token值如果不一致就抛出异常
            if (!token.equals(remoteToken)) {
                throw new RpcException("Invalid token! Forbid invoke remote service " + serviceType + " method " + inv.getMethodName() + "() from consumer " + RpcContext.getContext().getRemoteHost() + " to provider " + RpcContext.getContext().getLocalHost());
            }
        }
        return invoker.invoke(inv);
    }

}