RESTful 访问权限管理实现思路,采用路径匹配神器之 AntPathMatcher

f328573344b917f855230a77d7d74a36.png

经常在写程序时需要对路径进行匹配,比如说:资源的拦截与加载、RESTful访问控制、审计日志采集、等,伟大的SpringMVC在匹配Controller路径时是如何实现的?全都归功于ant匹配规则。

Spring源码之AntPathMatcher,这个工具类匹配很强大,采用的是ant匹配规则。

什么是ant匹配规则?

字符wildcard描述
?匹配一个字符(matches one character)
*匹配0个及以上字符(matches zero or more characters )
**匹配0个及以上目录directories(matches zero or more ‘directories’ in a path )

这个匹配规则很简单,采用简洁明了的方式来进行匹配解析,简化版本的正则。

结合官方的示例来理解一下

Pattern匹配说明
com/t?st.jsp匹配: com/test.jsp , com/tast.jsp , com/txst.jsp
com/*.jsp匹配: com文件夹下的全部.jsp文件
com/**/test.jsp匹配: com文件夹和子文件夹下的全部.jsp文件
org/springframework/*/.jsp匹配: org/springframework文件夹和子文件夹下的全部.jsp文件
org/**/servlet/bla.jsp匹配: org/springframework/servlet/bla.jsp , org/springframework/testing/servlet/bla.jsp , org/servlet/bla.jsp

如何实现RESTful访问权限管理?

在微服务和前后端分离的开发模式下,往往会使用RESTful来开发后端服务,那服务的访问权限控制就是一个问题,那下来我们就说一下如何实现RESTful访问权限管理。

权限资源类型

资源分为如下两种类型:

  • public(公有):public为不控制访问的资源

  • private(私有):private为需要被控制访问的资源

ps.这种方式资源管理的相对严格一些,如果想管理的粗犷一些,可以不需要public,只要在private中未找到的资源就是不控制访问的资源即可,实现时可以根据自己的业务场景来调整。

匹配原则

基础匹配规则:使用ant匹配规则

SpringMVC的路径匹配原则中有一个原则是:最长匹配原则(has more characters)

什么是最长匹配原则(has more characters)?

最长匹配原则(has more characters)简单的理解就是目标URL有多个pattern都可以匹配上就取最长的那个pattern

例如:请求的URL/app/dir/file.jsp,有两个pattern /**/*.jsp/app/dir/*.jsp都可以匹配成功,那么会根据pattern的长度来控制是否采用哪一个,这里使用/app/dir/*.jsp来匹配。

为什么要使用最长匹配原则?我的理解是长的pattern更符合目标URL格式,短的pattern往往是范围较广的,匹配取最适合的pattern也是比较符合预期的。

根据服务名分类

在做资源访问权限时往往会有多个服务可能会出现相同的资源路径,因此增加一级服务名来对资源进行分类。

例如:GET /v1/service1/product/1 和 GET /v1/service2/product/1,根据二级目录service名称来对服务进行模块化分割。/v1为RESTful版本号

ps.服务名就是为了做资源分类

权限验证逻辑

  • 验证public资源

    • 去除末尾"/"

    • 验证service服务名,服务名为空返回没有权限

    • 获取服务名下enabled=true的资源表,结果进行cache,结果为空没有权限

    • 根据pattern长度倒序

    • 匹配method,匹配成功进行下一步匹配

    • 匹配请求的url,匹配成功返回有权限,反之返回没有权限

  • 验证private资源

    • 去除末尾"/"

    • 验证service服务名,服务名为空返回没有权限

    • 获取服务名下用户角色对应的资源列表聚合结果,结果进行cache,结果为空返回没有权限

    • 根据pattern长度倒序

    • 匹配method,匹配成功进行下一步匹配,反之continue

    • 匹配请求的url,匹配成功进行下一步匹配,反之continue

    • 检查匹配成功的url是否为禁用状态,如果禁用返回无权限,反之进行下一步匹配

    • 匹配成功的url对应的角色列表进行登录用户的角色匹配

    • 角色匹配成功返回有权限,反之返回没有权限

ps.method是GETPOSTPUTPATCHDELETEservice是服务模块名

缓存结构

  • private资源数据

    • 结构:hash

    • cache key=${APPNAME}.METADATA.RESOURCEfield=${RESOURCE_ID}value=Resource对象

  • public资源数据

    • 结构:hash

    • cache key=${APPNAME}.METADATA.RESOURCE.PUBLICfield=${SERVICE}value=List<Resource>

  • 用户关联角色数据

    • 结构:hash

    • cache key=${APPNAME}.METADATA.ROLEfield=${USER_ID}value=List<ROLE_ID>

  • 角色关联的资源数据

    • 结构:hash

    • cache key=${APPNAME}.METADATA.MAPPINGfield=${SERVICE}value=List<Metadata<Resource,List<ROLE_ID>>>

    • 这里存储的数据结构是反向的,获取服务下的资源列表,每个资源数据中会有拥有这个资源的角色列表。

ps.缓存可以使用分布式的redisredisson、如果单机可以使用jvm cache

缓存控制

  • private资源数据发生变更时

    • 调用MetadataCache.invalidResources(),失效cache key=${APPNAME}.METADATA.RESOURCE下所有数据

  • public资源数据发生变更时

    • 调用MetadataCache.invalidPublicResource(service)失效服务名下的public资源集合,失效cache key=${APPNAME}.METADATA.RESOURCE.PUBLIC下的某个${SERVICE}数据

    • 调用MetadataCache.invalidPublicResource()失效所有服务名下的public资源集合,失效cache key=${APPNAME}.METADATA.RESOURCE.PUBLIC下所有数据

  • 用户关联角色数据发生变更时

    • 调用MetadataCache.invalidUserRoles(userId)失效用户下的角色集合,失效cache key=${APPNAME}.METADATA.ROLE下所有数据

  • 角色关联的资源数据发生变更时

    • 调用MetadataCache.invalidMetadata(service)失效服务名下的资源角色聚合对象,失效cache key=${APPNAME}.METADATA.MAPPING下的某个${SERVICE}数据

    • 调用MetadataCache.invalidMetadata()失效所有服务名下的资源角色聚合对象,失效cache key=${APPNAME}.METADATA.MAPPING下所有数据

ps.在以上触发点上对缓存数据进行更新,这里采用失效再加载方式

缓存加载

  • private资源数据,在系统启动加载,加载所有私有资源,如果失效了,会在private匹配的时再进行加载

  • public资源数据,在public匹配时加载,通过服务名加载,如果失效了,会在public匹配时再进行加载

  • 用户关联角色数据,在private匹配时加载,如果失效了,会在private匹配时再进行加载

  • 角色关联的资源数据,在private匹配时加载,如果失效了,会在private匹配时再进行加载

ps.资源数据加载触发点

pattern配置建议

  • 配置资源时,将不需要配置权限的url配置为public资源

  • 每个服务名下建议配置一个**(双星)通配符给超级管理员使用,例如:/v1/products/**

  • 每个url的第二级目录要与服务名一致,例如:/v1/products/{pid},服务名为products

  • url的目录结构必须大于两级目录,例如:/v1/products/{pid},不允许为:/v1/{pid}

  • url与权限通配符映射关系,前面url,后面pattern

    • 例如:/v1/products/{pid} -> /v1/products/*

    • 例如:/v1/products/{pid}/skus/{sid} -> /v1/products/*/skus/*

    • 例如:/v1/products/enabled -> /v1/products/enabled

    • 例如:/v1/products/**,匹配products目录下所有目录

以上就是一种RESTful资源管理的实现思路,能控制到RESTful的方法级别,在前后端分离的项目可以使用这种方式来控制访问权限。

source:  //ningyu1.github.io/site/post/62-ant-path-matcher

5c126bd09eb6b72211435e753fadf4c0.png

喜欢,在看

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值