说明:本文为阅读极客时间《数据结构与算法之美》专栏的笔记。
一、应用背景
如下图所示,有一个用户服务。它提供很多用户相关的接口,比如:获取用户信息、注册、登陆等等,但是并不是每个应用都有权限访问用户服务下的所有接口,那么就需要实现鉴权功能。
要实现接口鉴权功能,需要事先将应用对接口的访问权限规则设置好。当某个应用访问其中一个接口的时候,我们就可以拿应用的请求URL,在规则中进行匹配。如果匹配成功,就说明允许访问;如果没有可以匹配的规则,那就说明这个应用没有访问这个接口的权限,就拒绝服务。
二、实现方法
实际上,不同的规则和匹配模式对应的数据结构和匹配算法也是不一样的。下面将这个问题细分为三个更加详细的需求。
1、精确匹配规则
这是最简单的匹配模式,只有当请求URL和规则中配置的某个接口精确匹配时,这个请求才会被接受、处理。
不同的应用对应不同的规则集合。我们可以使用散列表来存储这种对应关系。这里着重讲下,每个应用对应的规则集合,该如何存储和匹配。
针对这种匹配模式,可以将每个应用对应的权限规则,存储在一个字符串数组中。当用户请求到来时,拿用户的请求URL和这个字符串数组中的规则逐一匹配(匹配算法:KMP、BM、BF)。
如果匹配的规则比较多,可以按照字符串大小给规则排序,把它们组成有序数组这种数据结构,当查找某个URL时,可以采用二分查找(O(logn)),在数组中进行匹配。
2、前缀匹配
只要某条规则可以匹配请求URL的前缀,我们就认为这条规则能够跟这个请求URL匹配。
不同的应用对应不同的规则集合,我们还是采用散列表来存储这种对应关系。那么每个应用的规则集合,我们使用Trie树这种数据结构。
不过,Trie树中的每个节点并不是存储每个字符,而是存储接口被“/”分割后的子目录(比如:/User/login 被分割为 User 和 login 两个子目录)。因为规则并不会经常变动,所以,在 Trie 树中,我们可以把每个节点的子结点组织成有序数组这种数据结构,这样就可以采用二分查找。
3、模糊匹配规则
有可能匹配规则比较复杂,包含通配符。比如:“**”表示匹配任意多个子目录,“*”表示匹配任意一个子目录。只要用户请求的URL可以跟某条规则模糊匹配,我们就认为这条规则适用于这个请求。
可以采用回溯算法,拿请求 URL 跟每条规则逐一进行模糊匹配。但是往往需要结合实际情况,实际上包含通配符的规则还是少数。于是,我们可以把不包含通配符的规则和包含通配符的规则分开处理。
把不包含通配符的规则组织成有序数组或者Tire树(视具体情况而定),这一部分匹配就会非常高效。剩下的是少数包含通配符的规则,我们只需要把它们简单存储在一个数组中就可以了。尽管匹配起来比较慢,但是毕竟这种规则比较少,所以是可以接受的。
当接收到一个请求URL之后,我们可以先在不包含通配符的有序数组或者Trie树中查找。如果能够匹配,就不需要继续在通配符规则中匹配了;如果不能匹配,就继续在通配符规则中查找匹配。
三、总结
本文讲述了三种不同的鉴权规则匹配模式。不管哪种匹配模式,我们都可以用散列表来存储不同应用对应的不同规则集合。对于每个应用的规则集合的存储,三种匹配模式适用不同的数据结构。
1、对于精确匹配模式:利用有序数组来存储每个应用的规则集合,并且通过二分查找和字符串匹配算法,来匹配请求URL与规则。
2、对于前缀匹配模式:利用Trie树来存储每个应用的规则集合。
3、对于模糊匹配模式:采用普通的数组来存储包含通配符的规则,通过回溯算法,来进行请求URL与规则的匹配。