keycloak-memoizeit
Changes
testsuite/performance-web/pom.xml 14(+14 -0)
testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java 6(+5 -1)
testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java 376(+376 -0)
Details
testsuite/performance-web/pom.xml 14(+14 -0)
diff --git a/testsuite/performance-web/pom.xml b/testsuite/performance-web/pom.xml
index 7ca6bac..8cf6f19 100644
--- a/testsuite/performance-web/pom.xml
+++ b/testsuite/performance-web/pom.xml
@@ -17,11 +17,25 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-testsuite-integration</artifactId>
<version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-testsuite-tools</artifactId>
<version>${project.version}</version>
+ <type>war</type>
+ </dependency>
+
+ <!-- Needed by undertow -->
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <version>1.0.0.Final</version>
</dependency>
<!-- Resteasy deps specified here as we want latest version of them -->
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
index 55cc44f..b151019 100644
--- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
@@ -4,9 +4,11 @@ import java.io.InputStream;
import javax.servlet.DispatcherType;
+import io.undertow.server.handlers.resource.ClassPathResourceManager;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.FilterInfo;
+import io.undertow.servlet.api.MimeMapping;
import io.undertow.servlet.api.ServletInfo;
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
import org.jboss.resteasy.spi.ResteasyDeployment;
@@ -76,10 +78,12 @@ public class KeycloakPerfServer {
deploymentInfo.setContextPath("/perf-app");
ServletInfo servlet = new ServletInfo("PerfAppServlet", PerfAppServlet.class);
- servlet.addMapping("/*");
+ servlet.addMapping("/perf-servlet");
deploymentInfo.addServlet(servlet);
+ deploymentInfo.setResourceManager(new ClassPathResourceManager(getClass().getClassLoader()));
+
keycloakServer.getServer().deploy(deploymentInfo);
System.out.println("PerfApp deployed");
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java
new file mode 100644
index 0000000..bb6e0c1
--- /dev/null
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java
@@ -0,0 +1,376 @@
+package org.keycloak.testsuite.performance.web;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.jboss.resteasy.security.PemUtils;
+import org.json.JSONObject;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.VerificationException;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.RefreshToken;
+import org.keycloak.services.resources.TokenService;
+import org.keycloak.util.BasicAuthHelper;
+
+/**
+ * TODO: Remove from here and instead merge with org.keycloak.testsuite.OAuthClient
+ *
+ *@author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OAuthClient {
+
+ private String baseUrl = "http://localhost:8081/auth";
+
+ private String realm = "perf-realm";
+
+ private String responseType = OAuth2Constants.CODE;
+
+ private String grantType = "authorization_code";
+
+ private String clientId = "perf-app";
+
+ private String redirectUri = "http://localhost:8081/perf-app/perf-servlet";
+
+ private String state = "123";
+
+ private PublicKey realmPublicKey;
+
+ public OAuthClient() {
+ try {
+ JSONObject realmJson = new JSONObject(IOUtils.toString(getClass().getResourceAsStream("/perfrealm.json")));
+ realmPublicKey = PemUtils.decodePublicKey(realmJson.getString("publicKey"));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to retrieve realm public key", e);
+ }
+ }
+
+ public AccessTokenResponse doAccessTokenRequest(String code, String password) {
+ HttpClient client = new DefaultHttpClient();
+ HttpPost post = new HttpPost(getAccessTokenUrl());
+
+ List<NameValuePair> parameters = new LinkedList<NameValuePair>();
+ if (grantType != null) {
+ parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType));
+ }
+ if (code != null) {
+ parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
+ }
+ if (redirectUri != null) {
+ parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
+ }
+ if (clientId != null && password != null) {
+ String authorization = BasicAuthHelper.createHeader(clientId, password);
+ post.setHeader("Authorization", authorization);
+ }
+ else if (clientId != null) {
+ parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
+ }
+
+ UrlEncodedFormEntity formEntity = null;
+ try {
+ formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ post.setEntity(formEntity);
+
+ try {
+ return new AccessTokenResponse(client.execute(post));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to retrieve access token", e);
+ }
+ }
+
+ public AccessTokenResponse doGrantAccessTokenRequest(String clientSecret, String username, String password) throws Exception {
+ HttpClient client = new DefaultHttpClient();
+ HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl());
+
+ String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
+ post.setHeader("Authorization", authorization);
+
+ List<NameValuePair> parameters = new LinkedList<NameValuePair>();
+ parameters.add(new BasicNameValuePair("username", username));
+ parameters.add(new BasicNameValuePair("password", password));
+
+ UrlEncodedFormEntity formEntity;
+ try {
+ formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ post.setEntity(formEntity);
+
+ return new AccessTokenResponse(client.execute(post));
+ }
+
+ public HttpResponse doLogout(String redirectUri, String sessionState) throws IOException {
+ HttpClient client = new DefaultHttpClient();
+ HttpGet get = new HttpGet(getLogoutUrl(redirectUri, sessionState));
+
+ return client.execute(get);
+ }
+
+ public AccessTokenResponse doRefreshTokenRequest(String refreshToken, String password) {
+ HttpClient client = new DefaultHttpClient();
+ HttpPost post = new HttpPost(getRefreshTokenUrl());
+
+ List<NameValuePair> parameters = new LinkedList<NameValuePair>();
+ if (grantType != null) {
+ parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType));
+ }
+ if (refreshToken != null) {
+ parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
+ }
+ if (clientId != null && password != null) {
+ String authorization = BasicAuthHelper.createHeader(clientId, password);
+ post.setHeader("Authorization", authorization);
+ }
+ else if (clientId != null) {
+ parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
+ }
+
+ UrlEncodedFormEntity formEntity;
+ try {
+ formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ post.setEntity(formEntity);
+
+ try {
+ return new AccessTokenResponse(client.execute(post));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to retrieve access token", e);
+ }
+ }
+
+ public AccessToken verifyToken(String token) {
+ try {
+ return RSATokenVerifier.verifyToken(token, realmPublicKey, realm);
+ } catch (VerificationException e) {
+ throw new RuntimeException("Failed to verify token", e);
+ }
+ }
+
+ public void verifyCode(String code) {
+ if (!RSAProvider.verify(new JWSInput(code), realmPublicKey)) {
+ throw new RuntimeException("Failed to verify code");
+ }
+ }
+
+ public RefreshToken verifyRefreshToken(String refreshToken) {
+ try {
+ JWSInput jws = new JWSInput(refreshToken);
+ if (!RSAProvider.verify(jws, realmPublicKey)) {
+ throw new RuntimeException("Invalid refresh token");
+ }
+ return jws.readJsonContent(RefreshToken.class);
+ } catch (Exception e) {
+ throw new RuntimeException("Invalid refresh token", e);
+ }
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public String getRedirectUri() {
+ return redirectUri;
+ }
+
+ public String getLoginFormUrl() {
+ UriBuilder b = TokenService.loginPageUrl(UriBuilder.fromUri(baseUrl));
+ if (responseType != null) {
+ b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType);
+ }
+ if (clientId != null) {
+ b.queryParam(OAuth2Constants.CLIENT_ID, clientId);
+ }
+ if (redirectUri != null) {
+ b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
+ }
+ if (state != null) {
+ b.queryParam(OAuth2Constants.STATE, state);
+ }
+ return b.build(realm).toString();
+ }
+
+ public String getAccessTokenUrl() {
+ UriBuilder b = TokenService.accessCodeToTokenUrl(UriBuilder.fromUri(baseUrl));
+ return b.build(realm).toString();
+ }
+
+ public String getLogoutUrl(String redirectUri, String sessionState) {
+ UriBuilder b = TokenService.logoutUrl(UriBuilder.fromUri(baseUrl));
+ if (redirectUri != null) {
+ b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
+ }
+ if (sessionState != null) {
+ b.queryParam("session_state", sessionState);
+ }
+ return b.build(realm).toString();
+ }
+
+ public String getResourceOwnerPasswordCredentialGrantUrl() {
+ UriBuilder b = TokenService.grantAccessTokenUrl(UriBuilder.fromUri(baseUrl));
+ return b.build(realm).toString();
+ }
+
+ public String getRefreshTokenUrl() {
+ UriBuilder b = TokenService.refreshUrl(UriBuilder.fromUri(baseUrl));
+ return b.build(realm).toString();
+ }
+
+ public OAuthClient realm(String realm) {
+ this.realm = realm;
+ return this;
+ }
+ public OAuthClient realmPublicKey(PublicKey key) {
+ this.realmPublicKey = key;
+ return this;
+ }
+
+ public OAuthClient clientId(String clientId) {
+ this.clientId = clientId;
+ return this;
+ }
+
+ public OAuthClient redirectUri(String redirectUri) {
+ this.redirectUri = redirectUri;
+ return this;
+ }
+
+ public OAuthClient responseType(String responseType) {
+ this.responseType = responseType;
+ return this;
+ }
+
+ public OAuthClient state(String state) {
+ this.state = state;
+ return this;
+ }
+
+ public String getRealm() {
+ return realm;
+ }
+
+ public static class AuthorizationCodeResponse {
+
+ private String code;
+ private String state;
+ private String error;
+
+ public AuthorizationCodeResponse(OAuthClient client, HttpServletRequest req) {
+ code = req.getParameter(OAuth2Constants.CODE);
+ state = req.getParameter(OAuth2Constants.STATE);
+ error = req.getParameter(OAuth2Constants.ERROR);
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ }
+
+ public static class AccessTokenResponse {
+ private int statusCode;
+
+ private String accessToken;
+ private String tokenType;
+ private int expiresIn;
+ private String refreshToken;
+ private String idToken;
+ private String sessionState;
+
+ private String error;
+
+ public AccessTokenResponse(HttpResponse response) throws Exception {
+ statusCode = response.getStatusLine().getStatusCode();
+ if (!"application/json".equals(response.getHeaders("Content-Type")[0].getValue())) {
+ throw new RuntimeException("Invalid content type");
+ }
+
+ String s = IOUtils.toString(response.getEntity().getContent());
+ JSONObject responseJson = new JSONObject(s);
+
+ if (statusCode == 200) {
+ accessToken = responseJson.getString("access_token");
+ tokenType = responseJson.getString("token_type");
+ expiresIn = responseJson.getInt("expires_in");
+ idToken = responseJson.optString("id_token");
+ sessionState = responseJson.optString("session-state");
+
+ if (responseJson.has(OAuth2Constants.REFRESH_TOKEN)) {
+ refreshToken = responseJson.getString(OAuth2Constants.REFRESH_TOKEN);
+ }
+ } else {
+ error = responseJson.getString(OAuth2Constants.ERROR);
+ }
+ }
+
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public int getExpiresIn() {
+ return expiresIn;
+ }
+
+ public int getStatusCode() {
+ return statusCode;
+ }
+
+ public String getRefreshToken() {
+ return refreshToken;
+ }
+
+ public String getTokenType() {
+ return tokenType;
+ }
+
+ public String getIdToken() {
+ return idToken;
+ }
+
+ public String getSessionState() {
+ return sessionState;
+ }
+ }
+
+
+}
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
index cc38ae6..6de11f9 100644
--- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
@@ -1,39 +1,115 @@
package org.keycloak.testsuite.performance.web;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import freemarker.cache.ClassTemplateLoader;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.RefreshToken;
+
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PerfAppServlet extends HttpServlet {
+ private Template indexTemplate;
+ private OAuthClient oauthClient;
+
+ @Override
+ public void init() throws ServletException {
+ try {
+ Configuration cfg = new Configuration();
+ cfg.setTemplateLoader(new ClassTemplateLoader(getClass(), "/"));
+ indexTemplate = cfg.getTemplate("perf-app-resources/index.ftl");
+
+ oauthClient = new OAuthClient();
+ } catch (IOException ioe) {
+ throw new ServletException(ioe);
+ }
+ }
+
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- String resourcePath = "perf-app-resources" + req.getPathInfo();
- System.out.println("Resource path: " + resourcePath);
-
- InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resourcePath);
- if (inputStream == null) {
- resp.getWriter().println("Not found: " + resourcePath);
- } else {
- OutputStream servletOutputStream = resp.getOutputStream();
-
- byte[] buf = new byte[1024];
- int bytesRead = 0;
- while (bytesRead != -1) {
- bytesRead = inputStream.read(buf);
- if (bytesRead != -1) {
- servletOutputStream.write(buf, 0, bytesRead);
- }
+ resp.setContentType("text/html");
+ String action = req.getParameter("action");
+
+ if (action != null) {
+ if (action.equals("code")) {
+ keycloakLoginRedirect(req, resp);
+ return;
+ } else if (action.equals("exchangeCode")) {
+ exchangeCodeForToken(req, resp);
+ } else if (action.equals("refresh")) {
+ refreshToken(req, resp);
+ } else if (action.equals("logout")) {
+ logoutRedirect(req, resp);
+ return;
}
- servletOutputStream.flush();
+ }
+
+ String code = req.getParameter("code");
+ if (code != null) {
+ req.getSession().setAttribute("code", code);
+ }
+
+ String freemarkerRedirect = freemarkerRedirect(req, resp);
+ resp.getWriter().println(freemarkerRedirect);
+ resp.getWriter().flush();
+ }
+
+ protected void keycloakLoginRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ String loginUrl = oauthClient.getLoginFormUrl();
+ resp.sendRedirect(loginUrl);
+ }
+
+ protected void exchangeCodeForToken(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ String code = (String)req.getSession().getAttribute("code");
+ OAuthClient.AccessTokenResponse atResponse = oauthClient.doAccessTokenRequest(code, "password");
+
+ String accessToken = atResponse.getAccessToken();
+ String refreshToken = atResponse.getRefreshToken();
+ req.getSession().setAttribute("accessToken", accessToken);
+ req.getSession().setAttribute("refreshToken", refreshToken);
+ }
+
+ protected void refreshToken(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ String refreshToken = (String)req.getSession().getAttribute("refreshToken");
+ OAuthClient.AccessTokenResponse atResponse = oauthClient.doRefreshTokenRequest(refreshToken, "password");
+
+ String accessToken = atResponse.getAccessToken();
+ refreshToken = atResponse.getRefreshToken();
+ req.getSession().setAttribute("accessToken", accessToken);
+ req.getSession().setAttribute("refreshToken", refreshToken);
+ }
+
+ protected void logoutRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+
+ }
+
+ private String freemarkerRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ Map<String, Object> attributes = new HashMap<String, Object>();
+ attributes.put("requestURI", req.getRequestURI());
+ attributes.put("code", req.getSession().getAttribute("code"));
+ attributes.put("accessToken", req.getSession().getAttribute("accessToken"));
+ attributes.put("refreshToken", req.getSession().getAttribute("refreshToken"));
+
+ try {
+ Writer out = new StringWriter();
+ indexTemplate.process(attributes, out);
+ return out.toString();
+ } catch (TemplateException te) {
+ throw new ServletException(te);
}
}
}
diff --git a/testsuite/performance-web/src/main/resources/perf-app-resources/index.ftl b/testsuite/performance-web/src/main/resources/perf-app-resources/index.ftl
new file mode 100644
index 0000000..b8e92eb
--- /dev/null
+++ b/testsuite/performance-web/src/main/resources/perf-app-resources/index.ftl
@@ -0,0 +1,51 @@
+<!doctype html>
+<html>
+ <head>
+ <title>PerfTest</title>
+ <script>
+ function updateElementWithToken(tokenStr, elementId) {
+ if (tokenStr && tokenStr != "") {
+ var tokenParsed = JSON.stringify(JSON.parse(decodeURIComponent(escape(window.atob( tokenStr.split('.')[1] )))), null, " ");
+ document.getElementById(elementId).innerHTML = tokenParsed;
+ }
+ }
+ </script>
+ </head>
+ <body>
+
+ <p><a href="${requestURI}?action=code">Login and get code</a> | <a href="${requestURI}?action=exchangeCode">Exchange code</a> | <a
+ href="${requestURI}?action=refresh">Refresh token</a> | <a href="${requestURI}?action=logout">Logout</a>
+ </p>
+
+ <p>
+ <#if code??>
+ Code: ${code}
+ <hr />
+ </#if>
+
+ <#if accessToken??>
+ <b>accessToken: </b> ${accessToken}
+ <br />
+ <pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="accessTokenParsed"></pre>
+ <hr />
+
+ <script>
+ updateElementWithToken("${accessToken}", "accessTokenParsed");
+ </script>
+ </#if>
+
+ <#if refreshToken??>
+ <b>refreshToken: </b> ${refreshToken}
+ <br />
+ <pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="refreshTokenParsed"></pre>
+ <hr />
+
+ <script>
+ updateElementWithToken("${refreshToken}", "refreshTokenParsed");
+ </script>
+ </#if>
+
+ </p>
+ <br><br>
+ </body>
+</html>
\ No newline at end of file