Merge pull request #5 from pedroigor/master
Initial getting started tutorials
This commit is contained in:
commit
bfbd4dc08c
10 changed files with 694 additions and 1 deletions
|
@ -3,6 +3,8 @@
|
|||
. link:topics/overview/overview.adoc[Overview]
|
||||
.. link:topics/overview/architecture.adoc[Architecture]
|
||||
.. link:topics/overview/terminology.adoc[Terminology]
|
||||
. link:topics/getting-started/getting-started.adoc[Getting Started]
|
||||
.. link:topics/getting-started/hello-world.adoc[Hello Authorization World]
|
||||
. link:topics/resource-server/overview.adoc[Managing Resource Servers]
|
||||
.. link:topics/resource-server/view.adoc[Viewing Resource Servers]
|
||||
.. link:topics/resource-server/create.adoc[Creating Resource Servers]
|
||||
|
|
BIN
images/gs-authz-hello-rs-created-page.png
Normal file
BIN
images/gs-authz-hello-rs-created-page.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
BIN
images/gs-keycloak-authz-create-rs-page.png
Normal file
BIN
images/gs-keycloak-authz-create-rs-page.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
BIN
images/gs-keycloak-authz-page.png
Normal file
BIN
images/gs-keycloak-authz-page.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
images/gs-keycloak-console-page.png
Normal file
BIN
images/gs-keycloak-console-page.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
12
topics/getting-started/getting-started.adoc
Executable file
12
topics/getting-started/getting-started.adoc
Executable file
|
@ -0,0 +1,12 @@
|
|||
== Getting Started
|
||||
|
||||
The purpose of this guide is to get you up and running as quickly as possible so that you can play with and test drive various authorization features that {{book.project.name}} has. It will help
|
||||
you to understand the basic steps to enable fine-grained authorization to an application and how to interact with the Authorization Services provided by {{book.project.name}}.
|
||||
|
||||
[NOTE]
|
||||
This guide assume that you are able to install and boot a {{book.project.name}} Server. For more information, please follow the intrusctions https://keycloak.gitbooks.io/getting-started-tutorials/content/[here].
|
||||
|
||||
Make sure you have a {{book.project.name}} instance up and running on http://localhost:8080/auth[http://localhost:8080/auth]. If everything is OK, you would be able to login to the
|
||||
_Administration Console_ and get a page like that:
|
||||
|
||||
image:../../images/gs-keycloak-console-page.png[alt="Keycloak Administration Console"]
|
202
topics/getting-started/hello-world-create-realm.adoc
Executable file
202
topics/getting-started/hello-world-create-realm.adoc
Executable file
|
@ -0,0 +1,202 @@
|
|||
=== Creating the Hello World AuthZ Realm
|
||||
|
||||
For this guide, we are going to create a *hello-world-authz* realm. Just import the following JSON file to create the new realm:
|
||||
|
||||
```json
|
||||
{
|
||||
"realm" : "hello-world-authz",
|
||||
"enabled" : true,
|
||||
"privateKey" : "MIIEpQIBAAKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQABAoIBAAwa4wVnKBOIS6srmYPfBTDNsTBBCEjxiYEErmn7JhoWxQ1DCPUxyxU6F177/q9Idqoj1FFOCtEO9P6/9+ym470HQmEQkR2Xxd1d3HOZy9oKuCro3ZbTDkVxY0JnlyxZz4MihGFxDH2e4MArfHy0sAgYbdIU+x2pWKGWSMzDd/TMSOExhc/sIQAg6ljbPCLLXCPQFAncoHRyGPrkRZs6UTZi5SJuCglVa2/3G+0drDdPuA83/mwsZfIBqQgbGbFgtq5T5C6CKMkPOQ42Rcclm7kEr6riTkJRo23EO1iOJVpxzI0tbxZsJAsW7zeqv0wWRyUgVfQAje6OdsNexp5aCtECgYEA6nMHCQ9xXvufCyzpIbYGxdAGqH6m1AR5gXerHqRiGNx+8UUt/E9cy/HTOhmZDK/eC4BT9tImeF01l1oSU/+wGKfux0SeAQchBhhq8GD6jmrtgczKAfZHp0Zrht7o9qu9KE7ZNWRmY1foJN9yNYmzY6qqHEy+zNo9amcqT7UZKO8CgYEA35sp9fMpMqkJE+NEJ9Ph/t2081BEkC0DYIuETZRSi+Ek5AliWTyEkg+oisTbWzi6fMQHS7W+M1SQP6djksLQNPP+353DKgup5gtKS+K/y2xNd7fSsNmkjW1bdJJpID7WzwwmwdahHxpcnFFuEXi5FkG3Vqmtd3cD0TYL33JlRy0CgYEA0+a3eybsDy9Zpp4m8IM3R98nxW8DlimdMLlafs2QpGvWiHdAgwWwF90wTxkHzgG+raKFQVbb0npcj7mnSyiUnxRZqt2H+eHZpUq4jR76F3LpzCGui2tvg+8QDMy4vwqmYyIxDCL8r9mqRnl3HpChBPoh2oY7BahTTjKEeZpzbR0CgYEAoNnVjX+mGzNNvGi4Fo5s/BIwoPcU20IGM+Uo/0W7O7Rx/Thi7x6BnzB0ZZ7GzRA51paNSQEsGXCzc5bOIjzR2cXLisDKK+zIAxwMDhrHLWZzM7OgdGeb38DTEUBhLzkE/VwYZUgoD1+/TxOkwhy9yCzt3gGhL1cF//GJCOwZvuECgYEAgsO4rdYScgCpsyePnHsFk+YtqtdORnmttF3JFcL3w2QneXuRwg2uW2Kfz8CVphrR9eOU0tiw38w6QTHIVeyRY8qqlHtiXj6dEYz7frh/k4hI29HwFx43rRpnAnN8kBEJYBYdbjaQ35Wsqkfu1tvHJ+6fxSwvQu/TVdGp0OfilAY=",
|
||||
"publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQAB",
|
||||
"certificate" : "MIICsTCCAZkCBgFVETX4AzANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFIZWxsbyBXb3JsZCBBdXRoWjAeFw0xNjA2MDIxMzAxMzdaFw0yNjA2MDIxMzAzMTdaMBwxGjAYBgNVBAMMEUhlbGxvIFdvcmxkIEF1dGhaMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQANm5gIT/c50lwjawM686gNXpppLA928WsCOn9NIIWjSKekP8Bf9S73kf7vWcsEppm5B8rRyRxolXmzwghv74L7uVDg8Injjgj+XbPVQP+cJqWpSaMZHF7UfWe0/4M945Xcbmsl5q+m9PmrPG0AaaZhqXHcp4ehB1H+awyRqiERpJUuwZNycw2+2kjDADpsFf8hZVUd1F6ReYyOkqUyUjbL+jYTC7ZBNa7Ok+w6HCXWgkgVATAgQXJRM3w14IOc5MH/vfMCrCl/eNQLbjGl9y7u8PKwh3MXHDO2OLqtg6hOTSrOGUPJZGmGtUAl+2/R7FzoWkML/BNe2hjsL6UJwg91",
|
||||
"requiredCredentials" : [ "password" ],
|
||||
"roles" : {
|
||||
"realm" : [
|
||||
{
|
||||
"name" : "uma_protection"
|
||||
}
|
||||
]
|
||||
},
|
||||
"users" :
|
||||
[
|
||||
{
|
||||
"username" : "alice",
|
||||
"enabled" : true,
|
||||
"credentials" : [ {
|
||||
"type" : "password",
|
||||
"value" : "password"
|
||||
} ],
|
||||
"clientRoles" : {
|
||||
"hello-world-authz-service" : [ "uma_authorization" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "jdoe",
|
||||
"enabled" : true,
|
||||
"credentials" : [ {
|
||||
"type" : "password",
|
||||
"value" : "password"
|
||||
} ],
|
||||
"clientRoles" : {
|
||||
"hello-world-authz-service" : [ "uma_authorization" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "service-account-hello-world-authz-service",
|
||||
"enabled" : true,
|
||||
"serviceAccountClientId" : "hello-world-authz-service",
|
||||
"realmRoles" : [ "uma_protection"]
|
||||
}
|
||||
],
|
||||
"clients" : [
|
||||
{
|
||||
"clientId" : "hello-world-authz-service",
|
||||
"secret" : "password",
|
||||
"serviceAccountsEnabled" : true,
|
||||
"enabled" : true,
|
||||
"redirectUris" : [ "http://localhost:8080/hello-world-authz-service" ],
|
||||
"directAccessGrantsEnabled" : true,
|
||||
"publicClient" : false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
The realm *hello-world-authz* consists of:
|
||||
|
||||
** Two users: _alice_ and _jdoe_
|
||||
** One client application: _hello-world-authz-service_
|
||||
** One global role: _uma_protection_
|
||||
** One client role: _uma_authorization_
|
||||
|
||||
The _hello-world-authz-service_ is the application with the resources we want to protect. In other words, it will act as a link:../overview/terminology.html[Resource Server].
|
||||
|
||||
In {{book.project.name}} a resource server is just a regular client application with some specific characteristics. It _must_ be a *confidential* client application as defined by:
|
||||
|
||||
```json
|
||||
"publicClient" : false
|
||||
```
|
||||
|
||||
It must have a *client_id*, *client_secret* and *Service Account* enabled:
|
||||
|
||||
```json
|
||||
"clients" : [
|
||||
{
|
||||
"clientId" : "hello-world-authz-service",
|
||||
"secret" : "7801be7c-437a-44ae-be34-67da32f024eb",
|
||||
"serviceAccountsEnabled" : true,
|
||||
...
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
And finally, an user mapping to the client's service account:
|
||||
|
||||
```json
|
||||
{
|
||||
"username" : "service-account-my-resource-server",
|
||||
"enabled" : true,
|
||||
"serviceAccountClientId" : "hello-world-authz-service",
|
||||
"realmRoles" : [ "uma_protection"]
|
||||
}
|
||||
```
|
||||
|
||||
In the latter case, we are also granting the *uma_protection* role to the client's service account. As you'll see, that will be necessary in order to get access to the link:../service/protection-api.html[Protection API].
|
||||
|
||||
=== Creating a Resource Server and Protecting Resources
|
||||
|
||||
Now that we have the *hello-world-authz* realm properly configured, we need to enable the *hello-world-authz-service* as a resource server. For that, click on the *Authorization* in the left menu bar.
|
||||
|
||||
image:../../images/gs-keycloak-authz-page.png[alt="Keycloak Authorization Page"]
|
||||
|
||||
To create a resource server you can click on the *Create* button.
|
||||
|
||||
image:../../images/gs-keycloak-authz-create-rs-page.png[alt="Create Resource Server"]
|
||||
|
||||
From that page you can create a resource server by manually filling that form or you can just import a JSON file with the configuration you want. For this guide, we'll just import a JSON file as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"clientId": "hello-world-authz-service",
|
||||
"resources": [
|
||||
{
|
||||
"name": "Hello World Resource"
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"name": "Only Special Users Policy",
|
||||
"type": "user",
|
||||
"logic": "POSITIVE",
|
||||
"config": {
|
||||
"users": "[\"alice\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hello World Resource Permission",
|
||||
"type": "resource",
|
||||
"config": {
|
||||
"resources": "[\"Hello World Resource\"]",
|
||||
"applyPolicies": "[\"Only Special Users Policy\"]"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
After importing the JSON file above, you would see a page like that:
|
||||
|
||||
image:../../images/gs-authz-hello-rs-created-page.png[alt="Resource Server Successfully Created"]
|
||||
|
||||
You may take some time now exploring the resource server we just created. But first, let's understand what we just created.
|
||||
|
||||
The resource server was created based on the *hello-world-authz-service* client application, as you can see from the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"clientId": "hello-world-authz-service",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
What we did was basically tell {{book.project.name}} that we want that client application acting as a resource server, so we can start creating the resources we want to protect as well the permissions
|
||||
and authorization policies we want to use to actually protect the resources.
|
||||
|
||||
The purpose of this guide is keep things simple to get you started, so our newly created resource server has a single protected resource, as defined by the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"resources": [
|
||||
{
|
||||
"name": "Hello World Resource"
|
||||
}
|
||||
],
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The *Hello World Resource* represents a set of one or more resources we want to protect. It can map to a single or to multiple resources in an application.
|
||||
|
||||
In order to protect it, we need to create the authorization policies and permissions we want to apply. Policies define the conditions that must be satisfied to grant a permission. Where a
|
||||
permission is the link between a resource and the policies(or conditions) we want to enforce when someone wants to access a resource.
|
||||
|
||||
In this example, we have a single policy *Only Special Users Policy*. This policy tells that only the specified users are allowed to access _something_ (we don't know what, yet. That is up to the permission).
|
||||
|
||||
[NOTE]
|
||||
{{book.project.name}} provides a few link:../policy/overview.html[policy types] that you can start using out-of-the-box. There are policies for RBAC, time constraints or even rules written using JavaScript or JBoss Drools.
|
||||
|
||||
The last step when protecting a resource is to define a permission. For that, we have defined a *Hello World Resource Permission* that links the resource we want to protect, _Hello World Resource_, with the
|
||||
policy we want to apply to that resource, _Only Special Users Policy_.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Hello World Resource Permission",
|
||||
"type": "resource",
|
||||
"config": {
|
||||
"resources": "[\"Hello World Resource\"]",
|
||||
"applyPolicies": "[\"Only Special Users Policy\"]"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
1
topics/getting-started/hello-world-entitlement.adoc
Executable file
1
topics/getting-started/hello-world-entitlement.adoc
Executable file
|
@ -0,0 +1 @@
|
|||
== Obtaining Permissions using the Entitlement API
|
126
topics/getting-started/hello-world-uma.adoc
Executable file
126
topics/getting-started/hello-world-uma.adoc
Executable file
|
@ -0,0 +1,126 @@
|
|||
== Obtaining Permissions using UMA
|
||||
|
||||
One of the main features provided by {{book.project.name}} {{book.project.module}} is support for https://docs.kantarainitiative.org/uma/rec-uma-core.html[UMA] specification. UMA defines :
|
||||
|
||||
"`... how resource owners can control protected-resource access by clients operated by arbitrary requesting parties, where the resources reside on any number of resource servers, and where a centralized authorization server governs access based on resource owner policies.`"
|
||||
|
||||
[NOTE]
|
||||
For now, {{book.project.name}} focus on API security and doesn't fully implement UMA. We don't support, yet, authorization flows where the user can manage their own resources and policies, share resources, and so forth.
|
||||
|
||||
=== Obtaining a Protection API Token (PAT)
|
||||
The authorization process begins with a special type of token: the *Permission Ticket*. A permission ticket can only be obtained by a resource server using a OAuth2 access token with the scope
|
||||
*uma_protection*. In UMA, the access token with the scope *uma_protection* is called a Protection API Token or PAT.
|
||||
|
||||
When we created the *hello-world-authz* realm, we also granted the scope *uma_protection* to the *hello-world-authz-service* client application. Considering that this application is also a *confidential* client,
|
||||
you can obtain a PAT as follows:
|
||||
|
||||
```curl
|
||||
curl -X POST \
|
||||
-H "Authorization: Basic aGVsbG8td29ybGQtYXV0aHotc2VydmljZTpwYXNzd29yZA==" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d 'grant_type=client_credentials' \
|
||||
"http://localhost:8080/auth/realms/hello-world-authz/protocol/openid-connect/token"
|
||||
```
|
||||
|
||||
As a result, you will get the following response from the server:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": ${access_token},
|
||||
"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"
|
||||
}
|
||||
```
|
||||
|
||||
Here, the ${access_token} represents a PAT, which is basically an OAuth2 access token with the scope *uma_protection*. With this token, you can now obtain a permission ticket from the
|
||||
Protection API.
|
||||
|
||||
=== Obtaining a Permission Ticket
|
||||
|
||||
Once you have a PAT, you can use it to access the Protection API in order to obtain a permission ticket as follows:
|
||||
|
||||
```json
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${access_token}" \
|
||||
-d '
|
||||
{
|
||||
"resource_set_id" : "Hello World Resource"
|
||||
}
|
||||
' \
|
||||
"http://localhost:8080/auth/realms/hello-world-authz/authz/protection/permission"
|
||||
```
|
||||
|
||||
As a result, you will get the following response from the server:
|
||||
|
||||
```json
|
||||
{
|
||||
"ticket": ${permission_ticket}
|
||||
}
|
||||
```
|
||||
|
||||
Here, we are asking the Protection API for a permission ticket representing the same extent of requested access that the resource server registered. In this case, the access request to
|
||||
the _Hello World Resource_ resource.
|
||||
|
||||
=== Obtaining an Authorization API Token
|
||||
|
||||
The final step is obtain a *Requesting Party Token* or *RPT* with a set of authorization data, that will be used to actually gain access to protected resources at the resource server.
|
||||
|
||||
A RPT is obtained from the link:../service/authorization-api.html[Authorization API]. Just like the Protection API and Permission Tickets, the Authorization API also requires a special
|
||||
OAuth2 access token to obtain a RPT. In UMA, this access token is called a *Authorization API Token* or *AAT*, which contains a scope *uma_authorization* in it.
|
||||
|
||||
The *uma_authorization* scope indicates that an user consented to allow a given client application to access authorization data on his behalf. When we created the *hello-world-authz* realm,
|
||||
we also defined a client role to the users:
|
||||
|
||||
```json
|
||||
"clientRoles" : {
|
||||
"hello-world-authz-service" : [ "uma_authorization" ]
|
||||
}
|
||||
```
|
||||
|
||||
To obtain an AAT you can send a request to {{book.project.name}} as follows:
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Basic aGVsbG8td29ybGQtYXV0aHotc2VydmljZTpwYXNzd29yZA==" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d 'username=alice&password=password&grant_type=password' \
|
||||
"http://localhost:8080/auth/realms/hello-world-authz/protocol/openid-connect/token"
|
||||
```
|
||||
As a result, you will get the following response from the server:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": ${access_token},
|
||||
"expires_in": 300,
|
||||
"refresh_expires_in": 1800,
|
||||
"refresh_token": ${refresh_token},
|
||||
"token_type": "bearer",
|
||||
"id_token": ${id_token},
|
||||
"not-before-policy": 0,
|
||||
"session_state": "3cad2afc-855b-47b7-8e4d-a21c66e312fb"
|
||||
}
|
||||
```
|
||||
|
||||
=== Obtaining a Requesting Party Token or RPT
|
||||
|
||||
Now that we have a AAT we can call the Authorization API and ask for a RPT.
|
||||
|
||||
```bash
|
||||
curl -X POST
|
||||
-H "Authorization: Bearer ${AAT}" -d '{
|
||||
"ticket" : ${permission_ticket}
|
||||
}' "http://localhost:8080/auth/realms/hello-world-authz/authz/authorize"
|
||||
```
|
||||
|
||||
As a result, you will get the follow response from the server:
|
||||
|
||||
```json
|
||||
{"rpt":"${rpt}"}
|
||||
```
|
||||
|
||||
=== Obtaining Permissions using the Entitlement API
|
350
topics/getting-started/hello-world.adoc
Executable file
350
topics/getting-started/hello-world.adoc
Executable file
|
@ -0,0 +1,350 @@
|
|||
== Hello Authorization World
|
||||
|
||||
This guide will show you how to:
|
||||
|
||||
* Create a realm with the necessary configuration to enable fine-grained authorization to your applications
|
||||
* Create a resource server and the resources that must be protected
|
||||
* Create permissions, authorization policies and how to apply them to your protected resources
|
||||
* Access the {{book.project.name}} {{book.project.module}} and enforce authorization decisions
|
||||
|
||||
The purpose of this guide is to give you a generic overview of {{book.project.name}} {{book.project.module}} so you can understand
|
||||
some core concepts and start protecting your applications and services despite the platform they are running on.
|
||||
|
||||
=== Creating the Hello World AuthZ Realm
|
||||
|
||||
For this guide, we are going to create a *hello-world-authz* realm. Just import the following JSON file to create the new realm:
|
||||
|
||||
```json
|
||||
{
|
||||
"realm" : "hello-world-authz",
|
||||
"enabled" : true,
|
||||
"privateKey" : "MIIEpQIBAAKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQABAoIBAAwa4wVnKBOIS6srmYPfBTDNsTBBCEjxiYEErmn7JhoWxQ1DCPUxyxU6F177/q9Idqoj1FFOCtEO9P6/9+ym470HQmEQkR2Xxd1d3HOZy9oKuCro3ZbTDkVxY0JnlyxZz4MihGFxDH2e4MArfHy0sAgYbdIU+x2pWKGWSMzDd/TMSOExhc/sIQAg6ljbPCLLXCPQFAncoHRyGPrkRZs6UTZi5SJuCglVa2/3G+0drDdPuA83/mwsZfIBqQgbGbFgtq5T5C6CKMkPOQ42Rcclm7kEr6riTkJRo23EO1iOJVpxzI0tbxZsJAsW7zeqv0wWRyUgVfQAje6OdsNexp5aCtECgYEA6nMHCQ9xXvufCyzpIbYGxdAGqH6m1AR5gXerHqRiGNx+8UUt/E9cy/HTOhmZDK/eC4BT9tImeF01l1oSU/+wGKfux0SeAQchBhhq8GD6jmrtgczKAfZHp0Zrht7o9qu9KE7ZNWRmY1foJN9yNYmzY6qqHEy+zNo9amcqT7UZKO8CgYEA35sp9fMpMqkJE+NEJ9Ph/t2081BEkC0DYIuETZRSi+Ek5AliWTyEkg+oisTbWzi6fMQHS7W+M1SQP6djksLQNPP+353DKgup5gtKS+K/y2xNd7fSsNmkjW1bdJJpID7WzwwmwdahHxpcnFFuEXi5FkG3Vqmtd3cD0TYL33JlRy0CgYEA0+a3eybsDy9Zpp4m8IM3R98nxW8DlimdMLlafs2QpGvWiHdAgwWwF90wTxkHzgG+raKFQVbb0npcj7mnSyiUnxRZqt2H+eHZpUq4jR76F3LpzCGui2tvg+8QDMy4vwqmYyIxDCL8r9mqRnl3HpChBPoh2oY7BahTTjKEeZpzbR0CgYEAoNnVjX+mGzNNvGi4Fo5s/BIwoPcU20IGM+Uo/0W7O7Rx/Thi7x6BnzB0ZZ7GzRA51paNSQEsGXCzc5bOIjzR2cXLisDKK+zIAxwMDhrHLWZzM7OgdGeb38DTEUBhLzkE/VwYZUgoD1+/TxOkwhy9yCzt3gGhL1cF//GJCOwZvuECgYEAgsO4rdYScgCpsyePnHsFk+YtqtdORnmttF3JFcL3w2QneXuRwg2uW2Kfz8CVphrR9eOU0tiw38w6QTHIVeyRY8qqlHtiXj6dEYz7frh/k4hI29HwFx43rRpnAnN8kBEJYBYdbjaQ35Wsqkfu1tvHJ+6fxSwvQu/TVdGp0OfilAY=",
|
||||
"publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQAB",
|
||||
"certificate" : "MIICsTCCAZkCBgFVETX4AzANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFIZWxsbyBXb3JsZCBBdXRoWjAeFw0xNjA2MDIxMzAxMzdaFw0yNjA2MDIxMzAzMTdaMBwxGjAYBgNVBAMMEUhlbGxvIFdvcmxkIEF1dGhaMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzMhNM9HXNQWhVf1m64zS67SIyQjj+tV5GR+MqlRTWDXdo8GAWHd+alY1urRhfRoqMy4F499+8wh2REKFykNt0ng6s6wWnEaKDboS3SAUV6lybcOAkwIOCtCZj1ItddKG3m64fzxDDQrcpkbiAvw3S8KJ4UJK+pyh9iX01duSDtM/HhPawsPdY8JSMfuo1IxQ2Vxw+8RKwbbdUeew6cyYGYAeFYwA66mlM3otB0RBHh4bjwg8297+2g53TdwM2rbCHRbrorMQD3031OTyFSp7lXCtoMLWRfAFnOP/2yZWZMXbiJheC0R3sLbU7Ef0/cUbYyk4Ckfq6pcYDR+VZBF7AwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQANm5gIT/c50lwjawM686gNXpppLA928WsCOn9NIIWjSKekP8Bf9S73kf7vWcsEppm5B8rRyRxolXmzwghv74L7uVDg8Injjgj+XbPVQP+cJqWpSaMZHF7UfWe0/4M945Xcbmsl5q+m9PmrPG0AaaZhqXHcp4ehB1H+awyRqiERpJUuwZNycw2+2kjDADpsFf8hZVUd1F6ReYyOkqUyUjbL+jYTC7ZBNa7Ok+w6HCXWgkgVATAgQXJRM3w14IOc5MH/vfMCrCl/eNQLbjGl9y7u8PKwh3MXHDO2OLqtg6hOTSrOGUPJZGmGtUAl+2/R7FzoWkML/BNe2hjsL6UJwg91",
|
||||
"requiredCredentials" : [ "password" ],
|
||||
"roles" : {
|
||||
"realm" : [
|
||||
{
|
||||
"name" : "uma_protection", "kc_entitlements"
|
||||
}
|
||||
]
|
||||
},
|
||||
"users" :
|
||||
[
|
||||
{
|
||||
"username" : "alice",
|
||||
"enabled" : true,
|
||||
"credentials" : [ {
|
||||
"type" : "password",
|
||||
"value" : "password"
|
||||
} ],
|
||||
"clientRoles" : {
|
||||
"hello-world-authz-service" : [ "uma_authorization", "kc_entitlement" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "jdoe",
|
||||
"enabled" : true,
|
||||
"credentials" : [ {
|
||||
"type" : "password",
|
||||
"value" : "password"
|
||||
} ],
|
||||
"clientRoles" : {
|
||||
"hello-world-authz-service" : [ "uma_authorization", "kc_entitlement" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "service-account-hello-world-authz-service",
|
||||
"enabled" : true,
|
||||
"serviceAccountClientId" : "hello-world-authz-service",
|
||||
"realmRoles" : [ "uma_protection"]
|
||||
}
|
||||
],
|
||||
"clients" : [
|
||||
{
|
||||
"clientId" : "hello-world-authz-service",
|
||||
"secret" : "password",
|
||||
"serviceAccountsEnabled" : true,
|
||||
"enabled" : true,
|
||||
"redirectUris" : [ "http://localhost:8080/hello-world-authz-service" ],
|
||||
"directAccessGrantsEnabled" : true,
|
||||
"publicClient" : false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
The realm *hello-world-authz* consists of:
|
||||
|
||||
** Two users: _alice_ and _jdoe_
|
||||
** One client application: _hello-world-authz-service_
|
||||
** One global role: _uma_protection_
|
||||
** One client role: _uma_authorization_
|
||||
|
||||
The _hello-world-authz-service_ is the application with the resources we want to protect. In other words, it will act as a link:../overview/terminology.html[Resource Server].
|
||||
|
||||
In {{book.project.name}} a resource server is just a regular client application with some specific characteristics. It _must_ be a *confidential* client application as defined by:
|
||||
|
||||
```json
|
||||
"publicClient" : false
|
||||
```
|
||||
|
||||
It must have a *client_id*, *client_secret* and *Service Account* enabled:
|
||||
|
||||
```json
|
||||
"clients" : [
|
||||
{
|
||||
"clientId" : "hello-world-authz-service",
|
||||
"secret" : "7801be7c-437a-44ae-be34-67da32f024eb",
|
||||
"serviceAccountsEnabled" : true,
|
||||
...
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
And finally, an user mapping to the client's service account:
|
||||
|
||||
```json
|
||||
{
|
||||
"username" : "service-account-my-resource-server",
|
||||
"enabled" : true,
|
||||
"serviceAccountClientId" : "hello-world-authz-service",
|
||||
"realmRoles" : [ "uma_protection"]
|
||||
}
|
||||
```
|
||||
|
||||
In the latter case, we are also granting the *uma_protection* role to the client's service account. As you'll see, that will be necessary in order to get access to the link:../service/protection-api.html[Protection API].
|
||||
|
||||
=== Creating a Resource Server and Protecting Resources
|
||||
|
||||
Now that we have the *hello-world-authz* realm properly configured, we need to enable the *hello-world-authz-service* as a resource server. For that, click on the *Authorization* in the left menu bar.
|
||||
|
||||
image:../../images/gs-keycloak-authz-page.png[alt="Keycloak Authorization Page"]
|
||||
|
||||
To create a resource server you can click on the *Create* button.
|
||||
|
||||
image:../../images/gs-keycloak-authz-create-rs-page.png[alt="Create Resource Server"]
|
||||
|
||||
From that page you can create a resource server by manually filling that form or you can just import a JSON file with the configuration you want. For this guide, we'll just import a JSON file as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"clientId": "hello-world-authz-service",
|
||||
"resources": [
|
||||
{
|
||||
"name": "Hello World Resource"
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"name": "Only Special Users Policy",
|
||||
"type": "user",
|
||||
"logic": "POSITIVE",
|
||||
"config": {
|
||||
"users": "[\"alice\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hello World Resource Permission",
|
||||
"type": "resource",
|
||||
"config": {
|
||||
"resources": "[\"Hello World Resource\"]",
|
||||
"applyPolicies": "[\"Only Special Users Policy\"]"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
After importing the JSON file above, you would see a page like that:
|
||||
|
||||
image:../../images/gs-authz-hello-rs-created-page.png[alt="Resource Server Successfully Created"]
|
||||
|
||||
You may take some time now exploring the resource server we just created. But first, let's understand what we just created.
|
||||
|
||||
The resource server was created based on the *hello-world-authz-service* client application, as you can see from the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"clientId": "hello-world-authz-service",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
What we did was basically tell {{book.project.name}} that we want that client application acting as a resource server, so we can start creating the resources we want to protect as well the permissions
|
||||
and authorization policies we want to use to actually protect the resources.
|
||||
|
||||
The purpose of this guide is keep things simple to get you started, so our newly created resource server has a single protected resource, as defined by the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"resources": [
|
||||
{
|
||||
"name": "Hello World Resource"
|
||||
}
|
||||
],
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The *Hello World Resource* represents a set of one or more resources we want to protect. It can map to a single or to multiple resources in an application.
|
||||
|
||||
In order to protect it, we need to create the authorization policies and permissions we want to apply. Policies define the conditions that must be satisfied to grant a permission. Where a
|
||||
permission is the link between a resource and the policies(or conditions) we want to enforce when someone wants to access a resource.
|
||||
|
||||
In this example, we have a single policy *Only Special Users Policy*. This policy tells that only the specified users are allowed to access _something_ (we don't know what, yet. That is up to the permission).
|
||||
|
||||
[NOTE]
|
||||
{{book.project.name}} provides a few link:../policy/overview.html[policy types] that you can start using out-of-the-box. There are policies for RBAC, time constraints or even rules written using JavaScript or JBoss Drools.
|
||||
|
||||
The last step when protecting a resource is to define a permission. For that, we have defined a *Hello World Resource Permission* that links the resource we want to protect, _Hello World Resource_, with the
|
||||
policy we want to apply to that resource, _Only Special Users Policy_.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Hello World Resource Permission",
|
||||
"type": "resource",
|
||||
"config": {
|
||||
"resources": "[\"Hello World Resource\"]",
|
||||
"applyPolicies": "[\"Only Special Users Policy\"]"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
== Obtaining Permissions using the Entitlement API
|
||||
|
||||
In {{book.project.name}}, authorization data is represented by a special security token called a *Requesting Party* Token or *RPT*. This token consists of all the permissions granted
|
||||
to an user as a result of the evaluation of the permissions and authorization policies associated with the resource being requested. In this guide, we'll see how to obtain a RPT using the link:../service/entitlement-api.html[Entitlement API].
|
||||
|
||||
Now that the *hello-world-authz* realm is properly configured with the resources we want to protected and their corresponding permissions and authorization policies, we can ask the server for what an user
|
||||
is entitled to do. In other words, what are the permissions the user has.
|
||||
|
||||
The Entitlement API consists of a single endpoint. This endpoint expects an OAuth2 Access Token with a scope *kc_entitlement* representing the user identity and the identifier of the resource server we want to access. The *kc_entitlement*
|
||||
scope is necessary to indicate that the user consented access to his authorization data from a given client application. The access token with a scope *kc_entitlement* is called *Entitlement API Token* or *EAT*.
|
||||
|
||||
In this case, for the sake of simplicity, our resource server *hello-world-authz-service* is also acting as a client application. What means that the user allowed this application to access his
|
||||
authorization data and obtain RPTs on his behalf.
|
||||
|
||||
The first step is obtain a EAT as follows:
|
||||
|
||||
```bash
|
||||
curl -X POST
|
||||
-H "Authorization: Basic aGVsbG8td29ybGQtYXV0aHotc2VydmljZTpwYXNzd29yZA=="
|
||||
-H "Content-Type: application/x-www-form-urlencoded"
|
||||
-d 'username=alice&password=password&grant_type=password'
|
||||
"http://localhost:8080/auth/realms/hello-world-authz/protocol/openid-connect/token"
|
||||
```
|
||||
|
||||
Here we are using the _Resource Owner Password Credentials Grant Type_ (Direct Access Grant in Keycloak terminology), as defined by OAuth2 specification, to obtain an EAT on behalf of _alice_. As a result, you
|
||||
would get a response from the server as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": ${EAT},
|
||||
"expires_in": 300,
|
||||
"refresh_expires_in": 1800,
|
||||
"refresh_token": ${refresh_token},
|
||||
"token_type": "bearer",
|
||||
"id_token": ${id_token},
|
||||
"not-before-policy": 0,
|
||||
"session_state": "1ad4d54c-7758-4698-92d3-d57d821f130b"
|
||||
}
|
||||
```
|
||||
|
||||
[NOTE]
|
||||
Resource Owner Password Credentials Grant Type is only used here for demonstration purposes. In the real world, you'll usually obtain an access token using a more secure grant type such as _authorization_code_.
|
||||
The idea here is demonstrate that you need to obtain an access token with the scope *kc_entitlement* prior to access the Entitlement API to ask for a RPT.
|
||||
|
||||
Finally, the last step is obtain a RPT from the Entitlement API as follows:
|
||||
|
||||
```bash
|
||||
curl -X GET \
|
||||
-H "Authorization: Bearer ${EAT}" \
|
||||
"http://localhost:8080/auth/realms/hello-world-authz/authz/entitlement/hello-world-authz-service"
|
||||
```
|
||||
|
||||
As result, you'll get a response from the server as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"rpt": ${RPT}
|
||||
}
|
||||
```
|
||||
|
||||
By default, {{book.project.name}} issues a RPT consisting of permissions for every single resource protected/managed by the resource server. If you want to limit the permissions to only a specific
|
||||
set of resources you can request a RPT as follows:
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer %{EAT}" \
|
||||
-d '{
|
||||
"permissions" : [
|
||||
{
|
||||
"resource_set_name" : "Hello World Resource"
|
||||
}
|
||||
]
|
||||
}' \
|
||||
"http://localhost:8080/auth/realms/hello-world-authz/authz/entitlement/hello-world-authz-service"
|
||||
```
|
||||
|
||||
Let's see now what happens when the user does not have access to a protected resource at the resource server. For that, let's obtain a new EAT but now using _jdoe_ credentials.
|
||||
|
||||
```bash
|
||||
curl -X POST
|
||||
-H "Authorization: Basic aGVsbG8td29ybGQtYXV0aHotc2VydmljZTpwYXNzd29yZA=="
|
||||
-H "Content-Type: application/x-www-form-urlencoded"
|
||||
-d 'username=jdoe&password=password&grant_type=password'
|
||||
"http://localhost:8080/auth/realms/hello-world-authz/protocol/openid-connect/token"
|
||||
```
|
||||
|
||||
Just like we did with _alice_, the server will return an EAT that we can use to obtain a RPT. But _jdoe_ is not supposed to access the protected resource, so the server is going to give you a response as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"error_description": "Authorization denied.",
|
||||
"error": "not_authorized"
|
||||
}
|
||||
```
|
||||
|
||||
The reason for that is that the _Hello World Resource_ is protected by a _Only Special Users Policy_ that says that only _alice_ is allowed to access the resource. You can play around now by changing that policy to
|
||||
include _jdoe_ as a valid user and see the results.
|
||||
|
||||
== Enforcing Authorization Decisions
|
||||
|
||||
A RPT is basically a https://tools.ietf.org/html/rfc7519[JSON Web Token (JWT)] digitally signed using https://www.rfc-editor.org/rfc/rfc7515.txt[JSON Web Signature (JWS)]. Its lifetime is the same as with the OAuth2 access token (EAT) that was used to obtain it.
|
||||
|
||||
When you decode a RPT you will see something like that:
|
||||
|
||||
```json
|
||||
{
|
||||
"permissions": [
|
||||
{
|
||||
"resource_set_id": "152251e6-f4cf-4464-8d91-f1b7960fa5fc",
|
||||
"resource_set_name": "Hello World Resource"
|
||||
"scopes": []
|
||||
}
|
||||
],
|
||||
"accessToken": ${EAT},
|
||||
"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"
|
||||
}
|
||||
```
|
||||
|
||||
The *permissions* claim lists all resource (and their respective scopes if any) permissions granted by the server. There is also a *accessToken* property holding the EAT that was used to issue the RPT.
|
||||
|
||||
The RPT provides everything you need to enforce authorization decisions at the resource server side. That can be easily accomplished by:
|
||||
|
||||
* Validating the RPT signature (based on realm's public key)
|
||||
* Checking the token validity
|
||||
* Decoding the RPT and extracting the permissions
|
||||
* Checking if a request sent to the resource server trying to access a protected resource matches any permissions within the RPT
|
||||
|
||||
You can even use the information within a RPT to protect resources within a page like buttons or any other visual component.
|
Loading…
Reference in a new issue