【问题标题】:Resources, scopes, permissions and policies in keycloakkeycloak 中的资源、范围、权限和策略
【发布时间】:2017-06-30 09:42:27
【问题描述】:

我想使用 Keycloak 的授权系统创建一个相当简单的基于角色的访问控制系统。 Keycloak 正在取代的系统允许我们创建一个“用户”,他是一个或多个“组”的成员。在这个遗留系统中,用户被授予“权限”来访问大约 250 个“能力”中的每一个,或者通过组成员身份(向组分配权限)或直接授予用户权限。

我想将旧系统映射到 keycloak 授权。

将现有系统中的每个“功能”映射到一个 keycloak 资源和一组 keycloak 范围对我来说应该很简单。例如,“viewAccount”功能显然会映射到“account”资源和“view”范围;并且“viewTransaction”映射到“事务”资源......但是最好的做法是只创建一个“视图”范围,并在多个资源(帐户、事务等)中使用它?还是应该创建“viewAccount”范围、“viewTransaction”范围等?

同样,我对权限有点困惑。对于资源和范围的每个实际组合,通常的做法是创建权限吗?如果有多个权限匹配给定的资源/范围,Keycloak 会做什么?我猜 Keycloak 的目的是允许我配置针对资源和范围的权限矩阵,因此例如我可以有权访问“帐户”和“查看”范围的权限,因此我将有权限查看帐户?

我问是因为这一切的结果似乎是我的旧“viewAccount”功能最终创建了一个“Account”资源,具有“View”范围和“viewAccount”权限,这似乎让我回到了哪里我曾是。没关系,如果它是正确的。

最后,显然我需要一组策略来确定是否应应用 viewAccount。但我是否正确,这意味着我需要为用户可能属于的每个旧组制定策略?例如,如果我有一个“帮助台”角色,那么我需要一个“帮助台成员”策略,然后我可以将其添加到“viewAccount”权限中。这是正确的吗?

谢谢,

标记

【问题讨论】:

  • Keycloak 看起来是一个非常成熟且功能强大的系统,但它究竟能做什么仍然是个谜,因为似乎有很多问题和很少的答案。我实际上是在问自己帖子中的所有问题,却找不到任何答案。为什么没有好的教程?真的没有人用这个东西吗?还是没人愿意写?​​span>
  • Keycloak 在生产中(到目前为止)非常适合我们,但授权除外,这真的很难与我的实际问题联系起来。但我同意,有很多关于 Keycloak 如何进行 OIDC 的文档,但也有一个普遍的假设,即我们知道 OAuth 和 OIDC。如果您还不了解 OIDC,很难将 Keycloak 与应用程序问题联系起来,但对我来说,Keycloak 是 OIDC 的介绍,这有点让人捉摸不透。(Picketlink/Picketbox 更糟糕!)。我发现下载它并只是玩它是最好的。
  • 同意这些 cmets,keycloak 文档和用例很烂
  • Keycloak 开发者,请注意这个问题!您的文档非常好,但需要更多教程来解决此处提出的问题。您还可以考虑从旧式邮件列表迁移到对用户更友好的东西,例如论坛或只是 Stackoverflow。
  • 迟到的答案,但你所有的假设基本上都是正确的。至于什么是最佳实践,我认为很难说,因为能力是非常新的。不确定现在是否连 kc 开发人员都知道最佳实践。

标签: permissions keycloak


【解决方案1】:

我知道我迟到了 2 年多,但我想我会分享我所知道的,并希望为未来的读者减轻一些痛苦。完全透明——我绝不是 Keycloak/OAuth/OIDC 专家,我所知道的主要来自阅读文档、书籍、优秀的 YouTube 和使用该工具。

这篇文章将由两部分组成:

  1. 我会尽力回答您的所有问题
  2. 我将向您展示如何在 Keycloak 中使用策略/范围/权限,而无需部署单独的应用程序,以便更好地理解此线程中的一些核心概念。请注意,这主要是为了让您开始。我正在使用Keycloak 8.0.0

第一部分

开始之前的一些术语:

  • 在 Keycloak 中,您可以创建两种权限:Resource-BasedScope-Based
  • 简单地说,对于Resource-Based 权限,您直接将其应用于您的资源
  • 对于Scoped-Based 权限,您将其应用于您的范围或范围资源。

最好的做法是只创建一个“视图”范围,并在多个资源(帐户、事务等)中使用它?还是应该创建“viewAccount”范围、“viewTransaction”范围等?

范围表示受保护资源的一组权限。在您的情况下,您有 2 个资源:accounttransaction,所以我倾向于第二种方法。

从长远来看,拥有与所有资源相关联的全局 view 范围(例如 accounttransactioncustomersettlement...)会使授权难以管理和适应安全要求的变化。

这里有一些示例,您可以查看以了解设计

请注意 - 我并不是说您不应该跨资源共享范围。事实上,Keycloak 允许对具有相同type 的资源执行此操作。例如,您可能需要viewAccountviewTransaction 范围来读取给定帐户下的交易(毕竟您可能需要访问该帐户才能查看交易)。您的要求和标准将严重影响您的设计。

对于资源和范围的每个实际组合,创建权限是通常的做法吗?

抱歉,我不完全理解这个问题,所以我会有点宽泛。要授予/拒绝对 resource 的访问权限,您需要:

  • 定义你的policies
  • 定义你的permissions
  • 将您的策略​​应用于您的权限
  • 将您的权限与scoperesource(或两者)相关联

让政策执行生效。见Authorization Process

如何设置这一切完全取决于您。例如,您可以:

  • 定义单独的策略,并在适当的权限下绑定每个策略。

  • 更好的是,定义单个策略,然后将所有相关策略分组到 aggregated 策略(策略策略)下,然后将该聚合策略与 scope-based 权限相关联。您可以将 scoped-based 权限同时应用于资源及其所有关联范围。

  • 或者,您可以通过利用这两种不同的类型来进一步分解您的权限。您可以通过resource-based 权限类型为您的资源单独创建权限,并通过scope-based 权限类型单独将其他权限与范围单独关联。

你有选择。

如果有多个权限匹配给定的资源/范围,Keycloak 会做什么?

这取决于

  1. 资源服务器的Decision Strategy
  2. 每个权限的Decision Strategy
  3. 每个策略的Logic 值。

Logic 值类似于 Java 的 ! 运算符。它可以是PositiveNegative。当LogicPositive 时,策略的最终评估保持不变。当它的Negative 时,最终结果被否定(例如,如果一个策略评估为假并且它的LogicNegative,那么它将是true)。为简单起见,我们假设Logic 始终设置为Positive

Decision Strategy 是我们真正想要解决的问题。 Decision Strategy 可以是 UnanimousAffirmative。从文档中,

决策策略

此配置更改了策略评估引擎根据所有评估权限的结果决定是否应授予资源或范围的方式。 肯定 意味着必须至少将一项权限评估为积极的决定,才能授予对资源及其范围的访问权限。 一致表示所有权限都必须评估为积极的决定,才能使最终决定也是积极的。例如,如果同一资源或范围的两个权限发生冲突(其中一个是授予访问权限,另一个是拒绝访问),如果选择的策略是肯定的,则将授予对该资源或范围的权限。否则,任何权限的单个拒绝也将拒绝对资源或范围的访问。

让我们用一个例子来更好地理解上面的内容。假设您有一个具有 2 个权限的资源,并且有人试图访问该资源(请记住,对于所有策略,LogicPositive)。现在:

  1. Permission One 有一个 Decision Strategy 设置为 Affirmative。它还有 3 个策略,每个策略都评估为:
    • true
    • false
    • false

由于其中一项政策设置为true,因此Permission One 设置为true(肯定 - 只需 1 项为 true)。

  1. Permission Two 有一个 Decision Strategy 设置为 Unanimous,有 2 个策略:
    • true
    • false

在这种情况下,Permission Twofalse,因为一个策略是错误的(一致 - 它们都需要是 true)。

  1. 现在是最终评估。如果资源服务器的Decision Strategy 设置为Affirmative,则将授予对该资源的访问权限,因为Permission Onetrue。另一方面,如果资源服务器的Decision Strategy 设置为Unanimous,则访问将被拒绝。

见:

我们将继续重新审视这一点。我在第二部分解释了如何设置资源服务器的Decision Strategy

例如,我可以有权访问“帐户”和“查看”范围的权限,因此我将有权查看帐户吗?

简短的回答是肯定的。现在,让我们稍微扩展一下:)

如果你有以下情况:

  1. 资源服务器的Decision Strategy 设置为UnanimousAffirmative
  2. 访问account/{id}资源的权限是true
  3. 访问view 范围的权限是true

您将被授予查看该帐户的权限。

  • true + true 等于trueAffirmativeUnanimous Decision Strategy 下。

如果你有这个

  1. 资源服务器的Decision Strategy 设置为Affirmative
  2. 访问account/{id}资源的权限是true
  3. 访问view范围的权限是false

您将被授予查看帐户的权限。

  • true + falsetrueAffirmative 策略下。

这里的重点是,对给定资源的访问还取决于您的设置,因此请小心,因为您可能不希望出现第二种情况。

但这是否意味着我需要为用户可能属于的每个旧组制定策略?

我不确定 Keycloak 在 2 年前的表现如何,但您可以指定 Group-Based policy 并简单地将所有组添加到该策略下。您当然不需要为每个组创建一个策略。

例如,如果我有一个“帮助台”角色,那么我需要一个“帮助台成员”策略,然后我可以将其添加到“viewAccount”权限。这是正确的吗?

差不多。您可以通过多种方式进行设置。例如,您可以:

  1. 创建您的资源(例如/account/{id})并将其与account:view 范围相关联。
  2. 创建一个Role-Based Policy 并在该策略下添加helpdesk 角色
  3. 创建一个名为viewAccountScope-Based 权限并将其与scoperesourcepolicy 绑定

我们将在第二部分设置类似的东西。

第二部分

Keycloak 有一个简洁的小工具,可让您测试所有策略。更好的是,您实际上不需要启动另一个应用程序服务器并部署一个单独的应用程序来工作。

这是我们将设置的场景:

  1. 我们将创建一个名为stackoverflow-demo 的新领域
  2. 我们将在该领域下创建一个bank-api 客户端
  3. 我们将为该客户端定义一个名为/account/{id} 的资源
  4. account/{id} 将具有 account:view 范围
  5. 我们将在新领域下创建一个名为bob 的用户
  6. 我们还将创建三个角色:bank_telleraccount_owneruser
    • 我们不会将bob 与任何角色相关联。现在不需要这个。
  7. 我们将设置以下两个Role-Based 策略:
    • bank_telleraccount_owner 可以访问 /account/{id} 资源
    • account_owner 可以访问 account:view 范围
    • user 无权访问资源或范围
  8. 我们将使用Evaluate 工具来了解如何授予访问权限或 否认。

请原谅我,这个例子不切实际,但我对银行业不熟悉:)

Keycloak 设置

下载并运行 Keycloak

cd tmp
wget https://downloads.jboss.org/keycloak/8.0.0/keycloak-8.0.0.zip 
unzip keycloak-8.0.0.zip
cd keycloak-8.0.0/bin
./standalone.sh 

创建初始管理员用户

  1. 转到http://localhost:8080/auth
  2. 点击Administration Console链接
  3. 创建管理员用户并登录

访问Getting Started 了解更多信息。对于我们的目的,以上内容就足够了。

搭建舞台

创建一个新领域

  1. 将鼠标悬停在master 领域并单击Add Realm 按钮。
  2. 输入stackoverflow-demo 作为名称。
  3. 点击Create
  4. 左上角现在应该显示stackoverflow-demo 而不是master 领域。

Creating a New Realm

创建一个新用户

  1. 点击左侧的Users链接
  2. 点击Add User按钮
  3. 输入username(例如bob
  4. 确保User Enabled 已打开
  5. 点击Save

Creating a New User

创建新角色

  1. 点击Roles链接
  2. 点击Add Role
  3. 添加以下角色:bank_telleraccount_owneruser

同样,不要将您的用户与角色相关联。出于我们的目的,这不是必需的。

Roles

创建客户端

  1. 点击Clients链接
  2. 点击Create
  3. 输入bank-api 作为Client ID
  4. 对于Root URL,输入http://127.0.0.1:8080/bank-api
  5. 点击Save
  6. 确保Client Protocolopenid-connect
  7. Access Type改成confidential
  8. Authorization Enabled 更改为On
  9. 向下滚动并点击Save。一个新的Authorization 选项卡应出现在顶部。
  10. 点击Authorization 标签,然后点击Settings
  11. 确保将Decision Strategy 设置为Unanimous
    • 这是资源服务器的Decision Strategy

见:

创建自定义范围

  1. 点击Authorization标签
  2. 点击Authorization Scopes>Create调出Add Scope页面
  3. 在名称中输入account:view,然后按回车键。

创建“查看账户资源”

  1. 点击上方Authorization链接
  2. 点击Resources
  3. 点击Create
  4. NameDisplay name 输入View Account Resource
  5. URI 输入account/{id}
  6. Scopes 文本框中输入account:view
  7. 点击Save

Creating Resources

制定您的政策

  1. 再次在Authorization 标签下,点击Policies
  2. Create Policy 下拉列表中选择Role
  3. Name 部分,输入Only Bank Teller and Account Owner Policy
  4. Realm Roles 下选择bank_telleraccount_owner 角色
  5. 确保将Logic 设置为Positive
  6. 点击Save
  7. 点击Policies链接
  8. 再次从Create Policy 下拉列表中选择Role
  9. 这次将Only Account Owner Policy 用于Name
  10. Realm Roles 下选择account_owner
  11. 确保将Logic 设置为Positive
  12. 点击Save
  13. 单击顶部的Policies 链接,您现在应该会看到新创建的策略。

Role-Based Policy

请注意,Keycloak 具有更强大的策略。见Managing Policies

创建基于资源的权限

  1. 再次在Authorization 选项卡下,单击Permissions
  2. 选择Resource-Based
  3. Name 键入View Account Resource Permission
  4. Resources 下输入View Account Resource Permission
  5. Apply Policy 下选择Only Bank Teller and Account Owner Policy
  6. 确保将Decision Strategy 设置为Unanimous
  7. 点击Save

Create Resource-Based Permissions

呼……

评估基于资源的权限

  1. 再次在Authorization 选项卡下,选择Evaluate
  2. User 下输入bob
  3. Roles 下选择user
    • 这是我们将我们的用户与我们创建的角色相关联的地方。
  4. Resources 下选择View Account Resource 并单击Add
  5. 单击评估。
  6. 展开View Account Resource with scopes [account:view] 以查看结果,您应该会看到DENY

  1. 这是有道理的,因为我们只允许两个角色通过Only Bank Teller and Account Owner Policy 访问该资源。让我们测试一下以确保这是真的!
  2. 点击评估结果正上方的Back链接
  3. 将 bob 的角色更改为 account_owner 并单击 Evaluate。您现在应该看到结果为PERMIT。如果您返回并将角色更改为bank_teller,则同样的交易

Evaluating and Testing Policies

创建基于范围的权限

  1. 返回Permissions部分
  2. 这次在Create Permission 下拉菜单下选择Scope-Based
  3. Name 下,输入View Account Scope Permission
  4. Scopes 下,输入account:view
  5. Apply Policy 下,输入Only Account Owner Policy
  6. 确保将Decision Strategy 设置为Unanimous
  7. 点击Save

Creating Scope-Based Permissions

第二次试运行

评估我们的新变化

  1. 返回Authorization部分
  2. 点击Evaluate
  3. 用户应该是bob
  4. 角色应该是bank_teller
  5. 资源应为View Account Resource 并单击Add
  6. 点击Evaluate,我们应该得到DENY
    • 这也不足为奇,因为bank_teller 可以访问resource,但不能访问scope。这里一个权限评估为真,另一个为假。鉴于资源服务器的Decision Strategy设置为Unanimous,最终决定为DENY
  7. 单击Authorization 选项卡下的Settings,将Decision Strategy 更改为Affirmative,然后再次返回步骤1-6。这一次,最终结果应该是PERMIT(一个权限是真的,所以最终决定是真的)。
  8. 为了完整起见,将资源服务器的Decision Strategy 转回Unanimous。再次返回步骤 1 到 6,但这次将角色设置为 account_owner。这一次,最终结果还是PERMIT,这是有道理的,因为account_owner 可以同时访问resourcescope

整洁 :) 希望这会有所帮助。

【讨论】:

  • @SANDEEPMACHIRAJU 不客气 :) 好问题!没有足够的字符通过评论给出深入的答案,但您可以使用token introspection endpoint。这是all their endpoints 的列表。我认为您也可以使用他们的Authorization Client,但我没有任何使用经验
  • @JWo 不客气! Tbh,没有特别的原因。我只是让示例尽可能简单,以便让任何人开始。我从经验中知道,文档并不是最令人兴奋的阅读内容。至于另一个需要访问银行 API 的客户端,听起来您可能正在寻找 User Management Access (UMA),其中资源所有者可以向请求方授予对受保护资源的访问权限(至少这是我的解释)。
  • 您确实让世界变得更美好,感谢您花时间将所有这些放在一起...您应该写一些关于所有这些的博客文章!非常有用@Andy
  • @Andy 哇!感谢您花时间消化所有 Auth Services 文档并与我们分享。你为很多人节省了大量的学习时间!只有一件事我看不到。你说account_owner has access to the account:view scope。您如何建立该角色与该范围之间的关系?
  • @codependent 我的荣幸 :) 事后看来,我应该措辞更好。最简单的方法是按照设置进行操作,但总而言之,在关联角色和策略后,您需要创建基于范围的权限,然后将策略应用于该权限。在上面的示例中,您拥有与account_owner 角色相关联的Only Account Owner Policy。然后将该策略应用于View Account Scope Permission。在我们创建该权限时,我们将其与account:view 范围相关联。希望这会有所帮助!
【解决方案2】:

我希望通过纯 HTTP 方法强制授权,而不使用适配器,因为 Lua 没有适配器。希望这个答案可以帮助人们寻找基于非适配器的方法。

如果您正在寻找适配器,quick start guide 是最好的起点。尤其是spring boot authz example

对于纯基于 HTTP 的实现:

第 1 步:

Keycloak Admin UI中定义策略和权限

第 2 步

对哪些 HTTP 路径属于哪些资源以及每个路径所需的范围有一个内部映射。这也可以保存在configuration file 中。调用特定路由时,调用 Keycloak 令牌端点以验证访问令牌的声明。

{
  "policy-enforcer": {
    "user-managed-access" : {},
    "enforcement-mode" : "ENFORCING"
    "paths": [
      {
        "path" : "/someUri/*",
        "methods" : [
          {
            "method": "GET",
            "scopes" : ["urn:app.com:scopes:view"]
          },
          {
            "method": "POST",
            "scopes" : ["urn:app.com:scopes:create"]
          }
        ]
      }
    ]
  }
}

如果您使用适配器并且未指定路径或资源,则适配器将在内部搜索路径和资源from Keycloak

第 3 步:

使用令牌端点来get or evaluate 权限。您可以使用response_mode 参数获得最终决定(是否提供访问权限)或检索整个权限。

curl -X POST \
  http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \
  -H "Authorization: Bearer ${access_token}" \
  --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
  --data "permission=Resource A#Scope A"

如果授权请求没有映射到任何权限,则返回403 HTTP 状态码。

【讨论】:

    【解决方案3】:

    我知道我参加聚会有点晚了,但让我尽可能多地解释一下。

    在 keycloak 中,我们有如下术语:

    资源:用户将访问或执行操作的对象

    授权范围:用户可以对特定对象执行的操作

    政策:政策

    权限:映射实际上发生在这里

    如果您不想遵循手动方式,您可以导出此 JSON,并且所有用户、资源、权限将自动设置 bu keycloak

    JSON configuration file

    现在让我们看一个场景:

    现在我们的资源很少:

    1. 帐户
    2. 机器人
    3. 报告

    我们希望实现只有特定用户才能执行特定操作的场景。

    设置 Keycloak

    创建一个新领域

    1. 点击添加领域按钮。

    1. 输入 test-v1 作为名称。
    2. 点击创建。

    创建新角色

    1. 点击角色
    2. 点击添加角色
    3. 创建角色“admin”、“agent”和“super_admin”

    创建客户端

    • 点击客户标签
    • 在客户端 ID 文本框中输入 app-client
    • 点击保存
    • 再次选择客户端以配置其他设置
    • 验证客户端协议是 openid-connect
    • 将访问类型设置为机密
    • 将授权设置为开启
    • 最后点击保存按钮。
    • 一个新的授权标签将出现在顶部。
    • 选择“授权”选项卡,然后选择“设置”

    检查决策策略是否设置为一致。这是资源服务器策略

    创建自定义身份验证范围

    转到授权标签 选择授权范围 >并点击创建 在文本中输入 scopes:create & scopes:view 并保存值。

    创建资源

    • 现在转到资源标签>并点击创建
    • 一一输入,创建以下资源res:account & res:bot & res:report
    • 对于范围文本中的所有资源,选择我们早期创建的两个范围 scopes:createscopes:view
    • 点击保存

    制定政策

    • 再次在“授权”选项卡中,选择 Policies
    • 点击创建策略下拉菜单并选择角色
    • 在名称文本框中,管理员
    • 在领域角色中选择角色 Admin
    • 检查逻辑设置为正
    • 点击保存,对“代理”和“超级管理员”执行相同操作

    • 再次在“授权”选项卡中,选择 Policies
    • 点击Create Policy下拉菜单并选择Aggregated
    • 在名称文本框中,Admin 或 Super_admin 或 Agent
    • 在领域角色中选择角色 Admin & Super_admin & Agent
    • 检查逻辑设置为肯定
    • 点击保存

    创建权限

    • 再次在“授权”选项卡中,选择权限
    • 点击创建权限下拉菜单并选择Scope-Based
    • 在名称文本框中,account-create
    • 在资源框中,选择“resource res:account”
    • 在范围选择中,范围:创建
    • 应用政策管理员
    • 我们必须按照要求为所有资源设置相同的权限

    创建用户

    • 在用户选项卡中创建一个 test 用户 我们不会为其分配任何角色、范围或组进行测试

    让我们评估

    • 再次在“授权”选项卡中,选择评估
    • 选择我们创建的客户端,app-client
    • 在用户中选择创建的用户,测试
    • 在角色中选择创建的用户,admin
    • 资源价值,res:account
    • 点击添加按钮
    • 点击评估按钮

    您会看到授权是permitted,因为Admin 角色有权对资源执行createview 操作帐户

    让我们再次评估

    • 再次在“授权”选项卡中,选择评估
    • 选择我们创建的客户端,app-client
    • 在用户中选择创建的用户,测试
    • 在角色中选择创建的用户,admin
    • 资源价值,res:report
    • 范围值,范围:创建
    • 点击添加按钮
    • 点击评估按钮

    您将看到授权被 拒绝,因为只有 Super_Admin 角色有权对资源 report 执行操作 create .

    【讨论】:

    • 非常感谢它有帮助!我有一个问题,如果你不介意可以使用一个 API,我会将它添加到 keycloak 的代码源中以将我的应用程序连接到它,或者我只需要使用识别提供者
    • 很高兴听到这个消息,抱歉没有收到您的问题。但是,请随时添加更多详细信息。仍在尝试回答:您的应用程序将通过 JSON 文件配置连接到 keycloak,例如:github.com/v-ladynev/keycloak-nodejs-example/blob/master/… 这整个示例都在 Node js 中,您可以检查一次非常好
    • 嗨@HarshManvar,它适用于微服务吗?我已经用两页(A 和 B)尝试了您的建议。 A&B 是两个服务,使用 keycloak 和 OAuth 进行身份验证。我在Validate Redirect URLs 中添加了它们,但似乎不起作用
    • @HarshManvar 你能看看我的 Keycloak 相关问题stackoverflow.com/q/70376766/2886891 吗?谢谢。
    【解决方案4】:

    可以在此处找到具有资源、范围和权限的有效解决方案 keycloak-nodejs-example

    只需使用 docker-compose 运行已配置的 Keycloak,使用 Quick Start guide

    项目中其他有用的例子

    • 不使用 Keycloak 登录页面的自定义登录。
    • 不使用会话的无状态 Node.js 服务器。 Keycloak 令牌使用 cookie 存储。
    • 用于检查权限的集中式中间件。未明确描述的路线无法访问。
    • 没有 keycloak.json 的配置。它可用于为多个环境进行配置。例如——DEV、QA。
    • 使用 Keycloak REST API 创建用户、角色和自定义属性的示例。

    【讨论】:

      猜你喜欢
      • 2021-12-31
      • 2018-07-21
      • 2020-04-11
      • 2017-03-06
      • 2021-02-05
      • 2018-11-02
      • 2020-10-08
      • 1970-01-01
      • 2021-09-18
      相关资源
      最近更新 更多