• 作者:老汪软件技巧
  • 发表时间:2024-11-20 15:02
  • 浏览量:

一、需求场景说明

我们在做多租户的SaaS服务时,时长碰到需要自定义子域名以及独立域名绑定的问题,例如 Coding 的设计:

注册租户(团队),填写团队名称、团队域名(团队编码),例如:

{tenant-code}.coding.net

例如我注册了个团队: Hamm(hamm)

接下来使用这个域名来访问系统,系统自动识别租户编码(团队编码)为 hamm,则完成了子域名的自定义,通过 即可访问。

另外有一家大企业,比较在意自己的品牌形象,需要绑定自己的域名,比如腾讯:

devops.tencent.com

他们不希望在域名上看到服务提供方 Coding 的字样,只出现自己的品牌域名等。

接下来,我们在 的 DNS服务 侧做一个解析:

CNAME: devops.tencent.com -> tencent.coding.net

这样,访问自己的 就可以访问到在 上的租户编码为 tencent 的SaaS服务了。

尴尬的是,腾讯收购了Coding...

那么,这些需求都是怎么实现的呢?

二、架构设计2.1 站点配置设计

首先,绑定子域名的实现方式,我们可以使用 Nginx 的虚拟站点通配符设计来完成:

server {
  # SaaS业务前端站点
  server_name  *.coding.net coding.net;
}
service {
  # 后端API服务,通过header获取租户编码
  server_name api.coding.net;
}

*. 以及 的域名都可以访问到我们的前端服务了。

2.1.1 如果请求的域名是 *.

我们只需要取出域名中的团队编码,即可完成租户编码的获取,如果从域名中没有获取到租户编码信息:

2.1.2 如果请求的域名是

则通过解析 CNAME 记录,如果依然获取不到,则通过传统的 列表选择、用户输入 等方式来获取租户编码后,使用 header 携带请求后端的 API服务 即可。

2.1.3 如果是其他域名

可能是用户绑定的独立域名,可通过获取 CNAME 解析记录后,重新走一遍 2.1.1 2.1.2 的判断后,再判断是否绑定即可获取到租户编码或是抛出异常。

2.2 独立域名如何实现2.2.1 前置条件

使用独立域名绑定需要有一些前提:

2.2.2 什么是 CNAME 解析?

CNAME 解析,也叫 别名解析,是指将一个域名指向另一个域名,通俗点说:

是腾讯的域名(下面简称 腾讯子域名 ), 是 Coding 的域名 (下面简称 Coding子域名 ),

腾讯子域名 是 Coding子域名 的别名,浏览器端将保持显示 腾讯子域名,但实际解析到的 IP 是 Coding子域名 的,服务也是由 Coding子域名 来提供的。

当然,直接使用 A 解析也是可以的,但可能还要去做默认站点配置,如果后期服务更改服务器网关IP,第三方也要同步解析,比较麻烦,国际上不推荐。

有了上面的步骤,我们可以着手下一步了。

三、流程设计图

开始实现前,我们先来画好流程设计图:

flowchart TD
    A[获取当前请求的域名] --> B{*.coding.net ?}
    B -->|是 *.coding.net| C[取出子域名 *]

域名绑定_域名绑定服务器教程_

B -.->|不是 *.coding.net| D{coding.net ?} D -.->|不是 coding.net| F[获取CNAME解析目标] D -->|是 coding.net| E[进入 coding.net 主站] F -.-> |肯定解析了| C[取出子域名 *] C -.-> H{是否绑定 ?} C -->J[进入租户编码 * 的系统] H -.-> |已绑定| J[进入租户编码 * 的系统] H --> |未绑定| I[显示未绑定错误]

四、实现代码

首先,我们可能需要引入一些 DNS查询的库.

private static final MAIN_DOMAIN = "coding.net";
public String getTenantCode(String domain){
  if(Objects.isNull(domain)){
    throw new BizException("获取租户编码失败,域名不能为空")
  }
  final String subDomain = "." + MAIN_DOMAIN
  // 如果是子域名
  if(domain.endsWith(subDomain)){
    // 替换掉后面的部分,取出子域名前缀
    return domain.replace(subDomain, "");
  }
  // 如果是主域名
  if(domain.equals(MAIN_DOMAIN)){
    throw new BizException("获取租户编码失败,传入域名不包含编码");
  }
  // 如果是其他域名 获取 **CNAME** 解析后递归
  final String cname = getCname(domain);
  final String tenantCode = getTenantCode(cname);
  Tenant tenant = tenantService.getByCode(tenantCode);
  if(Objects.isNull(tenant)){
    throw new BizException("无效的租户编码");
  }
  if(!tenant.getDomain().equals(domain)){
    // 管理员未在后台配置绑定的独立域名或配置的不一致
    throw new BizException("域名和租户未绑定");
  }
  return tenantCode;
}

OK,通过上面的代码即可完成域名的识别,判断绑定关系后即可进入对应租户编码的服务了。

获取 CNAME 解析的方式有很多,比如调用 NS 服务器的 API,或者是通过本地命令的 nslookup dig 等,都可以获取到指定域名的解析目标。

五、总结与思考

今天我们通过 CNAME 解析来完成了 独立域名绑定 的功能设计,通过子域名取租户编码的方式完成了 域名级租户隔离方案。

你们的 SaaS项目 是如何做 域名绑定 设计的呢?欢迎在评论区讨论~

That's all. Bye~