From af2bc368a260d55bb764c42cf12d460efef20024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andre=CC=81n?= Date: Thu, 19 Nov 2015 17:55:50 +0100 Subject: [PATCH] =doc #18968 Document auth options for Java DSL --- .../server/HttpBasicAuthenticatorExample.java | 74 +++++++++++++++++ .../server/OAuth2AuthenticatorExample.java | 81 +++++++++++++++++++ .../request-vals/http-basic-authenticator.rst | 38 +++++++++ .../http/routing-dsl/request-vals/index.rst | 6 ++ .../request-vals/oauth2-authenticator.rst | 47 +++++++++++ .../rst/scala/code/docs/utils/TestUtils.scala | 2 +- 6 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 akka-docs-dev/rst/java/code/docs/http/javadsl/server/HttpBasicAuthenticatorExample.java create mode 100644 akka-docs-dev/rst/java/code/docs/http/javadsl/server/OAuth2AuthenticatorExample.java create mode 100644 akka-docs-dev/rst/java/http/routing-dsl/request-vals/http-basic-authenticator.rst create mode 100644 akka-docs-dev/rst/java/http/routing-dsl/request-vals/oauth2-authenticator.rst diff --git a/akka-docs-dev/rst/java/code/docs/http/javadsl/server/HttpBasicAuthenticatorExample.java b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/HttpBasicAuthenticatorExample.java new file mode 100644 index 0000000000..624b34e441 --- /dev/null +++ b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/HttpBasicAuthenticatorExample.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ +package docs.http.javadsl.server; + +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.headers.Host; +import akka.http.javadsl.server.Handler1; +import akka.http.javadsl.server.RequestContext; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.server.RouteResult; +import akka.http.javadsl.server.values.BasicCredentials; +import akka.http.javadsl.server.values.HttpBasicAuthenticator; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.scaladsl.model.headers.Authorization; +import org.junit.Test; +import scala.Option; +import scala.concurrent.Future; + +public class HttpBasicAuthenticatorExample extends JUnitRouteTest { + + + @Test + public void testBasicAuthenticator() { + //#basic-authenticator-java + final HttpBasicAuthenticator authentication = new HttpBasicAuthenticator("My realm") { + + private final String hardcodedPassword = "correcthorsebatterystaple"; + + public Future> authenticate(BasicCredentials credentials) { + // this is where your actual authentication logic would go + if (credentials.available() && // no anonymous access + credentials.verify(hardcodedPassword)) { + return authenticateAs(credentials.identifier()); + } else { + return refuseAccess(); + } + } + }; + + final Route route = + authentication.route( + handleWith1( + authentication, + new Handler1() { + public RouteResult apply(RequestContext ctx, String user) { + return ctx.complete("Hello " + user + "!"); + } + + } + ) + ); + + + // tests: + final HttpRequest okRequest = + HttpRequest + .GET("http://akka.io/") + .addHeader(Host.create("akka.io")) + .addHeader(Authorization.basic("randal", "correcthorsebatterystaple")); + testRoute(route).run(okRequest).assertEntity("Hello randal!"); + + final HttpRequest badRequest = + HttpRequest + .GET("http://akka.io/") + .addHeader(Host.create("akka.io")) + .addHeader(Authorization.basic("randal", "123abc")); + testRoute(route).run(badRequest).assertStatusCode(401); + + //#basic-authenticator-java + } + + +} \ No newline at end of file diff --git a/akka-docs-dev/rst/java/code/docs/http/javadsl/server/OAuth2AuthenticatorExample.java b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/OAuth2AuthenticatorExample.java new file mode 100644 index 0000000000..3152179791 --- /dev/null +++ b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/OAuth2AuthenticatorExample.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ +package docs.http.javadsl.server; + +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.headers.Host; +import akka.http.javadsl.model.headers.OAuth2BearerToken; +import akka.http.javadsl.server.Handler1; +import akka.http.javadsl.server.RequestContext; +import akka.http.javadsl.server.Route; +import akka.http.javadsl.server.RouteResult; +import akka.http.javadsl.server.values.BasicCredentials; +import akka.http.javadsl.server.values.HttpBasicAuthenticator; +import akka.http.javadsl.server.values.OAuth2Authenticator; +import akka.http.javadsl.server.values.OAuth2Credentials; +import akka.http.javadsl.testkit.JUnitRouteTest; +import akka.http.scaladsl.model.headers.Authorization; +import org.junit.Test; +import scala.Option; +import scala.concurrent.Future; + +public class OAuth2AuthenticatorExample extends JUnitRouteTest { + + + @Test + public void testOAuth2Authenticator() { + //#oauth2-authenticator-java + final OAuth2Authenticator authentication = new OAuth2Authenticator("My realm") { + + private final String hardcodedToken = "token"; + + @Override + public Future> authenticate(OAuth2Credentials credentials) { + // this is where your actual authentication logic would go, looking up the user + // based on the token or something in that direction + if (credentials.available() && // no anonymous access + credentials.verify(hardcodedToken)) { + // not a secret + identity pair, so this is actually the token + return authenticateAs(credentials.identifier()); + } else { + return refuseAccess(); + } + } + + }; + + final Route route = + authentication.route( + handleWith1( + authentication, + new Handler1() { + public RouteResult apply(RequestContext ctx, String token) { + return ctx.complete("The secret token is: " + token); + } + + } + ) + ); + + + // tests: + final HttpRequest okRequest = + HttpRequest + .GET("http://akka.io/") + .addHeader(Host.create("akka.io")) + .addHeader(Authorization.oauth2("token")); + testRoute(route).run(okRequest).assertEntity("The secret token is: token"); + + final HttpRequest badRequest = + HttpRequest + .GET("http://akka.io/") + .addHeader(Host.create("akka.io")) + .addHeader(Authorization.oauth2("wrong")); + testRoute(route).run(badRequest).assertStatusCode(401); + + //#oauth2-authenticator-java + } + + +} \ No newline at end of file diff --git a/akka-docs-dev/rst/java/http/routing-dsl/request-vals/http-basic-authenticator.rst b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/http-basic-authenticator.rst new file mode 100644 index 0000000000..7ec157fb9e --- /dev/null +++ b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/http-basic-authenticator.rst @@ -0,0 +1,38 @@ +.. _http-basic-authenticator-java: + +Request Values: Http Basic Auth +=============================== + +An abstract class to implement HTTP basic authentication + +Description +----------- +Http basic auth allows for protection of one or more routes with a username and password. + +To use it you subclass ``HttpBasicAuthenticator`` and provide your authentication logic. +There are two factory methods to create the authentication results to return from the authentication logic: +``authenticateAs(T)`` and ``refuseAccess()``. If the authentication is not very quick in memory, for example +calls a database, make sure you do not block the web server thread by executing that in a separate ``Future`` +and then ``flatMap`` the result into the authentication result. + +When you use the authenticator in your routes you must reference the concrete authenticator twice, +first as a directive wrapping all the routes it should be required for, and then as a request +value to extract the user object for use inside the logic of the handler. + +Note that to protect developers from opening up for a timing attack on the password it is not available +directly, instead a constant time string comparison is provided. For more information about timing attacks +on passwords see for example `Timing Attacks Explained`_ . + +.. _Timing Attacks Explained: http://emerose.com/timing-attacks-explained + + +Example +------- + +Authenticating or refusing access to a user based on a hardcoded password and using a ``String`` with the +username as internal representation of a user (in a real application it would probably be an instance of +a richer class describing an authenticated user). + + +.. includecode:: ../../../code/docs/http/javadsl/server/HttpBasicAuthenticatorExample.java + :include: basic-authenticator-java diff --git a/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst index 1ba0178bb3..3e34dcd39c 100644 --- a/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst +++ b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/index.rst @@ -43,6 +43,10 @@ akka.http.javadsl.server.values.FormFieldsPathMatchers Contains request values to match and access URI path segments. akka.http.javadsl.server.values.FormFieldsCustomRequestVal An abstract class to implement arbitrary custom request values. +:ref:`akka.http.javadsl.server.values.HttpBasicAuthenticator.scala ` + An abstract class to implement HTTP basic authentication +:ref:`akka.http.javadsl.server.values.OAuth2Authenticator ` + An abstract class to implement Oauth 2 bearer token authentication See also -------- @@ -52,3 +56,5 @@ See also form-field-request-vals header-request-vals + http-basic-authenticator + oauth2-authenticator diff --git a/akka-docs-dev/rst/java/http/routing-dsl/request-vals/oauth2-authenticator.rst b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/oauth2-authenticator.rst new file mode 100644 index 0000000000..88ecb1e06f --- /dev/null +++ b/akka-docs-dev/rst/java/http/routing-dsl/request-vals/oauth2-authenticator.rst @@ -0,0 +1,47 @@ +.. _oauth2-authenticator-java: + +Request Values: OAuth 2 Bearer Token Authentication +=================================================== + +An abstract class to implement Oauth 2 bearer token authentication + +Description +----------- +Allows to protect one of more routes with authentication in the form of a OAuth2 Bearer Token. For more information +about OAuth 2 Bearer Token see `RFC6750`_. + +.. _RFC6750: https://tools.ietf.org/html/rfc6750 + +To use it you subclass ``OAutht2Authenticator`` and implement the ``authenticate`` method +to provide your own logic which verifies the OAuth2 credentials. When verification is done +the request can either be refused by returning the return value of ``refuseAccess()`` or completed +with an object that is application specific by returning the return value of ``authenticateAs(T)``. + +If the authentication is not very quick in memory, for example calls a separate authentication server +to verify the token, make sure you do not block the web server thread by executing that in a separate ``Future`` +and then ``flatMap`` the result into the authentication result. + +.. note:: OAuth2 Bearer Token sends the token as clear text and should ONLY EVER be used over + SSL/TLS + +When you use the OAuth2 authenticator in your routes you must reference the concrete authenticator twice, +first as a directive wrapping all the routes it should be required for, and then as a request +value to extract the user object for use inside the logic of the handler. + +Note that to protect developers from opening up for a timing attack on the token it is not available +directly, instead a constant time string comparison is provided. For more information about timing attacks +on passwords see for example `Timing Attacks Explained`_ . + +.. _Timing Attacks Explained: http://emerose.com/timing-attacks-explained + + +Example +------- + +Authenticating or refusing access to a user based on a hardcoded token and using a ``String`` with the +identity as internal representation of a user (in a real application it would probably be an instance of +a richer class describing an authenticated user). + + +.. includecode:: ../../../code/docs/http/javadsl/server/OAuth2AuthenticatorExample.java + :include: oauth2-authenticator-java diff --git a/akka-docs-dev/rst/scala/code/docs/utils/TestUtils.scala b/akka-docs-dev/rst/scala/code/docs/utils/TestUtils.scala index 41b6df832a..9e88725e81 100644 --- a/akka-docs-dev/rst/scala/code/docs/utils/TestUtils.scala +++ b/akka-docs-dev/rst/scala/code/docs/utils/TestUtils.scala @@ -19,6 +19,6 @@ object TestUtils { def temporaryServerHostnameAndPort(interface: String = "127.0.0.1"): (String, Int) = { val socketAddress = temporaryServerAddress(interface) - socketAddress.getHostName -> socketAddress.getPort // TODO getHostString in Java7 + socketAddress.getHostName -> socketAddress.getPort // TODO getHostString in Java7 } }