Work in progress...

This commit is contained in:
mposolda 2014-06-25 14:29:38 +02:00
parent 69f7dfbb47
commit a1d4d8e13f
6 changed files with 540 additions and 19 deletions

View file

@ -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 -->

View file

@ -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");

View file

@ -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;
}
}
}

View file

@ -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);
resp.setContentType("text/html");
String action = req.getParameter("action");
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);
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);
}
}
}

View file

@ -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>