diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpChallenge.java b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpChallenge.java index ad753869ff..c018313d53 100644 --- a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpChallenge.java +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/HttpChallenge.java @@ -20,4 +20,12 @@ public abstract class HttpChallenge { public static HttpChallenge create(String scheme, String realm, Map params) { return new akka.http.scaladsl.model.headers.HttpChallenge(scheme, realm, Util.convertMapToScala(params)); } -} \ No newline at end of file + + public static HttpChallenge createBasic(String realm) { + return create("Basic", realm); + } + + public static HttpChallenge createOAuth2(String realm) { + return create("Bearer", realm); + } +} diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpChallenge.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpChallenge.scala index ec79cc1e26..633d0d221f 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpChallenge.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/HttpChallenge.scala @@ -21,3 +21,10 @@ final case class HttpChallenge(scheme: String, realm: String, /** Java API */ def getParams: util.Map[String, String] = params.asJava } + +object HttpChallenges { + + def basic(realm: String): HttpChallenge = HttpChallenge("Basic", realm) + + def oAuth2(realm: String): HttpChallenge = HttpChallenge("Bearer", realm) +} diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/SecurityDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/SecurityDirectivesSpec.scala index bec592cf40..9cb6b52811 100644 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/SecurityDirectivesSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/SecurityDirectivesSpec.scala @@ -18,18 +18,19 @@ class SecurityDirectivesSpec extends RoutingSpec { val doOAuth2Auth = authenticateOAuth2PF("MyRealm", { case Credentials.Provided(identifier) ⇒ identifier }) val authWithAnonymous = doBasicAuth.withAnonymousUser("We are Legion") - val challenge = HttpChallenge("Basic", "MyRealm") + val basicChallenge = HttpChallenges.basic("MyRealm") + val oAuth2Challenge = HttpChallenges.oAuth2("MyRealm") "basic authentication" should { "reject requests without Authorization header with an AuthenticationFailedRejection" in { Get() ~> { dontBasicAuth { echoComplete } - } ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsMissing, challenge) } + } ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsMissing, basicChallenge) } } "reject unauthenticated requests with Authorization header with an AuthenticationFailedRejection" in { Get() ~> Authorization(BasicHttpCredentials("Bob", "")) ~> { dontBasicAuth { echoComplete } - } ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsRejected, challenge) } + } ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsRejected, basicChallenge) } } "reject requests with an OAuth2 Bearer Token Authorization header with 401" in { Get() ~> Authorization(OAuth2BearerToken("myToken")) ~> Route.seal { @@ -37,7 +38,7 @@ class SecurityDirectivesSpec extends RoutingSpec { } ~> check { status shouldEqual StatusCodes.Unauthorized responseAs[String] shouldEqual "The supplied authentication is invalid" - header[`WWW-Authenticate`] shouldEqual Some(`WWW-Authenticate`(challenge)) + header[`WWW-Authenticate`] shouldEqual Some(`WWW-Authenticate`(oAuth2Challenge)) } } "reject requests with illegal Authorization header with 401" in { @@ -46,7 +47,7 @@ class SecurityDirectivesSpec extends RoutingSpec { } ~> check { status shouldEqual StatusCodes.Unauthorized responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request" - header[`WWW-Authenticate`] shouldEqual Some(`WWW-Authenticate`(challenge)) + header[`WWW-Authenticate`] shouldEqual Some(`WWW-Authenticate`(basicChallenge)) } } "extract the object representing the user identity created by successful authentication" in { @@ -74,12 +75,12 @@ class SecurityDirectivesSpec extends RoutingSpec { "reject requests without Authorization header with an AuthenticationFailedRejection" in { Get() ~> { dontOAuth2Auth { echoComplete } - } ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsMissing, challenge) } + } ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsMissing, oAuth2Challenge) } } "reject unauthenticated requests with Authorization header with an AuthenticationFailedRejection" in { Get() ~> Authorization(OAuth2BearerToken("myToken")) ~> { dontOAuth2Auth { echoComplete } - } ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsRejected, challenge) } + } ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsRejected, oAuth2Challenge) } } "reject requests with a Basic Authorization header with 401" in { Get() ~> Authorization(BasicHttpCredentials("Alice", "")) ~> Route.seal { @@ -87,7 +88,7 @@ class SecurityDirectivesSpec extends RoutingSpec { } ~> check { status shouldEqual StatusCodes.Unauthorized responseAs[String] shouldEqual "The supplied authentication is invalid" - header[`WWW-Authenticate`] shouldEqual Some(`WWW-Authenticate`(challenge)) + header[`WWW-Authenticate`] shouldEqual Some(`WWW-Authenticate`(basicChallenge)) } } "reject requests with illegal Authorization header with 401" in { @@ -96,7 +97,7 @@ class SecurityDirectivesSpec extends RoutingSpec { } ~> check { status shouldEqual StatusCodes.Unauthorized responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request" - header[`WWW-Authenticate`] shouldEqual Some(`WWW-Authenticate`(challenge)) + header[`WWW-Authenticate`] shouldEqual Some(`WWW-Authenticate`(oAuth2Challenge)) } } "extract the object representing the user identity created by successful authentication" in { @@ -132,7 +133,7 @@ class SecurityDirectivesSpec extends RoutingSpec { status shouldEqual StatusCodes.Unauthorized headers.collect { case `WWW-Authenticate`(challenge +: Nil) ⇒ challenge - } shouldEqual Seq(challenge, otherChallenge) + } shouldEqual Seq(basicChallenge, otherChallenge) } } } diff --git a/akka-http/src/main/scala/akka/http/javadsl/server/directives/SecurityDirectives.scala b/akka-http/src/main/scala/akka/http/javadsl/server/directives/SecurityDirectives.scala index 2670c60a55..9ed7f9a2fb 100644 --- a/akka-http/src/main/scala/akka/http/javadsl/server/directives/SecurityDirectives.scala +++ b/akka-http/src/main/scala/akka/http/javadsl/server/directives/SecurityDirectives.scala @@ -301,10 +301,4 @@ abstract class SecurityDirectives extends SchemeDirectives { def authorizeAsyncWithRequestContext(check: akka.japi.function.Function[RequestContext, CompletionStage[Boolean]], inner: Supplier[Route]): Route = RouteAdapter { D.authorizeAsync(rc ⇒ check(RequestContext.wrap(rc)).toScala)(inner.get().delegate) } - - /** - * Creates a `Basic` [[HttpChallenge]] for the given realm. - */ - def challengeFor(realm: String): HttpChallenge = HttpChallenge.create("Basic", realm) - } diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala index 3deba71abd..b2a8cb0f7f 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala @@ -13,13 +13,15 @@ import akka.http.scaladsl.util.FastFuture._ import akka.http.scaladsl.model.headers._ import akka.http.scaladsl.server.AuthenticationFailedRejection.{ CredentialsRejected, CredentialsMissing } -import scala.util.{ Try, Success } +import scala.util.Success /** * Provides directives for securing an inner route using the standard Http authentication headers [[`WWW-Authenticate`]] - * and [[Authorization]]. Most prominently, HTTP Basic authentication as defined in RFC 2617. + * and [[Authorization]]. Most prominently, HTTP Basic authentication and OAuth 2.0 Authorization Framework + * as defined in RFC 2617 and RFC 6750 respectively. * * See: RFC 2617. + * See: RFC 6750. * * @groupname security Security directives * @groupprio security 220 @@ -95,7 +97,7 @@ trait SecurityDirectives { authenticateOrRejectWithChallenge[BasicHttpCredentials, T] { cred ⇒ authenticator(Credentials(cred)).fast.map { case Some(t) ⇒ AuthenticationResult.success(t) - case None ⇒ AuthenticationResult.failWithChallenge(challengeFor(realm)) + case None ⇒ AuthenticationResult.failWithChallenge(HttpChallenges.basic(realm)) } } } @@ -146,7 +148,7 @@ trait SecurityDirectives { authenticateOrRejectWithChallenge[OAuth2BearerToken, T] { cred ⇒ authenticator(Credentials(cred)).fast.map { case Some(t) ⇒ AuthenticationResult.success(t) - case None ⇒ AuthenticationResult.failWithChallenge(challengeFor(realm)) + case None ⇒ AuthenticationResult.failWithChallenge(HttpChallenges.oAuth2(realm)) } } } @@ -248,13 +250,6 @@ trait SecurityDirectives { } } } - - /** - * Creates a `Basic` [[HttpChallenge]] for the given realm. - * - * @group security - */ - def challengeFor(realm: String) = HttpChallenge(scheme = "Basic", realm = realm, params = Map.empty) } object SecurityDirectives extends SecurityDirectives