October 6, 2019

Getting Started with RH SSO 7.3 (Keycloak)

Introduction

In this blog we are going to install and configure RH SSO 7.3 (upstream Keycloak) from scratch and then develop a simple Java Web application, protected with RH SSO (Keycloak).

We are going to run the RH SSO 7.3 and JBoss EAP 7.2 on the same machine and locally, but before that we need to install Java 11 (OpenJDK 11). Since this is a developing machine we are going to install every OpenJDK 11 packages.


$ sudo dnf install -y java-11-openjdk java-11-openjdk-src java-11-openjdk-demo java-11-openjdk-devel java-11-openjdk-jmods java-11-openjdk-javadoc java-11-openjdk-headless java-11-openjdk-javadoc-zip

If you have multiple of Java versions installed, please set Java 11 as default


$ sudo update-alternatives --config java

$ java -version
openjdk version "11.0.4" 2019-07-16
OpenJDK Runtime Environment 18.9 (build 11.0.4+11)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.4+11, mixed mode, sharing)

RH SSO 7.3 (Keycloak)

Now install RH SSO from ZIP (in production you would install it via RPM).


$ unzip rh-sso-7.3.0.GA.zip
$ mv rh-sso-7.3 rh-sso-7.3.0
$ cd rh-sso-7.3.0/bin/

$ ./standalone.sh

Now configure Admin account, by open http://localhost:8080/auth/ and then open http://localhost:8080/auth/admin/ and login as Admin.

  1. In the left menu create a new Realm 'demo'.
  2. In the demo realm create a new User with username 'joe'
  3. After created User, click on User Credential tab.
  4. And reset User 'joe' password and don't forget to set Temporary to false.
  5. Then create a new Role, by clicking on left menu Role and then Add role.
  6. Lets name the Role to 'OIDCDEMO_USER'.
  7. Go back to User by clicking on User in lefter meny and find user 'joe'.
  8. After clicking on User 'joe', then click on tab Role Mappings.
  9. Select our newly created Role 'OIDCDEMO_USER' under Available Roles and click Add selected.

Web Application Server - JBoss EAP 7.2

Install via zip (in production you would install via RPM). After extracting the zip files, we install OpenID Connector to EAP via adapter-elytron-install-offline.cli and then starts EAP with a port offset of 100. To test it open http://localhost:8180/


$ unzip jboss-eap-7.2.0.zip
$ mv jboss-eap-7.2 jboss-eap-7.2.0-oidc
$ unzip rh-sso-7.3.0.GA-eap7-adapter.zip -d jboss-eap-7.2.0-oidc
$ cp jboss-eap-7.2.0-oidc/standalone/configuration/standalone.xml jboss-eap-7.2.0-oidc/standalone/configuration/standalone.xml.ORIG

$ cd jboss-eap-7.2.0-oidc/bin
$ ./jboss-cli.sh --file=adapter-elytron-install-offline.cli

$ ./standalone.sh -Djboss.socket.binding.port-offset=100

The Java EE 8 Web Application

Now lets create a simple Java EE 8 Web Application, with a single index.jsp and a logout.jsp.


$ mkdir rh-sso-7.3-demo
$ cd rh-sso-7.3-demo 
$ mkdir -p src/main/java/se/magnuskkarlsson/example/oidc
$ mkdir -p src/main/resources/META-INF
$ mkdir -p src/main/webapp/WEB-INF
$ touch src/main/webapp/WEB-INF/web.xml
$ mkdir -p src/test/java/se/magnuskkarlsson/example/oidc
$ mkdir -p src/test/resources
$ vi pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>se.magnuskkarlsson.examples</groupId>
    <artifactId>rh-sso-7.3-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.build.outputEncoding>UTF-8</project.build.outputEncoding>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>8.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- Test Support -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>2.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.10.19</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.199</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>11</release>
                    <showDeprecation>true</showDeprecation>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Now the important part of web.xml, where we configure our security.


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Unprotected pages</web-resource-name>
            <url-pattern>/logout.jsp</url-pattern>
        </web-resource-collection>
        <!-- <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> -->
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Protected pages</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>OIDCDEMO_USER</role-name>
        </auth-constraint>
        <!-- <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> -->
    </security-constraint>

    <login-config>
        <auth-method>KEYCLOAK</auth-method>
        <!-- <realm-name>demo</realm-name> -->
    </login-config>

    <security-role>
        <role-name>OIDCDEMO_USER</role-name>
    </security-role>

    <session-config>
        <session-timeout>15</session-timeout>
        <cookie-config>
            <http-only>true</http-only>
            <!-- <secure>true</secure> -->
        </cookie-config>
        <tracking-mode>COOKIE</tracking-mode>
    </session-config>
</web-app>

And our index.jsp and logout.jsp


<%@ page import="java.util.*"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Secure Web App</title>
<style>
table, th, td {
    border: 1px solid black;
    border-collapse: collapse;
}

th, td {
    padding: 10px;
}
</style>
</head>
<body>
    <table border="1">
        <!-- Security -->
        <tr>
            <td>getAuthType:</td>
            <td><%=request.getAuthType()%></td>
        </tr>
        <tr>
            <td>isSecure:</td>
            <td><%=request.isSecure()%></td>
        </tr>
        <tr>
            <td>getRemoteUser:</td>
            <td><%=request.getRemoteUser()%></td>
        </tr>
        <tr>
            <td>getUserPrincipal:</td>
            <td><%=request.getUserPrincipal()%></td>
        </tr>
        <tr>
            <td>getSession(false):</td>
            <td><%=request.getSession(false)%></td>
        </tr>
        <tr>
            <td>isUserInRole(<%=request.getParameter("UserInRole")%>):
            </td>
            <td>
                <div>
                    <form action="index.jsp" method="get">
                        <input type="text" id="UserInRole" name="UserInRole" /> <input type="submit" name="Submit" />
                    </form>
                </div>
                <div>
                    <%=request.isUserInRole(request.getParameter("UserInRole"))%>
                </div>
            </td>
        </tr>
        <!-- Cookies and Headers -->
        <tr>
            <td>Cookies:</td>
            <td>
                <%
                    for (int i = 0; request.getCookies() != null && i < request.getCookies().length; ++i) {
                        Cookie cookie = request.getCookies()[i];
                        out.println(cookie.getPath() + " " + cookie.getDomain() + " " + cookie.getName() + "="
                                + cookie.getValue() + " " + cookie.isHttpOnly() + " " + cookie.getSecure() + "<br/>");
                    }
                %>
            </td>
        </tr>
        <tr>
            <td>Header:</td>
            <td>
                <%
                    for (Enumeration<String> headers = request.getHeaderNames(); headers.hasMoreElements();) {
                        String header = headers.nextElement();
                        out.println(header + ": " + request.getHeader(header) + "<br/>");
                    }
                %>
            </td>
        </tr>
        <!-- Locale Address -->
        <tr>
            <td>getLocalAddr:</td>
            <td><%=request.getLocalAddr()%></td>
        </tr>
        <tr>
            <td>getLocalName:</td>
            <td><%=request.getLocalName()%></td>
        </tr>
        <tr>
            <td>getLocalPort:</td>
            <td><%=request.getLocalPort()%></td>
        </tr>
        <!-- Remote Address -->
        <tr>
            <td>getRemoteAddr:</td>
            <td><%=request.getRemoteAddr()%></td>
        </tr>
        <tr>
            <td>getLocalName:</td>
            <td><%=request.getRemoteHost()%></td>
        </tr>
        <tr>
            <td>getRemotePort:</td>
            <td><%=request.getRemotePort()%></td>
        </tr>
        <!-- Different Paths -->
        <tr>
            <td>getContextPath:</td>
            <td><%=request.getContextPath()%></td>
        </tr>
        <tr>
            <td>getPathInfo:</td>
            <td><%=request.getPathInfo()%></td>
        </tr>
        <tr>
            <td>getQueryString:</td>
            <td><%=request.getQueryString()%></td>
        </tr>
        <tr>
            <td>getRequestURI:</td>
            <td><%=request.getRequestURI()%></td>
        </tr>
        <tr>
            <td>getRequestURL:</td>
            <td><%=request.getRequestURL()%></td>
        </tr>
        <tr>
            <td>getServletPath:</td>
            <td><%=request.getServletPath()%></td>
        </tr>
    </table>
</body>
</html>

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Logout</title>
</head>
<body>
    <%
        if (request.getSession(false) != null) {
            request.getSession(false).invalidate();
        }
        request.logout();
        /*
        "You can log out of a web application in multiple ways. For Java EE servlet containers, you 
        can call HttpServletRequest.logout(). For other browser applications, you can redirect 
        the browser to 
        http://auth-server/auth/realms/{realm-name}/protocol/openid-connect/logout?redirect_uri=encodedRedirectUri, 
        which logs you out if you have an SSO session with your browser."
        [https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.3/html/securing_applications_and_services_guide/openid_connect_3#logout]
        */
    %>
    <h3>You are logged out</h3>
</body>
</html>

Configure OpenID Connect for Web Application

Now we need to configure OpenID Connect for our Web Application in RH SSO

And finally add OpenID Connect configuration to our web application src/main/webapp/WEB-INF/keycloak.json.


{
 "realm": "demo",
 "auth-server-url": "http://localhost:8080/auth",
 "ssl-required": "external",
 "resource": "rh-sso-7.3-demo",
 "public-client": true,
 "enable-cors": true
}

References

  1. https://www.keycloak.org/docs/latest/getting_started/index.html
  2. https://github.com/keycloak/keycloak-quickstarts/tree/latest/app-profile-jee-vanilla
  3. https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.3/html/securing_applications_and_services_guide/openid_connect_3#java_adapters

No comments: