在RESTful服务中,我是否应该拥有一个资源的多个视图/端点?

8
假设我正在创建一个RESTful服务,用于处理我仓库的订单。以下是相关需求:
  • 允许顾客创建账户
  • 顾客管理员可以为他们办公室的其他用户创建账户
  • 允许顾客用户创建订单
  • 网站管理员可以创建和管理所有顾客账户
  • 网站管理员可以创建和管理所有用户
  • 网站管理员可以创建和管理所有订单
鉴于这些要求,我的初步想法是按照以下方式设计端点。
# to request a new customer account
/customers/request {POST}
# create and view customers - limited to admins
/customers {GET, POST}
# view customer info, update a customer
/customers/{customer_id} {GET, PATCH}
# create and view orders for a customer
/customers/{customer_id}/orders {GET, POST}
# view and update order for a customer
/customers/{customer_id}/orders/{order_id} {GET, PATCH}

我相信这些路径是合理的,并且遵循了一般的RESTful思想。然而,我不确定如何处理用户端点。问题是,我希望客户管理员能够创建用户,这些用户可以使用他们的客户账户创建订单。客户管理员应该在哪里进行POST请求呢?我有一些想法。 根据这个答案,我考虑了以下内容。

# creation of users always done through this endpoint no matter what the
# authenticated user's role is
/users { GET, POST }
# associate user with customer
/customers/{customer_id}/user_memberships { GET, POST }

这种方法的问题在于客户账户管理员如何获取要与客户账户关联的用户ID。任何对 /users 的 GET 请求都将通过仅检索其客户帐户中的用户来进行过滤。但是,因为会员创建之前会创建用户,所以他们永远无法查看该用户。 我也考虑过只有两个端点来创建用户。
# create a user for a customer account
/customers/{customer_id}/users {GET, POST}
# root users endpoint only accessible to admins
/users {GET, POST}
# return same user
/users/1
/customers/{customer_id}/users/1

实质上就是使用客户URL前缀作为授权手段。两个终端点互相无效的做法有点奇怪。如果根终端点只是子资源终端点的视图,那该怎么办呢?

# view all users in system - admin only
/users {GET}
# create & view admin users
/admin/users {GET, POST}
# create internal office users
/locations/{location_id}/users { GET, POST }
# create customer users
/customers/{customer_id}/users { GET, POST }

在这种情况下,我们仍然可以缓存子资源的GET响应,因为它们不会改变,除非对特定id的子资源进行POST或PATCH/DELETE操作。
这种方式似乎也适用于订单。管理员可以查看所有订单,即使它们在技术上属于某个客户。
# admin can view all orders
/orders?customer_id=1234
/orders

我喜欢把根资源看作是子资源的视图,这样更容易基于URL进行授权,这个想法不错。
所以,我想问的真正问题是:
即使其中一个端点只是子资源的聚合视图,并且不允许通过该端点创建资源,拥有多个代表同一资源的端点是否会成为问题?

根据我的理解,例如您希望针对特定的客户ID,使该客户仅能查看其用户而无法创建自己的用户,这可以使用Spring Security来实现,但这肯定会带来问题,因此您需要根据要求对客户进行分类。 - Girish
2个回答

6
您不应混淆API设计、REST原则和授权需求。您应以易于使用、易于维护和易于理解的方式设计API。
RESTful API设计方法试图解决这些不同的问题。RESTful方法是关于识别您拥有的对象、它们的状态和可能的转换。
在此结束。现在,您想知道授权。您希望能够根据用户是谁(管理员、客户等)以及所针对的资源是什么(客户记录等)来控制用户在特定记录上的操作。
您需要做的是以松散耦合的方式在REST API之上部署授权框架。换句话说,您希望将授权外部化。您绝不希望直接构建授权到API中。想象一下,突然出现了新的授权规则/约束:您将不得不重新编码API。这样做会破坏所有客户端。这将导致用户体验不佳。
因此,我们已经确定了您需要外部化授权。很好。有哪些不同的方法可以做到这一点?这取决于您使用的语言和框架。
您可以使用:
  • Java中的Spring安全
  • PHP中的Yii
  • Ruby中的CanCan
  • ......等等

您还可以实现自己的过滤器,例如在REST端点前面使用Java中的Servlet过滤器。

最后,您可以转向基于XACML的完整属性授权模型。有几种开源和供应商替代品。如果您不熟悉基于属性的访问控制或XACML,请查看以下链接:

使用XACML,您可以集中定义策略,例如:

  • 管理员可以查看所有客户帐户
  • 管理员可以修改分配给他/她的客户帐户
  • 客户只能查看和编辑自己的帐户

然后,这些策略会在授权服务中进行评估(在XACML中称为策略决策点)。授权服务公开了二进制授权API,您的API可以调用:用户Alice是否可以查看记录foo?

使用基于策略的外部授权和使用XACML,您可以实现业务逻辑(您的业务API)与授权逻辑之间的松耦合,从而更轻松地维护和更新。


我听明白了你提到的授权与API设计分离的观点。不过,我并不是出于授权的目的在设计API。我的主要问题是这样的:你不能有一个不属于某个客户的订单(customers/cust_id/orders)。然而,我也想能够查询所有订单(/orders)。我主要担心两个端点潜在地涉及相同的资源。(/orders?cust_id=1234) === (/customers/1234/orders)。你认为这可能会成为一个问题吗? - chrislbs
我不认为这是个问题。如果你仔细想想,这就像是说你有一个API来管理订单和一个API来管理客户。事实上,你可以从订单中确定客户记录,就像你可以从客户记录中确定订单记录一样。 - David Brossard

0
根据我的理解,例如您希望针对特定的客户ID,使该客户仅能查看其用户而无法创建自己的用户(这些用户只能由管理员创建),因此可以使用Spring Security来实现。但是这肯定会带来问题,所以您需要根据自己的要求对客户进行分类。

1
考虑服务的三个角色:管理员、客户管理员和客户用户。管理员可以创建和查看任何内容。客户管理员可以创建其他用户以访问客户账户。客户用户可以代表客户账户执行操作。例如,A公司的所有者想要访问系统。他们向customers/request发送POST请求,获取客户账户并成为客户账户的管理员用户。然后,他可以通过向/customers/customer_id/users发送POST请求为X员工创建一个帐户,X员工可以通过向/customer/customer_id/orders发送POST请求创建订单。 - chrislbs
@chrislbs 是的,我的朋友,你可以通过使用Spring Security来为特定成员提供特定URL的访问权限,并且你也可以对它们应用约束。 - Girish
你的评论没有回答我的问题。我知道如何使用我的URL方案进行授权。我的问题是,是否拥有多种访问资源的方式会影响RESTful设计的质量。我添加了授权组件以澄清一个潜在的用例。 - chrislbs
@chrislbs 我的朋友,这不会影响你的RESTful设计,你可以将多个URI映射到单个资源,REST架构非常迅速地支持这种设计,但这种设计可能会降低你的代码性能。 - Girish
@chrislbs 你还在思考吗,还是已经明白我说的了? - Girish
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接