OAuth2 Based Security For OData Using KeyCloak

This document will provide detailed instructions to enable OAuth V2 authentication on Teiid’s OData interface using the Keycloak as authentication server (IDP). Please note that use a different IDP server will not work with this implementation as OAuth2 implementations are not interoperable. To work with separate IDP than Keycloak consult their documentation, replace the web layer semantics, like the "login-config" in web.xml file etc. Providing the details of other IDP is beyond the scope of this document.

This examples will show case an example, where Teiid’s OData rest interface is secured using OAuth using Keycloak using OpenID Connect. The VDB accessed by the OData interface also depends on another web service which is used as a data source, that is also secured with OAuth using the same Keycloak IDP. The central idea behind this example is to pass the same "access-token" used at OData interface layer to passthrough the Teiid layer to bottom data source layer and gain access to the source.

Download and install Keycloak as a separate web server.

  • Login using the default "admin/admin" credentials into the Keycloak "master" realm.

  • Add a new realm called "oauth-demo"

realm

  • Add a new user called "user" and add credentials.

users

Add two roles "odata" and "user". These are enterprise roles, that will be used by the web services to grant the access to user. Also these roles are used as "scopes" in the OAuth protocol.

roles

  • Add a new client called "odata4-oauth", this client represents the Teiid’s OData client that we are going to create

odata-client

and choose scopes "odata" and "user" for this client. Note that the redirect URI needs to be where the actual service is going to be available.

Note
The client web-service typically defines what roles that logged in user must have in order for to grant the access. In the Keycloak OAuth implementation, these roles are used as "scopes". Note that the "odata4-oauth" client MUST have ALL the scopes that it is going to delegate the access-token for gaining access to bottom data services. In this example Teiid’s OData web services requires "odata" role, the bottom web-service requires the "user" role. Since the OData accesses the bottom web-service it requires both the roles.

odata-client-scope

  • Add another client called "database-service" and choose scope "user". Choose type as "Bearer".

database-client

Install and configure Teiid server

  • Download and install Teiid server

  • Download Keycloak adapter for the EAP, and unzip over the Teiid server installation or follow Keycloak installtion directions.

Run the following to add Keycloak specific modules to the server

$ cd $WILDFLY_HOME
$ unzip keycloak-wildfly-adapter-dist-${version}.zip

Now, start the Teiid Server and using the jboss-cli.sh file run the following to install the KeyCloak configuration into the Teiid Server.

./bin/jboss-cli.sh --file=adapter-install.cli

Then we need to change the OData transport’s "security-domain" to "keycloak".

./bin/jboss-cli.sh --connect
/subsystem=teiid:write-attribute(name=authentication-security-domain, value=keycloak)

reload

above commands will result in XML in standalone.xml or domain.xml file like (you can also edit standalone.xml directly)

    <authentication security-domain="keycloak"/>
    <transport name="odata"/>

The Keycloak is installed and the OData transport is modified, now we need to install security-domain called "passthrough". Note that the web layer is using OAuth2, but at the VDB layer, this logged in user need to be passed through and this security domain will help with that.

./bin/jboss-cli.sh --connect
/subsystem=security/security-domain=passthrough:add(cache-type=default)
/subsystem=security/security-domain=passthrough/authentication=classic:add
/subsystem=security/security-domain=passthrough/authentication=classic/login-module=passthrough:add(code=org.teiid.jboss.PassthroughIdentityLoginModule, flag=required, module=org.jboss.teiid)

reload

above commands will result in XML in standalone.xml or domain.xml file like (you can also edit standalone.xml directly)

    <security-domain name="passthrough">
        <authentication>
            <login-module code="org.teiid.jboss.PassthroughIdentityLoginModule" flag="required" module="org.jboss.teiid"/>
        </authentication>
    </security-domain>

This finishes all the server side changes that are required to make OAuth authentication using Keycloak.

OData Application WAR

In order to use OAuth2 authentication, the OData WAR needs to be updated to make use of the OAuth2 based security domain. By default Teiid installation comes with OData web service WAR file configured with "HTTP Basic" authentication. This WAR needs to either replaced or updated.

Build the new OData WAR file that supports OAuth.

To build OAuth based OData WAR file, Teiid provides a template maven project, either download or clone the project from https://github.com/teiid/teiid-web-security

The above link provides templates for creating two WAR files, one WAR file is to create Teiid’s OData service with OAuth, the next is a sample "database-service" for this demo. Please note that "database-service" is to mimic the database service, that will be different in a real use-case, however the steps defined for the access will be same.

Replace the "teiid-web-security/teiid-odata-oauth-keycloak/src/main/webapp/WEB-INF/keyclock.json" file contents with "installation" script in "keycloak.json" format from Keycloak admin console’s "odata4-client" client application.

Similarly replace the "teiid-web-security/examples/database-service/src/main/webapp/WEB-INF/keyclock.json" file contents with "installation" script in "keycloak.json" format from Keycloak admin console’s "database-client" client application.

Edit the "teiid-web-security/odata-oauth-keycloak/src/main/webapp/WEB-INF/web.xml" file to enable Passthrough Authentication

    <init-param>
        <param-name>PassthroughAuthentication</param-name>
        <param-value>true</param-value>
    </init-param>

Build the WAR files running the maven command

mvn clean package
Note
You may have to update Teiid and Keycloak versions in the pom.xml file

The above command will generate a new WAR file for deployment. Follow the below directions to deploy this new WAR file.

Teiid Server on WildFly

Replace the <wildfly>/modules/system/layers/dv/org/jboss/teiid/main/deployments/teiid-olingo-odata4.war" file with new WAR file, by executing a command similar to

cp teiid-web-security/odata-oauth-keycloak/target/teiid-odata-oauth-keycloak-{version}.war \
   <wildfly>/modules/system/layers/dv/org/jboss/teiid/main/deployments/teiid-olingo-odata4.war

JDV Server

If you are working with JDV 6.3 server or greater, then run the following CLI script, you may have change the below script to adopt to the correct version of the WAR and directory names where the content is located.

undeploy teiid-olingo-odata4.war
deploy teiid-web-security/odata-oauth-keycloak/target/teiid-odata-oauth-keycloak-{version}.war

or overlay the new one using CLI script like

deployment-overlay add --name=myOverlay --content=/WEB-INF/web.xml=teiid-web-security/odata-oauth-keycloak/src/main/webapp/WEB-INF/web.xml,/WEB-INF/jboss-web.xml=teiid-web-security/odata-oauth-keycloak/src/main/webapp/WEB-INF/jboss-web.xml,/META-INF/MANIFEST.MF=teiid-web-security/odata-oauth-keycloak/src/main/webapp/META-INF/MANIFEST.MF,/WEB-INF/keycloak.json=teiid-web-security/odata-oauth-keycloak/src/main/webapp/WEB-INF/keycloak.json /WEB-INF/lib/teiid-odata-oauth-keycloak-{version}.jar=teiid-web-security/odata-oauth-keycloak/src/main/webapp/WEB-INF/lib/teiid-odata-oauth-keycloak-{version}.jar --deployments=teiid-olingo-odata4.war --redeploy-affected

Working with example VDB

<vdb name="oauthdemo" version="1">
    <model visible="true" name="PM1">
        <source name="any" translator-name="loopback"/>
        <metadata type = "DDL"><![CDATA[
            CREATE FOREIGN TABLE G1 (e1 integer PRIMARY KEY, e2 varchar(25), e3 double);
        ]]>
        </metadata>
    </model>
</vdb>

Start both Keycloak and Teiid Servers. If both of these servers are in the same machine, then we need to offset the ports of Teiid server such that they will not conflict with that of the Keycloak server. For this example, I started the Teiid server as

./standalone.sh -c standalone-teiid.xml -Djboss.socket.binding.port-offset=100

where all ports are offset by 100. So the management port is 10090 and default JDBC port will be 31100. The Keycloak server is started on default ports.

Testing the example

There are two different mechanisms for testing this example. One is purely for testing the using the browser, then other is programatically. Typically using the browser is NOT correct for accessing the Teiid’s OData service, but it is shown below for testing purposes.

Using the Web Browser

Using the browser issue a query (the use of browser is needed because, this process does few redirects only browsers can automatically follow)

http://localhost:8180/odata4/oauthdemo/PM1/G1

The user will be presented with Keycloak based login page, once the credentials are presented the results of the above request are shown.

Calling programatically

This process of calling does not need to involve a web-browser, this is typical of scenario where another web-application or mobile application is calling the Teiid’s OData web-service to retrieve the data. However in this process, the process of negotiating the "access-token" is externalized and is defined by the IDP, which in this case is Keycloak.

For demonstration purposes we can use CURL to negotiate this token as shown below (client_secret can found the Keycloak admin console under client credentials tab)

curl -v POST http://localhost:8080/auth/realms/oauth-demo/protocol/openid-connect/token  -H "Content-Type: application/x-www-form-urlencoded" -d 'username=user' -d 'password=user' -d 'grant_type=password' -d 'client_id=odata4-oauth' -d 'client_secret=36fdc2b9-d2d3-48df-8eea-99c0e729f525'

this should return a JSON payload similar to

{  "access_token":"eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI0YjI4NDMzYS1..",
   "expires_in":300,
   "refresh_expires_in":1800,
   "refresh_token":"eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJmY2JmNjY2ZC0xNzIwLTQwODQtOTBiMi0wMjg4ODdhNDkyZWYiLCJl..",
   "token_type":"bearer",
   "id_token":"eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiIwZjYyNDQ1MS1iNTE0LTQ5YjUtODZlNy1jNTI5MDU2OTI3ZDIiLCJleH..",
   "not-before-policy":0,
   "session-state":"6c8884e8-c5aa-4f7a-a3fe-9a7f6c32658c"
}

from the above you can take the "access_token" and issue the query to fetch results like

curl -k -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI0YjI4NDMzYS1.." http://localhost:8180/odata4/oauthdemo/PM1/G1

You should see same XML response as above. Please note that to programatically achieve the access_token in your own program (not using curl) you can see some suggestions in this document [https://keycloak.gitbooks.io/documentation/server_development/topics/admin-rest-api.html]

results matching ""

    No results matching ""