美文网首页
转:Keycloak授权服务指南(下)

转:Keycloak授权服务指南(下)

作者: 只弹棉花不谈是非 | 来源:发表于2020-06-29 17:01 被阅读0次

    转自:https://www.liangzl.com/get-article-detail-124061.html

    上一期:Keycloak授权服务指南(上)

    授权服务端点发现和元数据

    Keycloak提供了一个发现文档来帮助客户端获取与Keycloak授权服务交互所需的任何信息,包括端点位置和功能。

    可以从这里获取发现文档:

    curl -X GET \

      http://${host}:${port}/auth/realms/${realm}/.well-known/uma2-configuration

    请将上面占位符中的变量替换为实际的值。

    你收到的响应应该是如下类型:

    {

        // some claims are expected here

        // these are the main claims in the discovery document about Authorization Services endpoints location

        "token_endpoint": "http://${host}:${post}/auth/realms/${realm}/protocol/openid-connect/token",

        "token_introspection_endpoint": "http://${host}:${post}/auth/realms/${realm}/protocol/openid-connect/token/introspect",

        "resource_registration_endpoint": "http://${host}:${post}/auth/realms/${realm}/authz/protection/resource_set",

        "permission_endpoint": "http://${host}:${post}/auth/realms/${realm}/authz/protection/permission",

        "policy_endpoint": "http://${host}:${post}/auth/realms/${realm}/authz/protection/uma-policy"

    }

    每个端点都暴露了一组功能:

    token_endpoint

    遵循OAuth2的Token端点支持 urn:ietf:params:oauth:grant-type:uma-ticket 授权类型。客户端可以向此端点发送授权申请并获取Keycloak RPT。

    token_introspection_endpoint

    遵循Oauth2的Token检查端点。客户端可以使用该端点查询RPT的状态,并确定与token相关的任何其他信息,比如Keycloak授予的权限。

    resource_registration_endpoint

    遵循UMA协议的资源注册端点,资源服务器可以使用它来管理资源以及范围。此端点提供了资源、范围的创建、查询、更新以及删除等功能。

    permission_endpoint

    遵循UMA协议的权限管理端点,资源服务器可以用来管理权限tickets。提供了对permission ticket的创建、查询、更新以及删除等功能。

    获取权限

    要从Keycloak获取权限,需要向token端点发送授权请求。Keycloak会根据申请的资源以及范围来评估关联策略,最后发送携带权限的RPT。

    客户端发送的授权申请可以有下列参数:

    grant_type

    必填。必须是 urn:ietf:params:oauth:grant-type:uma-ticket。

    ticket

    可选。UMA授权过程中,客户端最近一次收到的权限ticket。

    claim_token

    可选。Keycloak评估授权时用到的附加的声明。它允许客户端推送声明到Keycloak。更多细节请查看token格式中的 cliam_token_format 参数。

    claim_token_format

    可选。用于说明claim_token参数的格式。Keycloak支持两种token格式:urn:ietf:params:oauth:token-type:jwt 以及https://openid.net/specs/openid-connect-core-1_0.html#IDToken。urn:ietf:params:oauth:token-type:jwt格式表示claim_token参数引用了一个access token。https://openid.net/specs/openid-connect-core-1_0.html#IDToken则表示claim_token参数引用了一个OpenID Connect ID Token。

    rpt

    可选。代表一个以前发放的RPT,其权限将被评估并添加到一个新的RPT中。此参数允许拥有RPT的客户端执行增量授权。

    permission

    可选。表示客户端正在寻求访问的一组资源以及范围。此参数可以重复定义,以请求针对多个资源和范围的权限。这个参数扩展了 urn:ietf:params:oauth:grant-type: uml -ticket 授权类型,以允许客户端在没有权限ticket的情况下发送授权请求。字符串的格式必须是:RESOURCE_ID#SCOPE_ID。例如:Resource A#Scope A, Resource A#Scope A, Scope B, Scope C, Resource A,#Scope A。

    audience

    可选。正在访问的资源服务器的客户端标识符。如果定义了permission参数,则必须填此参数。它用作Keycloak计算权限的上下文提示。

    response_include_resource_name

    可选。指示RPT的权限中是否包含资源名称。如果为false,则只包含资源ID。

    response_permissions_limit

    可选。一个整型的值N,用来表示RPT最多可携带的权限个数。和rpt参数一起使用,只有最近N次请求的权限会被保留在RPT中。

    submit_request

    可选。布尔型,用来指示服务器是否要创建权限ticket相关资源/范围的权限请求。此参数只有在UMA授权过程中与ticket参数一起使用时才有效。

    response_mode

    可选。指示服务器如何响应授权请求。可以让服务器仅返回决策结果或者权限列表而不是完整的标准OAuth2响应,此参数可选的值为:

    -decision

    指示服务器仅发送决策结果:  

    {

        'result': true

    }

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

    -permissions

    指示服务器返回权限列表:

    [

        {

            'rsid': 'My Resource'

            'scopes': ['view', 'update']

        },

        ...

    ]

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

    下面的例子展示了客户端申请访问资源服务器保护的两个资源:

    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 "audience={resource_server_client_id}" \

      --data "permission=Resource A#Scope A" \

      --data "permission=Resource B#Scope B"

    下面的例子展示了客户端请求访问受保护的任意资源和范围:

    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 "audience={resource_server_client_id}"

    下面的例子展示了客户端在从资源服务器接收到权限ticket后,继续请求受UMA保护的资源:

    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 "ticket=${permission_ticket}

    Keycloak会根据评估结果发放RPT:

    HTTP/1.1 200 OK

    Content-Type: application/json

    ...

    {

        "access_token": "${rpt}",

    }

    RPT可以从access_token响应参数中获取。如果客户端未通过授权评估,Keycloak将返回403 HTTP状态码:

    HTTP/1.1 403 Forbidden

    Content-Type: application/json

    ...

    {

        "error": "access_denied",

        "error_description": "request_denied"

    }

    客户端验证方式

    客户端需要身份验证才能获取RPT。当使用 urn:ietf:params:oauth:grant-type: uma-ticket 授权类型时,客户端可以使用以下任何一种身份验证方法:

    Bearer Token

    此时发送给token端点的请求必须带有Bearer的HTTP Authorization头。

    下面的例子展示了如何使用access token对客户端进行身份认证。

    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"

    当客户端代表用户执行操作时,上边的方法特别有用。上面的Bearer token是Keycloak已经向(代表用户的)客户端发放的access token。Keycloak将根据access token所代表的上下文来评估权限。例如,如果access token是向(代表用户A的)客户端A发放的,那么Keycloak将根据用户A来授予权限。

    Client Credentials

    客户端可以使用Keycloak提供的任意方式来认证。如 client_id/client_secret 或者JWT。

    下面的例子展示了如何在请求中附带client id以及client secret来验证:

    curl -X POST \

      http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \

      -H "Authorization: Basic cGhvdGg6L7Jl13RmfWgtkk==pOnNlY3JldA==" \

      --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket"

    推送声明

    在从服务端获取权限的过程中,你可以向服务端提供任意的声明,用来向权限评估提供信息。

    如果不使用权限ticket来获取权限,你可以发送下面的授权请求:

    curl -X POST \

      http://${host}:${port}/auth/realms/${realm}/protocol/openid-connect/token \

      --data "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \

      --data "claim_token=ewogICAib3JnYW5pemF0aW9uIjogWyJhY21lIl0KfQ==" \

      --data "claim_token_format=urn:ietf:params:oauth:token-type:jwt" \

      --data "client_id={resource_server_client_id}" \

      --data "client_secret={resource_server_client_secret}" \

      --data "audience={resource_server_client_id}"

    claim_token参数是一个BASE64编码的JSON,编码前的格式如下:

    {

        "organization" : ["acme"]

    }

    格式中可以有一个或多个声明,其中每个声明的值都是一个字符串数组。 

    使用UMA推送声明

    有关使用UMA和权限ticket时如何推送声明的更多细节,请查看Permission API

    User-Managed Access

    Keycloak 授权服务是基于User-Managed Access的,我们简写为UMA。UMA协议在下面这些方面增强了OAuth2:

    隐私

    如今,越来越多的数据和设备连接到云,用户隐私也随之成为一个巨大的问题。资源服务器可以使用UMA和Keycloak来增强它们的功能,以便改进它们保护用户隐私方面的方式,根据用户定义的策略授予权限。

    点对点授权

    资源所有者(如终端用户)可以管理对其资源的访问,并授权其他人(如另一个终端用户)访问这些资源。这与OAuth2不同,在OAuth2中,被授予许可的是代表用户的客户端。而UMA允许资源所有者对其他用户本身进行异步授权。

    资源共享

    资源所有者可以管理其资源的权限,并决定谁可以访问特定的资源以及如何访问。Keycloak作为共享管理服务,资源所有者在其上来管理自己的资源。

    Keycloak符合UMA 2.0规范,提供了大部分UMA功能。

    举例来说,用户Alice(资源所有者)使用互联网银行服务(资源服务器)管理他的银行帐户(资源)。有一天,爱丽丝决定把她的银行账户开给一个会计专业人士鲍勃(请求方)。但是,Bob应该只能查看(范围)Alice的帐户。

    作为资源服务器,互联网银行服务必须保护Alice的银行帐户。为此,它依赖于Keycloak资源注册端点在服务器中创建一个代表Alice银行账户的资源。

    如果Bob此时试图访问Alice的银行帐户,访问将被拒绝。互联网银行服务为银行帐户定义了一些默认策略。如只有所有者(Alice)才能访问自己的银行账户。

    网上银行服务考虑到Alice的隐私,因此允许她更改自己账户的策略。如允许哪些人查看其银行账户。为此,互联网银行服务基于Keycloak为Alice提供一个地方来选择允许谁来访问她的数据以及能做出哪些操作。在任何时候,Alice都可以对Bob撤销或新增额外授权。

    授权过程

    在UMA中,授权过程开始于客户端试图访问受UMA保护的资源服务器时。

    受UMA保护的资源服务器会要求请求带有bearer token。下面模拟了不带权限ticket的请求:

    curl -X GET \

      http://${host}:${port}/my-resource-server/resource/1bfdfe78-a4e1-4c2d-b142-fc92b75b986f

    资源服务器将一个响应发送回客户机,该响应带有一个权限ticket和一个as_uri参数,该as_uri表示Keycloak服务器的地址,客户端可以将ticket发送到此地址来获得RPT。

    下面是资源服务器的响应,注意响应中包含权限ticket:

    HTTP/1.1 401 Unauthorized

    WWW-Authenticate: UMA realm="${realm}",

        as_uri="https://${host}:${port}/auth/realms/${realm}",

        ticket="016f84e8-f9b9-11e0-bd6f-0021cc6004de"

    权限ticket是Keycloak权限API发放的一种特殊token。它们代表被申请的权限(如资源以及范围)以及其它请求中的关联信息。只有资源服务器可以创建它们。

    客户机已经拥有了权限ticket和Keycloak服务器的地址,就可以使用文档发现API来获取token endpoint的地址并发送授权请求。

    下面的例子展示了客户端向token endpoint请求RPT:

    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 "ticket=${permission_ticket}

    Keycloak评估通过后会发放权限相关的RPT:

    HTTP/1.1 200 OK

    Content-Type: application/json

    ...

    {

        "access_token": "${rpt}",

    }

    响应和其他授权类型的响应类似。RPT在access_token响应参数中,如果client没有通过授权服务端会返回403状态码:

    HTTP/1.1 403 Forbidden

    Content-Type: application/json

    ...

    {

        "error": "access_denied",

        "error_description": "request_denied"

    }

    提交权限请求

    在整个授权过程中,客户端首先需要从受UMA保护的资源服务器获得一个权限ticket,以便在Keycloak的token端点用它来交换RPT。

    如果Keycloak判断客户端无法通过授权,会返回403及request_denied错误信息。

    HTTP/1.1 403 Forbidden

    Content-Type: application/json

    ...

    {

        "error": "access_denied",

        "error_description": "request_denied"

    }

    上面的响应表示Keycloak不能对该权限ticket发放RPT。

    某些时候客户端会用到异步授权流程,并让被资源所有者来决定是否授权。为此,客户端可以在对token端点的请求中带上submit_request参数:

    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 "ticket=${permission_ticket} \

      --data "submit_request=true"

    当使用submit_request参数时,Keycloak会记录下每个被拒绝的权限申请。然后资源所有者就可以检查并管理这些它们。

    你可以将此功能视为APP中的请求访问按钮,用户可以向其他用户申请以访问他们的资源。

    管理对用户资源的访问

    用户可以使用Keycloak来管理对其资源的访问。要启用此功能,必须首先启用域的用户管理访问,请打开Keycloak管理控制台中的Realm设置页面,并启用用户管理的访问开关。然后进入account客户端:

    Account客户端的左侧的菜单栏会展示My Resources选项,用户可以在这里:

    管理需要同意授权的请求

    包含等待批准的所有权限请求列表。每个请求都关联到请求访问特定资源的用户。用户可以批准或拒绝这些请求。

    管理我的资源

    包含当前用户的所有资源。用户可以点击资源来查看详情或者把它们共享给其他用户。

    管理其他人共享给我的资源

    包含一个共享给当前用户的资源列表。

    管理等待审批的请求

    包含一个等待当前用户(资源所有者)审批的请求列表。

    当用户点击 “我的资源” 列表中的任何资源详情时,将被重定向到如下页面:

    用户可以在这里:

    管理允许访问此资源的用户

    在这里可以查看所有有权访问此资源的用户。可以通过点击 Revoke 来收回他们的访问权。

    将资源共享给其他人

    可以在这里输入用户名或者邮箱,来选择为特定用户开放权限。

    保护API

    保护API遵循UMA提供了一系列端点:

    资源管理

    用户可以用此端点来远程管理资源,并开启策略执行器来向服务器查询资源的访问权限。

    权限管理

    UMA协议中,资源服务器可以访问此端点来获取权限ticket。此端点还支持查询权限以及管理权限状态。

    策略API

    Keycloak利用UMA保护API使资源服务器能够管理各自用户的权限。除了资源和权限API外,Keycloak还提供了策略API。可以让资源服务器代表其用户设置资源的权限。

    使用此API要求资源服务器拥有名为protection API token(PAT)的特殊OAuth2 access token。在UMA中,PAT是有着uma_protection范围的token。

    什么是PAT,如何获取它们?

    一个protection API token(PAT)是有着uma_protection范围的特殊token。创建资源服务器时Keycloak会自动创建uma_protection的角色,并将其与客户端的服务帐户关联起来。

    资源服务器可以像获取其他OAuth2 token那样从Keycloak获取PAT。下边是一个curl的例子:

    curl -X POST \

        -H "Content-Type: application/x-www-form-urlencoded" \

        -d 'grant_type=client_credentials&client_id=${client_id}&client_secret=${client_secret}' \

        "http://localhost:8080/auth/realms/${realm_name}/protocol/openid-connect/token"

    上面的示例使用client_credentials授权类型从服务器获取PAT。服务器返回的响应如下:

    {

      "access_token": ${PAT},

      "expires_in": 300,

      "refresh_expires_in": 1800,

      "refresh_token": ${refresh_token},

      "token_type": "bearer",

      "id_token": ${id_token},

      "not-before-policy": 0,

      "session_state": "ccea4a55-9aec-4024-b11c-44f6f168439e"

    }

    Keycloak可以使用不同的方式来认证客户端应用。上面使用了client_credentials方式,它要求提供一个client_id和一个client_secret。你可以自由选择其它方式来认证。

    管理资源

    Keycloak提供了遵循UMA协议的端点来远程管理资源。

    http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set

    下面列出了此端点的主要功能(为了清晰起见省略了全路径):

    创建资源: POST /resource_set

    读取资源:GET /resource_set/{_id}

    更新资源: PUT /resource_set/{_id}

    删除资源:DELETE /resource_set/{_id}

    资源列表:GET /resource_set

    更多信息请查看UMA资源注册API

    创建资源

    使用下面的POST请求来创建资源:

    curl -v -X POST \

      http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set \

      -H 'Authorization: Bearer '$pat \

      -H 'Content-Type: application/json' \

      -d '{

        "name":"Tweedl Social Service",

        "type":"http://www.example.com/rsrcs/socialstream/140-compatible",

        "icon_uri":"http://www.example.com/icons/sharesocial.png",

        "resource_scopes":[

            "read-public",

            "post-updates",

            "read-private",

            "http://www.example.com/scopes/all"

          ]

      }'

    默认情况下,资源的所有者是资源服务器。可以使用下面的请求来定义其他资源所有者:

    curl -v -X POST \

      http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set \

      -H 'Authorization: Bearer '$pat \

      -H 'Content-Type: application/json' \

      -d '{

        "name":"Alice Resource",

        "owner": "alice"

      }'

    其中owner字段开始是用户名或者用户唯一标识。

    创建用户管理的资源

    默认情况下,通过保护API创建的资源不能由资源所有者通过帐户服务来管理。

    要创建资源所有者可管理的资源,你必须设置ownerManagedAccess属性:

    curl -v -X POST \

      http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set \

      -H 'Authorization: Bearer '$pat \

      -H 'Content-Type: application/json' \

      -d '{

        "name":"Alice Resource",

        "owner": "alice",

        "ownerManagedAccess": true

      }'

    更新资源

    使用HTTP PUT请求来更新资源:

    curl -v -X PUT \

      http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set/{resource_id} \

      -H 'Authorization: Bearer '$pat \

      -H 'Content-Type: application/json' \

      -d '{

        "_id": "Alice Resource",

        "name":"Alice Resource",

        "resource_scopes": [

            "read"

        ]

      }'

    删除资源

    使用HTTP DELETE请求来删除资源:

    curl -v -X DELETE \

      http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set/{resource_id} \

      -H 'Authorization: Bearer '$pat

    查询资源

    通过ID查询资源:

    http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set/{resource_id}

    通过name来查询资源:

    http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set?name=Alice Resource

    通过URI来查询资源:

    http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set?uri=/api/alice

    通过owner查询资源:

    http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set?owner=alice

    通过type查询资源:

    http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set?type=albums

    通过scope查询资源:

    http://${host}:${port}/auth/realms/${realm_name}/authz/protection/resource_set?scope=read

     查询时可以通过first和max来限制结果条数。

    管理权限申请

    使用UMA的资源服务器可以使用端点来管理权限申请。端点也提供了UMA兼容的流程来注册权限请求以及获取权限ticket。

    http://${host}:${port}/auth/realms/${realm_name}/authz/protection/permission

    上面说到的权限ticket是一种特殊的token,代表了一个权限申请。UMA规范对权限ticket的定义如下:

    它代表着一个相关句柄,从授权服务器传递到资源服务器、再到客户端,最终回到授权服务器。授权服务器在授权过程中基于此句柄来选择要评估的策略。

    在大多数情况下,你不需要直接调用此端点。Keycloak为资源服务器提供了策略执行器,用来从授权服务器获取权限ticket,再将此ticket返回客户端应用,并且基于最终的RPT执行授权决策。

    从Keycloak获取权限ticket的过程是由资源服务器执行,而不是由常规客户端执行的。在常规的客户端中,当客户端试图访问受保护的资源而没有访问该资源的必要授权时,就会获得权限ticket。签发权限ticket是UMA的一个重要方面,它使得资源服务器:

    将资源服务器要保护的资源相关的数据从客户端抽象出来

    在Keycloak中注册授权请求,使得资源所有者的可以管理是否同意授权

    将资源服务器和授权服务器解耦,使得资源服务器可以选择不同的授权服务器来管理资源

    从客户端的角度来看,权限ticket也有着重要作用:

    客户端无需知道授权和资源是如何关联的。权限ticket对客户端来说是不透明的。

    客户端可以访问不同资源服务器上被不同授权服务器所保护的资源。

    这些只是UMA带来的一些好处,其实UMA主要优势在隐私和用户控制资源访问方面。

    创建权限ticket

    下面的HTTP POST请求展示了如何创建权限Ticket:

    curl -X POST \

      http://${host}:${port}/auth/realms/${realm_name}/authz/protection/permission \

      -H 'Authorization: Bearer '$pat \

      -H 'Content-Type: application/json' \

      -d '[

      {

        "resource_id": "{resource_id}",

        "resource_scopes": [

          "view"

        ]

      }

    ]'

    你可以在上面的请求中添加任意的声明:

    curl -X POST \

      http://${host}:${port}/auth/realms/${realm_name}/authz/protection/permission \

      -H 'Authorization: Bearer '$pat \

      -H 'Content-Type: application/json' \

      -d '[

      {

        "resource_id": "{resource_id}",

        "resource_scopes": [

          "view"

        ],

        "claims": {

            "organization": ["acme"]

        }

      }

    ]'

    这些声明将用于评估此权限ticket相关的策略。 

    使用策略API来管理资源权限

    Keycloak利用UMA保护API来允许资源服务器管理其用户的资源。除了资源以及权限API之外,Keycloak还提供了策略API,来让资源服务器代表其用户设置资源权限。

    策略服务器端点如下:

    http://${host}:${port}/auth/realms/${realm_name}/authz/protection/uma-policy/{resource_id}

    访问这些API时需要携带bearer token,来证明用户允许资源服务器替代自己来管理权限。bearer token可以是从token端点获取到的常规token:

    资源所有者密码验证授权

    交换令牌,即将授予某些客户端(公共客户端)的access token交换为资源服务器的bearer token

    将权限与资源关联

    用下面的POST请求来将权限关联到特定资源:

    curl -X POST \

      http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{resource_id} \

      -H 'Authorization: Bearer '$access_token \

      -H 'Cache-Control: no-cache' \

      -H 'Content-Type: application/json' \

      -d '{

            "name": "Any people manager",

            "description": "Allow access to any people manager",

            "scopes": ["read"],

            "roles": ["people-manager"]

    }'

    上面的例子中,我们创建了一个权限,并将它关联到指定 resource_id 的资源,表示有着 people-manager 角色的用户都被授予 read 范围。

    下面展示了一个新建组访问控制策略的例子:

    curl -X POST \

      http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{resource_id} \

      -H 'Authorization: Bearer '$access_token \

      -H 'Cache-Control: no-cache' \

      -H 'Content-Type: application/json' \

      -d '{

            "name": "Any people manager",

            "description": "Allow access to any people manager",

            "scopes": ["read"],

            "groups": ["/Managers/People Managers"]

    }'

    下面使用了客户端访问的策略:

    curl -X POST \

      http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{resource_id} \

      -H 'Authorization: Bearer '$access_token \

      -H 'Cache-Control: no-cache' \

      -H 'Content-Type: application/json' \

      -d '{

            "name": "Any people manager",

            "description": "Allow access to any people manager",

            "scopes": ["read"],

            "clients": ["my-client"]

    }'

    甚至可以使用JavaScript来自定义策略:

    curl -X POST \

      http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{resource_id} \

      -H 'Authorization: Bearer '$access_token \

      -H 'Cache-Control: no-cache' \

      -H 'Content-Type: application/json' \

      -d '{

            "name": "Any people manager",

            "description": "Allow access to any people manager",

            "scopes": ["read"],

            "condition": "if (isPeopleManager()) {$evaluation.grant()}"

    }'

    也可以组合不用的访问控制机制。

    使用HTTP PUT请求来更新权限:

    curl -X PUT \

      http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{permission_id} \

      -H 'Authorization: Bearer '$access_token \

      -H 'Content-Type: application/json' \

      -d '{

        "id": "21eb3fed-02d7-4b5a-9102-29f3f09b6de2",

        "name": "Any people manager",

        "description": "Allow access to any people manager",

        "type": "uma",

        "scopes": [

            "album:view"

        ],

        "logic": "POSITIVE",

        "decisionStrategy": "UNANIMOUS",

        "owner": "7e22131a-aa57-4f5f-b1db-6e82babcd322",

        "roles": [

            "user"

        ]

    }'

    删除权限

    使用下面的DELETE请求来删除特定的权限:

    curl -X DELETE \

      http://localhost:8180/auth/realms/photoz/authz/protection/uma-policy/{permission_id} \

      -H 'Authorization: Bearer '$access_token

    查询权限

    查询某资源关联的所有权限:

    http://${host}:${post}/auth/realms/${realm}/authz/protection/uma-policy?resource={resource_id}

    使用权限名称查询权限:

    http://${host}:${post}/auth/realms/${realm}/authz/protection/uma-policy?resource={resource_id}

    查询关联 read 范围的权限:

    http://${host}:${post}/auth/realms/${realm}/authz/protection/uma-policy?scope=read

    查询全部权限:

    http://${host}:${post}/auth/realms/${realm}/authz/protection/uma-policy

    可以使用first以及max参数来限定查询结果。

    RPT

    RPT是使用JSON web signature(JWS)签发的JSON web token(JWT)。RPT基于之前由Keycloak发出的OAuth2 access token来构建,该token用于用户自身或代表用户的特定客户端。

    解码RPT可以看到它的payload如下:

    {

      "authorization": {

          "permissions": [

            {

              "resource_set_id": "d2fe9843-6462-4bfc-baba-b5787bb6e0e7",

              "resource_set_name": "Hello World Resource"

            }

          ]

      },

      "jti": "d6109a09-78fd-4998-bf89-95730dfd0892-1464906679405",

      "exp": 1464906971,

      "nbf": 0,

      "iat": 1464906671,

      "sub": "f1888f4d-5172-4359-be0c-af338505d86c",

      "typ": "kc_ett",

      "azp": "hello-world-authz-service"

    }

    从此token中,你可以从服务器获取所有permissions声明的权限。

    注意,权限与要保护的资源/范围直接相关,并且完全与实际授予和发出这些权限的机制解耦。

    审视RPT

    有些时候可能需要检视RPT来查看有效性或者从中获取权限,以便让资源服务器来强制执行授权决策。

    有两种检视RPT的主要场景:

    客户端应用需要检查token有效性来获取一个新的token

    在资源服务器端执行授权决策,特别是没有内置的策略执行器能够满足你的要求时

    获取RPT信息

    你可以从下面的端点来获取RPT信息,它满足OAuth token 检视规范:

    http://${host}:${port}/auth/realms/${realm_name}/protocol/openid-connect/token/introspect

    要使用上面的端点来检视一个RPT,你可以发送如下请求:

    curl -X POST \

        -H "Authorization: Basic aGVsbG8td29ybGQtYXV0aHotc2VydmljZTpzZWNyZXQ=" \

        -H "Content-Type: application/x-www-form-urlencoded" \

        -d 'token_type_hint=requesting_party_token&token=${RPT}' \

        "http://localhost:8080/auth/realms/hello-world-authz/protocol/openid-connect/token/introspect"

    上面的请求使用了HTTP BASIC认证,并且发送了客户端的client ID以及secret。用于审核申请检视RPT的客户端身份,你也可以选择Keycloak支持的其它客户端认证方式。

    请求中需要两个参数:

    token_type_hint

    此参数传值requesting_party_token 代表你期望检视RPT。

    token

    使用授权过程中服务器传回的token传值。

    服务端对上面请求的响应如下:

    {

      "permissions": [

        {

          "resource_id": "90ccc6fc-b296-4cd1-881e-089e1ee15957",

          "resource_name": "Hello World Resource"

        }

      ],

      "exp": 1465314139,

      "nbf": 0,

      "iat": 1465313839,

      "aud": "hello-world-authz-service",

      "active": true

    }

    如果RPT处于过期状态,响应会直接返回:

    {

      "active": false

    }

    每次检视RPT时都需要调用服务端吗?

    不是的。默认情况下RPT也是符合JWT规范的token,这意味着你完全可以自己解释它。

    如果希望在不调用远程检视端点的情况下验证这些token,可以本地解码RPT并检查有效性。解码之后,还可以使用token中的权限来执行授权决策。

    这正是策略执行器做的事情,请记得:

    用realm的公钥来检查RPT签名

    根据其exp、iat和aud声明查询token有效性

    授权客户端的Java API

    根据实际项目的需求,资源服务器客远程管理资源以及通过编程来检查权限。如果你正在使用Java,则可以使用授权客户端API来访问Keycloak授权服务。

    Keycloak针对资源服务器的不同需求,提供了不同端点(如token端点、资源端点和权限管理端点)。

    Maven依赖

    <dependencies>

        <dependency>

            <groupId>org.keycloak</groupId>

            <artifactId>keycloak-authz-client</artifactId>

            <version>${KEYCLOAK_VERSION}</version>

        </dependency>

    </dependencies>

    配置

    客户端配置在keycloak.json文件中,下面是一个简单的例子:

    {

      "realm": "hello-world-authz",

      "auth-server-url" : "http://localhost:8080/auth",

      "resource" : "hello-world-authz-service",

      "credentials": {

        "secret": "secret"

      }

    }

    realm(必须)

    realm的名称。

    auth-server-url(必须)

    Keycloak地址。也是所有Keycloak页面以及REST符端点的根路径。通常是 https://host:port/auth 的形式。

    resource(必须)

    客户端ID。即客户端在Keycloak上的唯一标识。

    客户端身份(必须)

    客户端凭据。这是一个键值对,其中键是凭据类型,值是该类型对应的值。

    一般情况下该配置文件应该放在项目的classpath中,这也是客户端寻找keycloak.json文件的默认路径。

    创建授权客户端

    现在假定classpath中已经有了keycloak.json,现在来创建一个AuthzClient:

        // create a new instance based on the configuration defined in a keycloak.json located in your classpath

        AuthzClient authzClient = AuthzClient.create();

    获取用户授权

    下面的例子展示了如果获取用户授权:

    // create a new instance based on the configuration defined in keycloak-authz.json

    AuthzClient authzClient = AuthzClient.create();

    // create an authorization request

    AuthorizationRequest request = new AuthorizationRequest();

    // send the entitlement request to the server in order to

    // obtain an RPT with all permissions granted to the user

    AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize(request);

    String rpt = response.getToken();

    System.out.println("You got an RPT: " + rpt);

    // now you can use the RPT to access protected resources on the resource server

    下面是一个例子,说明如何为一组资源获取用户权限:

    // create a new instance based on the configuration defined in keycloak-authz.json

    AuthzClient authzClient = AuthzClient.create();

    // create an authorization request

    AuthorizationRequest request = new AuthorizationRequest();

    // add permissions to the request based on the resources and scopes you want to check access

    request.addPermission("Default Resource");

    // send the entitlement request to the server in order to

    // obtain an RPT with permissions for a single resource

    AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize(request);

    String rpt = response.getToken();

    System.out.println("You got an RPT: " + rpt);

    // now you can use the RPT to access protected resources on the resource server

    使用保护API来创建资源

    // create a new instance based on the configuration defined in keycloak-authz.json

    AuthzClient authzClient = AuthzClient.create();

    // create a new resource representation with the information we want

    ResourceRepresentation newResource = new ResourceRepresentation();

    newResource.setName("New Resource");

    newResource.setType("urn:hello-world-authz:resources:example");

    newResource.addScope(new ScopeRepresentation("urn:hello-world-authz:scopes:view"));

    ProtectedResource resourceClient = authzClient.protection().resource();

    ResourceRepresentation existingResource = resourceClient.findByName(newResource.getName());

    if (existingResource != null) {

        resourceClient.delete(existingResource.getId());

    }

    // create the resource on the server

    ResourceRepresentation response = resourceClient.create(newResource);

    String resourceId = response.getId();

    // query the resource using its newly generated id

    ResourceRepresentation resource = resourceClient.findById(resourceId);

    System.out.println(resource);

    检查RPT

    // create a new instance based on the configuration defined in keycloak-authz.json

    AuthzClient authzClient = AuthzClient.create();

    // send the authorization request to the server in order to

    // obtain an RPT with all permissions granted to the user

    AuthorizationResponse response = authzClient.authorization("alice", "alice").authorize();

    String rpt = response.getToken();

    // introspect the token

    TokenIntrospectionResponse requestingPartyToken = authzClient.protection().introspectRequestingPartyToken(rpt);

    System.out.println("Token status is: " + requestingPartyToken.getActive());

    System.out.println("Permissions granted by the server: ");

    for (Permission granted : requestingPartyToken.getPermissions()) {

        System.out.println(granted);

    }

    策略执行器

    策略实施点(Policy Enforcement Point, PEP)是一种设计模式,可以由不同的方式实现。Keycloak提供了为不同平台、环境和编程语言实现PEPs的所有必要方法。Keycloak授权服务提供了一个RESTful API,并利用OAuth2授权功能使用集中式授权服务器进行细粒度授权。

    PEP负责强制执行(Keycloak服务器决定的)访问决策。它在应用程序中充当过滤器或拦截器,以检查是否可以根据这些决策的授权来访问受保护的资源。

    权限的执行取决于使用的协议。当使用UMA时,策略执行器使用RPT作为bearer token来发起请求。这意味着客户端在向资源服务器发送权限申请之前,应该首先从Keycloak获取一个RPT。

    但是如果没有使用UMA,你就可以使用一个普通的access token。这种情况下,策略执行器会尝试从服务器直接获取权限。

    如果你正在使用任何Keycloak OIDC适配器,就可以通过向keycloak.json文件添加以下属性来启用策略执行器:

    {

    "policy-enforcer": {}

    }

    当您启用策略强制器时,所有发送到你的应用的请求都会被拦截,并根据Keycloak所授予的权限来检查是否能够访问。

    策略的强制执行和Keycloak创建的资源以及项目项目路径强关联。默认情况下,创建资源服务器时,Keycloak会为资源服务器创建一个默认配置,这样就可以快速地启用策略强制执行。

    配置

    上面提高过,在keycloak.json文件中添加policy-enforcer来启动策略执行:

    {

      "policy-enforcer": {}

    }

    如果要手动定义资源,可以写的复杂一点:

    {

      "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"]

              }

            ]

          },

          {

            "name" : "Some Resource",

            "path" : "/usingPattern/{id}",

            "methods" : [

              {

                "method": "DELETE",

                "scopes" : ["urn:app.com:scopes:delete"]

              }

            ]

          },

          {

            "path" : "/exactMatch"

          },

          {

            "name" : "Admin Resources",

            "path" : "/usingWildCards/*"

          }

        ]

      }

    }

    下面来解释这个配置文件:

    policy-enforcer

    定义如何实际执行策略,以及希望保护的路径。如果没有指定,策略强制器将查询服务器,以查找与受保护的资源服务器相关联的所有资源。在这种情况下,需要确保资源的URIS被正确配置。

    -user-managed-access

    指定适配器使用UMA协议。如果指定了,适配器将向服务器查询权限ticket,并根据UMA规范将它们返回给客户端。如果没有指定,则策略强制器将基于常规access token或RPT来执行权限。在这种情况下,拒绝访问资源前策略执行器将尝试直接从服务器获得权限。

    -enforcement-mode

    指定策略如何强制执行。

    ENFORCING

                这是默认模式,当没有策略关联到指定资源时,请求会被拒绝。

    PERMISSIVE

                当没有策略关联到指定资源时,请求会被允许。

    DISABLE

    完全禁用策略评估,允许访问任何资源。禁用后,应用程序仍然能够通过授权上下文获得Keycloak授予的所有权限。

    -on-deny-reirect-to

    定义一个URL,用于拒绝访问时重定向客户端请求。默认情况下,适配器使用403 HTTP状态码进行响应。

    -path-cache

    定义策略执行器应如何跟踪应用中的路径及Keycloak的资源之的关联。通过这些配置关联,可以避免到Keycloak的不必要请求。

    lifespan

                本地缓存的过期时间,默认为3000,设置为0或者以下的值会禁用缓存。

    max-entries

                定义本地缓存的条数,如果没有提供时默认为1000条。

    -paths

    指定要保护的路径。此配置是可选的。如果没有定义,策略执行器将查询Keycloak来获取本客户端对应的全部路径,并使用Keycloak上配置的URIS当做本地路径。

    name

    给定路径关联的服务器上资源的名称。当与path一起使用时,策略强制器将忽略资源的URIS属性,而使用配置提供的路径。

    path

    必填。相对路径的URI。如果指定了此选项,策略执行器将向服务器查询相同值的URI的资源。目前支持基本的路径匹配逻辑。有效路径的例子如下:

    通配符: /*

    后缀:/*.html

    子路径:/path/*

    路径参数: /resource/{id}

    精确匹配: /resource

    数组: /{version}/resource, /api/{version}/resource, /api/{version}/resource/*

    methods

                用于保护的HTTP方法(例如GET、POST、PATCH),以及它们如何与服务器中给定资源的范围相关联。

    method:HTTP方法的名称

    scopes:关联的范围数组。当范围与特定method关联时,试图访问受保护资源的客户端必须提供一个RPT用来检查范围权限。例如定义了create 范围来表示POST,则RPT必须包含对此路径的create 范围的访问权限。

    scopes-enforcement-mode:范围的执行模式。值可以是ALLANY。如果是ALL,则必须授权给所有已定义的范围。如果是ANY,至少应该授权给一个范围。默认情况下,强制模式设置为ALL

    enforcement-mode

               指定策略如何执行

    ENFORCING:默认模式,没有policy关联的资源拒绝方案。

    DIABLED:禁用。

    claim-information-point

                定义一组个声明,这些声明必须被解析并推送到Keycloak服务器,以便策略使用。有关详细信息,请参见这里

    -lazy-load-paths

    指定适配器应如何获取与应用路径关联的资源的服务器。如果为true,则策略执行器将根据请求的路径按需获取资源。当不想在部署期间从服务器获取所有资源(没有提供路径时),或者只定义了一个子路径集,并且希望按需获取其它路径时,这种配置特别有用。

    -http-method-as-scope

    指定范围应如何映射到HTTP方法。如果设置为true,策略执行器将使用当前请求中的HTTP方法来检查是否应该授予访问权限。启用时,请确保Keycloak配置的资源范围与要保护的路径HTTP方法正确对应。

    -claim-information-point

    定义一组全局声明,这些声明必须被解析并推送到Keycloak服务器,以便策略评估使用。有关详细信息,请参见这里

    声明信息点

    声明信息点(Claim Informatica Point, 简写为CIP)负责处理声明并推到Keycloak服务器,以便提供关于策略上下文的更多信息。可以将它们定义为策略执行器的配置选项,以便处理来自不同来源的声明,例如:

    HTTP请求(参数、header、body 等)

    外部HTTP服务

    配置中的静态值

    实现了Claim Information Provider SPI的任意资源

    当将声明推送到Keycloak服务器时,策略不仅可以基于当前用户,还可以基于给定事务的人、事务、时间、地点等等内容来作为运行上下文考虑并做出决策。即如何使用运行时信息来支持细粒度的授权决策。

    从HTTP请求中获取信息

    下面的keycloak.json配置说明了如何从HTTP请求中来解析信息:

    "policy-enforcer": {

        "paths": [

          {

            "path": "/protected/resource",

            "claim-information-point": {

              "claims": {

                "claim-from-request-parameter": "{request.parameter['a']}",

                "claim-from-header": "{request.header['b']}",

                "claim-from-cookie": "{request.cookie['c']}",

                "claim-from-remoteAddr": "{request.remoteAddr}",

                "claim-from-method": "{request.method}",

                "claim-from-uri": "{request.uri}",

                "claim-from-relativePath": "{request.relativePath}",

                "claim-from-secure": "{request.secure}",

                "claim-from-json-body-object": "{request.body['/a/b/c']}",

                "claim-from-json-body-array": "{request.body['/d/1']}",

                "claim-from-body": "{request.body}",

                "claim-from-static-value": "static value",

                "claim-from-multiple-static-value": ["static", "value"],

                "param-replace-multiple-placeholder": "Test {keycloak.access_token['/custom_claim/0']} and {request.parameter['a']} "

              }

            }

          }

        ]

      }

    从外部HTTP服务获取信息

    "policy-enforcer": {

        "paths": [

          {

            "path": "/protected/resource",

            "claim-information-point": {

              "http": {

                "claims": {

                  "claim-a": "/a",

                  "claim-d": "/d",

                  "claim-d0": "/d/0",

                  "claim-d-all": ["/d/0", "/d/1"]

                },

                "url": "http://mycompany/claim-provider",

                "method": "POST",

                "headers": {

                  "Content-Type": "application/x-www-form-urlencoded",

                  "header-b": ["header-b-value1", "header-b-value2"],

                  "Authorization": "Bearer {keycloak.access_token}"

                },

                "parameters": {

                  "param-a": ["param-a-value1", "param-a-value2"],

                  "param-subject": "{keycloak.access_token['/sub']}",

                  "param-user-name": "{keycloak.access_token['/preferred_username']}",

                  "param-other-claims": "{keycloak.access_token['/custom_claim']}"

                }

              }

            }

          }

        ]

      }

    静态声明

    "policy-enforcer": {

        "paths": [

          {

            "path": "/protected/resource",

            "claim-information-point": {

              "claims": {

                "claim-from-static-value": "static value",

                "claim-from-multiple-static-value": ["static", "value"],

              }

            }

          }

        ]

      }

    Cliam Informatica Provider 的SPI

    当内置CIP不能满足实际需求时,开发人员可以使用SPI来支持不同的CIP。

    例如,要实现一个新的CIP提供者,需要实现 org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory 以及ClaimInformationPointProvide,并且在项目的classpath中提供 META-INF/services/org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory 文件。

    下面是一个例子:

    public class MyClaimInformationPointProviderFactory implements ClaimInformationPointProviderFactory<MyClaimInformationPointProvider> {

        @Override

        public String getName() {

            return "my-claims";

        }

        @Override

        public void init(PolicyEnforcer policyEnforcer) {

        }

        @Override

        public MyClaimInformationPointProvider create(Map<String, Object> config) {

            return new MyClaimInformationPointProvider(config);

        }

    }

    每个CIP提供程序都要提供一个名称,如上面在MyClaimInformationPointProviderFactory.getName方法。该名称必须与policy- enforcer配置的claim-information-point一致,Keycloak通过此名称定位到实现。

    当处理请求时,策略执行器将调用MyClaimInformationPointProviderFactory.create 方法,来获取MyClaimInformationPointProvider实例。调用时,此CIP provider定义的任何配置(通过CIP)都将传给Keycloak。

    下面是一个Provider的例子:

    public class MyClaimInformationPointProvider implements ClaimInformationPointProvider {

        private final Map<String, Object> config;

        public ClaimsInformationPointProvider(Map<String, Object> config) {

            this.config = config;

        }

        @Override

        public Map<String, List<String>> resolve(HttpFacade httpFacade) {

            Map<String, List<String>> claims = new HashMap<>();

            // put whatever claim you want into the map

            return claims;

        }

    }

    获取授权上下文

    当启用策略强制执行时,可以通过org.keycloak.AuthorizationContext从服务器来获得授权信息。这个类提供了几个方法,可以使用它们来获取权限并判断是否为特定的资源或范围授予了权限。

    下面的代码展示如何从Servlet容器中获取授权上下文:

        HttpServletRequest request = ... // obtain javax.servlet.http.HttpServletRequest

        KeycloakSecurityContext keycloakSecurityContext =

            (KeycloakSecurityContext) request

                .getAttribute(KeycloakSecurityContext.class.getName());

        AuthorizationContext authzContext =

            keycloakSecurityContext.getAuthorizationContext();

    获取KeycloakSecurityContext的信息视具体的适配器而定,请查看运行平台的适配器配置。上面的例子用于运行在servlet容器中的应用。

    授权上下文可以帮助你根据Keycloak决策来构建更多功能。例如,可以使用它构建一个仅展示已授权资源的动态菜单:

    if (authzContext.hasResourcePermission("Project Resource")) {

        // user can access the Project Resource

    }

    if (authzContext.hasResourcePermission("Admin Resource")) {

        // user can access administration resources

    }

    if (authzContext.hasScopePermission("urn:project.com:project:create")) {

        // user can create new projects

    }

    AuthorizationContext代表了Keycloak授权服务的主要功能之一。从上面的示例可以看出受保护的资源与管理它们的策略没有直接关联。

    在基于角色的访问控制(RBAC)权限控制中它们被写作这样:

    if (User.hasRole('user')) {

        // user can access the Project Resource

    }

    if (User.hasRole('admin')) {

        // user can access administration resources

    }

    if (User.hasRole('project-manager')) {

        // user can create new projects

    }

    虽然这两个示例都处理相同的需求,着手点不同。在RBAC中,角色仅隐式地定义对其资源的访问。使用Keycloak,可以创建更易于管理的代码,这些代码直接关注资源本身,无论使用的是RBAC、基于属性的访问控制(ABAC)还是任何其他BAC。

    假设我们项目的安全性需求发生了变化,除了项目经理之外,PMOs现在也可以新建项目。

    如果使用了Keycloak,你完全不需要更改应用程序就可以处理新的需求。一旦应用程序基于资源和范围来构建,就可以只更改授权服务器中特定资源关联的权限或策略。在这种情况下,只需要更改与项目资源和/或范围 urn:project.com:project:create 关联的权限和策略。

    使用AuthorizationContext获取授权客户端实例

    AuthorizationContext 还可用于获取你项目中配置的授权客户端API的引用:

        ClientAuthorizationContext clientContext = ClientAuthorizationContext.class.cast(authzContext);

        AuthzClient authzClient = clientContext.getClient();

    某些情况下,受策略执行器保护的资源服务器需要访问授权服务器提供的API。有了AuthzClient 实例,资源服务器就可以与授权服务器交互,编程式地创建资源或检查特定的权限。

    JavaScript集成

    Keycloak服务器附带一个JavaScript库,可以使用它与受策略执行器保护的资源服务器进行交互。这个库基于Keycloak JavaScript适配器,你的客户端可以直接集成它来从Keycloak服务器获得权限。

    可以从Keycloak服务实例上在线获取它:

    <script src="http://.../auth/js/keycloak-authz.js"></script>

    然后可以方便地创建KeycloakAuthorization实例:

    var keycloak = ... // obtain a Keycloak instance from keycloak.js library

    var authorization = new KeycloakAuthorization(keycloak);

    keycloak-authz.js 库主要提供了两个功能:

    如果资源服务器开启了UMA,则可以使用权限ticket来从Keycloak获得权限。

    发送请求的资源和范围,申请从服务器获得权限。

    在这两种情况下,js库都允许你方便的和资源服务器或者Keycloak进行交互以获取token,然后客户端可以将其作为bearer token来访问资源服务器上受保护的资源。

    处理来自UMA保护的资源服务器的授权响应

    如果资源服务器由策略执行器保护,就将根据bearer token上携带的权限来做出响应。当携带的bearer token无权访问请求的资源时,服务器将响应401状态码及WWW-Authenticate头。

    HTTP/1.1 401 Unauthorized

    WWW-Authenticate: UMA realm="${realm}",

        as_uri="https://${host}:${post}/auth/realms/${realm}",

        ticket="016f84e8-f9b9-11e0-bd6f-0021cc6004de"

    UMA授权流程查看更多细节。

    客户端可以从资源服务器返回的WWW-Authenticate头中提取权ticket,并使js库来发送如下授权请求如下:

    // prepare a authorization request with the permission ticket

    var authorizationRequest = {};

    authorizationRequest.ticket = ticket;

    // send the authorization request, if successful retry the request

    Identity.authorization.authorize(authorizationRequest).then(function (rpt) {

        // onGrant

    }, function () {

        // onDeny

    }, function () {

        // onError

    });

    authorize函数是完全异步的,使用回调来接收服务器响应:

    onGrant:函数的第一个参数。如果授权成功,回调函数将接收到服务器返回的RPT。

    onDeny:函数的第二个参数。仅当服务器拒绝授权请求时调用。

    onError:第三个参数。仅当出现异常时调用。

    大多数应用程序应该使用onGrant回调函数在401响应之后重试请求。后续请求应该用此RPT作为重试的bearer token。

    获取权限

    keycloak-authz.js 库提供了一个 entitlement 函数,用于从服务器获得RPT。

    下面的例子展示了如何获取用户可以访问的全部资源和范围:

    authorization.entitlement('my-resource-server-id').then(function (rpt) {

        // onGrant callback function.

        // If authorization was successful you'll receive an RPT

        // with the necessary permissions to access the resource server

    });

    下面的例子展示了如何获得具有特定资源和范围权限的RPT:

    authorization.entitlement('my-resource-server', {

        "permissions": [

            {

                "id" : "Some Resource"

            }

        ]

    }).then(function (rpt) {

        // onGrant

    });

    在使用 entitlement 函数时,必须提供要访问的资源服务器的client_id。

    授权函数是完全异步的,支持一些回调函数来接收来自服务器的通知:

    onGrant:函数的第一个参数。如果授权成功,回调函数将接收到服务器返回的RPT。

    onDeny:函数的第二个参数。仅当服务器拒绝授权请求时调用。

    onError:第三个参数。仅当出现异常时调用。

    授权请求

    authorize 和 authorize 函数都接受一个授权请求对象。该对象可以通过以下属性设置:

    permissions

    表示资源和范围的对象数组。例如:

    var authorizationRequest = {

      "permissions": [

          {

              "id" : "Some Resource",

              "scopes" : ["view", "edit"]

          }

      ]

    }

    metadata

    定义服务器应如何处理授权请求。

    -response_include_resource_name

    布尔值,指示服务器资源名是否应该包含在RPT的权限中。如果为false,则只包含资源标识符。

    -response_permissions_limit

    整数N,它定义了RPT可以携带的权限数量上限。当与rpt参数一起使用时,只有最后N个请求的权限将保留在rpt中。

    submit_request

    布尔值,指示服务器是否需要创建对权限ticket的权限请求。此参数只有在与ticket参数一起,且在UMA授权流程时才会生效。

    获取RPT

    如果已经使用库提供的任何授权函数获得了RPT,则始终可以从授权对象获得RPT,如下所示:

    var rpt = authorization.rpt;

    设置TLS/HTTPS

    如果server使用的是HTTPS,请在adapter中加入以下配置,来让授权客户端使用TLS/HTTPS协议访问Keycloak:

    {

      "truststore": "path_to_your_trust_store",

      "truststore-password": "trust_store_password"

    }

    强烈建议通过HTTPS访问Keycloak。

    源文档更新于2019-03-13。

    相关文章

      网友评论

          本文标题:转:Keycloak授权服务指南(下)

          本文链接:https://www.haomeiwen.com/subject/dgqafktx.html