From 56eaca7a5a3e04925300b124c806d9b789c141c2 Mon Sep 17 00:00:00 2001 From: "mateusz.hinc" Date: Wed, 29 Jan 2020 16:38:25 +0100 Subject: [PATCH] Knotx/knotx-example-project#58 refactor OAuth2 security --- api-gateway/security/gradle.properties | 4 +- api-gateway/security/knotx/conf/openapi.yaml | 17 ++-- .../knotx/conf/routes/operations.conf | 31 ++++--- .../HelloWorldOpenIDHandlerFactory.java | 25 ++++++ ...x.server.api.handler.RoutingHandlerFactory | 3 +- .../auth/OAuth2HelloHandlerFactory.java | 48 +++++++++++ .../GoogleLoginRedirectHandlerFactory.java | 65 --------------- .../security/routing/OAuth2HelloHandler.java | 83 ------------------- .../routing/OAuth2HelloHandlerFactory.java | 20 ----- ...x.server.api.handler.RoutingHandlerFactory | 2 - ...otx.server.api.security.AuthHandlerFactory | 1 + 11 files changed, 103 insertions(+), 196 deletions(-) create mode 100644 api-gateway/security/modules/hello-module/src/main/java/io/knotx/example/hellohandler/HelloWorldOpenIDHandlerFactory.java create mode 100644 api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/auth/OAuth2HelloHandlerFactory.java delete mode 100644 api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/GoogleLoginRedirectHandlerFactory.java delete mode 100644 api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/OAuth2HelloHandler.java delete mode 100644 api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/OAuth2HelloHandlerFactory.java delete mode 100644 api-gateway/security/modules/security-module/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory diff --git a/api-gateway/security/gradle.properties b/api-gateway/security/gradle.properties index b4a38f9..4b0ed85 100644 --- a/api-gateway/security/gradle.properties +++ b/api-gateway/security/gradle.properties @@ -1,4 +1,4 @@ -version=2.0.0-SNAPSHOT -knotx.version=2.0.0 +version=2.1.1-SNAPSHOT +knotx.version=2.1.1-SNAPSHOT knotx.conf=knotx docker.image.name=knotx-example/secure-api-gateway \ No newline at end of file diff --git a/api-gateway/security/knotx/conf/openapi.yaml b/api-gateway/security/knotx/conf/openapi.yaml index 5ebedef..acff7fd 100644 --- a/api-gateway/security/knotx/conf/openapi.yaml +++ b/api-gateway/security/knotx/conf/openapi.yaml @@ -40,15 +40,11 @@ paths: description: Hello World API protected with JWT '401': description: Anauthorized access - /api/secure/login: - get: - operationId: oauth2-redirect-operation - responses: - '303': - description: Google Account login redirect /api/secure/oauth2: get: operationId: hello-world-operation-oauth2 + security: + - helloOAuth2Auth: [] responses: '200': description: Hello World API protected with JWT @@ -64,3 +60,12 @@ components: type: http scheme: bearer bearerFormat: JWT + helloOAuth2Auth: + type: oauth2 + flows: + authorizationCode: + authorizationUrl: https://accounts.google.com/o/oauth2/v2/auth + tokenUrl: https://accounts.google.com/o/oauth2/v4/token + scopes: + https://www.googleapis.com/auth/userinfo.profile: read user info + diff --git a/api-gateway/security/knotx/conf/routes/operations.conf b/api-gateway/security/knotx/conf/routes/operations.conf index 1a9340e..acf7e73 100644 --- a/api-gateway/security/knotx/conf/routes/operations.conf +++ b/api-gateway/security/knotx/conf/routes/operations.conf @@ -21,28 +21,13 @@ routingOperations = ${routingOperations} [ } ] } - { - operationId = oauth2-redirect-operation - handlers = [ - { - name = google-login-redirect-handler - config = { - clientId = ${googleConfig.clientId} - redirectUri = "http://localhost:8092/api/secure/oauth2" - scope = "https://www.googleapis.com/auth/userinfo.profile" - } - } - ] - } { operationId = hello-world-operation-oauth2 handlers = [ { - name = oauth2-hello-handler-factory + name = helloOpenIdHandler config = { - redirectUri = "http://localhost:8092/api/secure/oauth2" - clientId = ${googleConfig.clientId} - clientSecret = ${googleConfig.clientSecret} + message = "Hello World From Knot.x with OAuth2!" } } ] @@ -50,6 +35,18 @@ routingOperations = ${routingOperations} [ ] securityHandlers = [ + { + schema = helloOAuth2Auth + factory = helloOAuth2AuthFactory + config = { + clientId = ${googleConfig.clientId} + clientSecret = ${googleConfig.clientSecret} + redirectUrl = "http://localhost:8092/api/secure/callback" + scopes = [ + "https://www.googleapis.com/auth/userinfo.profile" + ] + } + } { schema = helloBasicAuth factory = helloBasicAuthFactory diff --git a/api-gateway/security/modules/hello-module/src/main/java/io/knotx/example/hellohandler/HelloWorldOpenIDHandlerFactory.java b/api-gateway/security/modules/hello-module/src/main/java/io/knotx/example/hellohandler/HelloWorldOpenIDHandlerFactory.java new file mode 100644 index 0000000..de5c61d --- /dev/null +++ b/api-gateway/security/modules/hello-module/src/main/java/io/knotx/example/hellohandler/HelloWorldOpenIDHandlerFactory.java @@ -0,0 +1,25 @@ +package io.knotx.example.hellohandler; + +import io.knotx.server.api.handler.RoutingHandlerFactory; +import io.vertx.core.Handler; +import io.vertx.core.json.JsonObject; +import io.vertx.reactivex.core.Vertx; +import io.vertx.reactivex.ext.auth.oauth2.KeycloakHelper; +import io.vertx.reactivex.ext.web.RoutingContext; + +public class HelloWorldOpenIDHandlerFactory implements RoutingHandlerFactory { + + @Override + public String getName() { + return "helloOpenIdHandler"; + } + + @Override + public Handler create(Vertx vertx, JsonObject config) { + return event -> { + String name = KeycloakHelper.idToken(event.user().principal()) + .getString("name"); + event.response().end("Hello " + name); + }; + } +} \ No newline at end of file diff --git a/api-gateway/security/modules/hello-module/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory b/api-gateway/security/modules/hello-module/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory index bf4c1bf..fd108e4 100644 --- a/api-gateway/security/modules/hello-module/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory +++ b/api-gateway/security/modules/hello-module/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory @@ -1 +1,2 @@ -io.knotx.example.hellohandler.HelloWorldHandlerFactory \ No newline at end of file +io.knotx.example.hellohandler.HelloWorldHandlerFactory +io.knotx.example.hellohandler.HelloWorldOpenIDHandlerFactory \ No newline at end of file diff --git a/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/auth/OAuth2HelloHandlerFactory.java b/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/auth/OAuth2HelloHandlerFactory.java new file mode 100644 index 0000000..5ea22c2 --- /dev/null +++ b/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/auth/OAuth2HelloHandlerFactory.java @@ -0,0 +1,48 @@ +package io.knotx.examples.security.auth; + +import io.knotx.server.api.security.AuthHandlerFactory; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.auth.oauth2.OAuth2ClientOptions; +import io.vertx.ext.auth.oauth2.OAuth2FlowType; +import io.vertx.reactivex.core.Vertx; +import io.vertx.reactivex.ext.auth.oauth2.OAuth2Auth; +import io.vertx.reactivex.ext.web.Router; +import io.vertx.reactivex.ext.web.handler.AuthHandler; +import io.vertx.reactivex.ext.web.handler.OAuth2AuthHandler; + +public class OAuth2HelloHandlerFactory implements AuthHandlerFactory { + + private OAuth2AuthHandler oAuth2AuthHandler; + + @Override + public String getName() { + return "helloOAuth2AuthFactory"; + } + + @Override + public AuthHandler create(Vertx vertx, JsonObject config) { + OAuth2ClientOptions clientOptions = new OAuth2ClientOptions() + .setClientID(config.getString("clientId")) + .setClientSecret(config.getString("clientSecret")) + .setSite("https://accounts.google.com") + .setTokenPath("https://www.googleapis.com/oauth2/v4/token") + .setAuthorizationPath("/o/oauth2/auth") + .setUserInfoPath("https://www.googleapis.com/oauth2/v3/userinfo") + .setIntrospectionPath("https://www.googleapis.com/oauth2/v3/tokeninfo") + .setUserInfoParameters(new JsonObject().put("alt", "json")) + .setScopeSeparator(" ") + .setUseBasicAuthorizationHeader(false); + + OAuth2Auth authProvider = OAuth2Auth.create(vertx, OAuth2FlowType.AUTH_CODE, clientOptions); + + oAuth2AuthHandler = OAuth2AuthHandler.create(authProvider, config.getString("redirectUrl")); + config.getJsonArray("scopes").forEach(scope -> oAuth2AuthHandler.addAuthority((String) scope)); + + return oAuth2AuthHandler; + } + + @Override + public void registerCustomRoute(Router router) { + oAuth2AuthHandler.setupCallback(router.route()); + } +} diff --git a/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/GoogleLoginRedirectHandlerFactory.java b/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/GoogleLoginRedirectHandlerFactory.java deleted file mode 100644 index 28440dd..0000000 --- a/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/GoogleLoginRedirectHandlerFactory.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.knotx.examples.security.routing; - -import io.knotx.server.api.handler.RoutingHandlerFactory; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.vertx.core.Handler; -import io.vertx.core.json.JsonObject; -import io.vertx.core.logging.Logger; -import io.vertx.core.logging.LoggerFactory; -import io.vertx.reactivex.core.Vertx; -import io.vertx.reactivex.ext.web.RoutingContext; -import java.net.URISyntaxException; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.client.utils.URIBuilder; - - -public class GoogleLoginRedirectHandlerFactory implements RoutingHandlerFactory { - - private static final Logger LOGGER = LoggerFactory.getLogger(GoogleLoginRedirectHandlerFactory.class); - - private static final String AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"; - - @Override - public String getName() { - return "google-login-redirect-handler"; - } - - @Override - public Handler create(Vertx vertx, JsonObject config) { - try { - String authorizationUri = getAuthorizationUri(config); - - return event -> event.response() - .putHeader("Location", authorizationUri) - .setStatusCode(HttpResponseStatus.SEE_OTHER.code()) - .end(); - } catch (URISyntaxException e) { - LOGGER.error("Error while building the authorization URI: {}", e); - return event -> event.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()).end(); - } - } - - private static String getAuthorizationUri(JsonObject config) throws URISyntaxException { - String redirectUri = config.getString("redirectUri"); - String scope = config.getString("scope"); - String clientId = config.getString("clientId"); - String state = RandomStringUtils.random(20); - String nonce = RandomStringUtils.random(20); - - if (StringUtils.isAnyBlank(redirectUri, scope, clientId)) { - throw new IllegalArgumentException("Configuration for Google Auth must include redirectUri, clientId and scope"); - } - - return new URIBuilder(AUTH_URL) - .addParameter("redirect_uri", redirectUri) - .addParameter("scope", scope) - .addParameter("client_id", clientId) - .addParameter("state", state) - .addParameter("nonce", nonce) - .addParameter("prompt", "consent") - .addParameter("access_type", "offline") - .addParameter("response_type", "code") - .build().toString(); - } -} diff --git a/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/OAuth2HelloHandler.java b/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/OAuth2HelloHandler.java deleted file mode 100644 index 34a119e..0000000 --- a/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/OAuth2HelloHandler.java +++ /dev/null @@ -1,83 +0,0 @@ -package io.knotx.examples.security.routing; - -import io.reactivex.Single; -import io.vertx.core.Handler; -import io.vertx.core.json.JsonObject; -import io.vertx.core.logging.Logger; -import io.vertx.core.logging.LoggerFactory; -import io.vertx.reactivex.core.Vertx; -import io.vertx.reactivex.core.buffer.Buffer; -import io.vertx.reactivex.ext.web.RoutingContext; -import io.vertx.reactivex.ext.web.client.HttpRequest; -import io.vertx.reactivex.ext.web.client.HttpResponse; -import io.vertx.reactivex.ext.web.client.WebClient; -import org.apache.commons.lang3.StringUtils; - -public class OAuth2HelloHandler implements Handler { - - private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2HelloHandler.class); - - private static final String USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo"; - - private static final String TOKEN_URL = "https://www.googleapis.com/oauth2/v4/token"; - - private WebClient webClient; - - private JsonObject config; - - OAuth2HelloHandler(Vertx vertx, JsonObject config) { - webClient = WebClient.create(vertx); - this.config = config; - } - - @Override - public void handle(RoutingContext event) { - String code = event.request().getParam("code"); - - exchangeCodeForToken(code) - .flatMap(this::fetchUserInfo) - .subscribe(response -> { - LOGGER.info("Response from Google userinfo endpoint: {}", response.statusCode()); - - JsonObject body = response.bodyAsJsonObject(); - String name = body.getString("name"); - - event.response().end("Hello " + name); - }, error -> LOGGER.error("An error occurred: {}", error)); - } - - private Single exchangeCodeForToken(String code) { - return prepareTokenRequest(code, config) - .rxSend() - .map(response -> { - LOGGER.info("Response from Google token endpoint: {}", response.statusCode()); - JsonObject body = response.bodyAsJsonObject(); - return body.getString("access_token"); - }); - } - - private HttpRequest prepareTokenRequest(String code, JsonObject config) { - String clientId = config.getString("clientId"); - String clientSecret = config.getString("clientSecret"); - String redirectUri = config.getString("redirectUri"); - - if (StringUtils.isAnyBlank(code, clientId, clientSecret, redirectUri)) { - throw new IllegalArgumentException("Configuration for Google Auth must include code, clientId, clientSecret and redirectUri"); - } - - return webClient.postAbs(TOKEN_URL) - .setQueryParam("code", code) - .setQueryParam("client_id", clientId) - .setQueryParam("client_secret", clientSecret) - .setQueryParam("redirect_uri", redirectUri) - .setQueryParam("grant_type", "authorization_code") - .putHeader("Content-Length", "0"); - } - - private Single> fetchUserInfo(String accessToken) { - return webClient.getAbs(USERINFO_URL) - .bearerTokenAuthentication(accessToken) - .rxSend(); - } - -} diff --git a/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/OAuth2HelloHandlerFactory.java b/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/OAuth2HelloHandlerFactory.java deleted file mode 100644 index 4b77526..0000000 --- a/api-gateway/security/modules/security-module/src/main/java/io/knotx/examples/security/routing/OAuth2HelloHandlerFactory.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.knotx.examples.security.routing; - -import io.knotx.server.api.handler.RoutingHandlerFactory; -import io.vertx.core.Handler; -import io.vertx.core.json.JsonObject; -import io.vertx.reactivex.core.Vertx; -import io.vertx.reactivex.ext.web.RoutingContext; - -public class OAuth2HelloHandlerFactory implements RoutingHandlerFactory { - - @Override - public String getName() { - return "oauth2-hello-handler-factory"; - } - - @Override - public Handler create(Vertx vertx, JsonObject config) { - return new OAuth2HelloHandler(vertx, config); - } -} diff --git a/api-gateway/security/modules/security-module/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory b/api-gateway/security/modules/security-module/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory deleted file mode 100644 index b76b7ec..0000000 --- a/api-gateway/security/modules/security-module/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory +++ /dev/null @@ -1,2 +0,0 @@ -io.knotx.examples.security.routing.GoogleLoginRedirectHandlerFactory -io.knotx.examples.security.routing.OAuth2HelloHandlerFactory \ No newline at end of file diff --git a/api-gateway/security/modules/security-module/src/main/resources/META-INF/services/io.knotx.server.api.security.AuthHandlerFactory b/api-gateway/security/modules/security-module/src/main/resources/META-INF/services/io.knotx.server.api.security.AuthHandlerFactory index c4bb504..5fba90b 100644 --- a/api-gateway/security/modules/security-module/src/main/resources/META-INF/services/io.knotx.server.api.security.AuthHandlerFactory +++ b/api-gateway/security/modules/security-module/src/main/resources/META-INF/services/io.knotx.server.api.security.AuthHandlerFactory @@ -1,2 +1,3 @@ io.knotx.examples.security.auth.BasicAuthHandlerFactory io.knotx.examples.security.auth.JwtAuthHandlerFactory +io.knotx.examples.security.auth.OAuth2HelloHandlerFactory