- 作者:老汪软件技巧
- 发表时间: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~