Generalised Authentication Directives to accept Bearer Tokens
Make type of credential authentication explicit Cleanup
This commit is contained in:
parent
8d72e30235
commit
4dde089d82
9 changed files with 276 additions and 55 deletions
|
|
@ -17,4 +17,7 @@ public abstract class Authorization extends akka.http.scaladsl.model.HttpHeader
|
|||
public static Authorization basic(String username, String password) {
|
||||
return create(HttpCredentials.createBasicHttpCredentials(username, password));
|
||||
}
|
||||
public static Authorization oauth2(String token) {
|
||||
return create(HttpCredentials.createOAuth2BearerToken(token));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,45 +17,72 @@ public class HttpBasicAuthenticationTest extends JUnitRouteTest {
|
|||
HttpBasicAuthenticator<String> authenticatedUser =
|
||||
new HttpBasicAuthenticator<String>("test-realm") {
|
||||
@Override
|
||||
public Future<Option<String>> authenticate(BasicUserCredentials credentials) {
|
||||
public Future<Option<String>> authenticate(BasicCredentials credentials) {
|
||||
if (credentials.available() && // no anonymous access
|
||||
credentials.userName().equals("sina") &&
|
||||
credentials.verifySecret("1234"))
|
||||
credentials.identifier().equals("sina") &&
|
||||
credentials.verify("1234"))
|
||||
return authenticateAs("Sina");
|
||||
else return refuseAccess();
|
||||
}
|
||||
};
|
||||
|
||||
OAuth2Authenticator<String> authenticatedToken =
|
||||
new OAuth2Authenticator<String>("test-realm") {
|
||||
@Override
|
||||
public Future<Option<String>> authenticate(OAuth2Credentials credentials) {
|
||||
if (credentials.available() && // no anonymous access
|
||||
credentials.identifier().equals("myToken") &&
|
||||
credentials.verify("myToken"))
|
||||
return authenticateAs("myToken");
|
||||
else return refuseAccess();
|
||||
}
|
||||
};
|
||||
|
||||
Handler1<String> helloWorldHandler =
|
||||
new Handler1<String>() {
|
||||
@Override
|
||||
public RouteResult apply(RequestContext ctx, String user) {
|
||||
return ctx.complete("Hello "+user+"!");
|
||||
public RouteResult apply(RequestContext ctx, String identifier) {
|
||||
return ctx.complete("Identified as "+identifier+"!");
|
||||
}
|
||||
};
|
||||
|
||||
TestRoute route =
|
||||
testRoute(
|
||||
path("secure").route(
|
||||
path("basicSecure").route(
|
||||
authenticatedUser.route(
|
||||
handleWith1(authenticatedUser, helloWorldHandler)
|
||||
)
|
||||
),
|
||||
path("oauthSecure").route(
|
||||
authenticatedToken.route(
|
||||
handleWith1(authenticatedToken, helloWorldHandler)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@Test
|
||||
public void testCorrectUser() {
|
||||
HttpRequest authenticatedRequest =
|
||||
HttpRequest.GET("/secure")
|
||||
HttpRequest.GET("/basicSecure")
|
||||
.addHeader(Authorization.basic("sina", "1234"));
|
||||
|
||||
route.run(authenticatedRequest)
|
||||
.assertStatusCode(200)
|
||||
.assertEntity("Hello Sina!");
|
||||
.assertEntity("Identified as Sina!");
|
||||
}
|
||||
@Test
|
||||
public void testCorrectToken() {
|
||||
HttpRequest authenticatedRequest =
|
||||
HttpRequest.GET("/oauthSecure")
|
||||
.addHeader(Authorization.oauth2("myToken"));
|
||||
|
||||
route.run(authenticatedRequest)
|
||||
.assertStatusCode(200)
|
||||
.assertEntity("Identified as myToken!");
|
||||
}
|
||||
@Test
|
||||
public void testRejectAnonymousAccess() {
|
||||
route.run(HttpRequest.GET("/secure"))
|
||||
route.run(HttpRequest.GET("/basicSecure"))
|
||||
.assertStatusCode(401)
|
||||
.assertEntity("The resource requires authentication, which was not supplied with the request")
|
||||
.assertHeaderExists("WWW-Authenticate", "Basic realm=\"test-realm\"");
|
||||
|
|
@ -63,7 +90,7 @@ public class HttpBasicAuthenticationTest extends JUnitRouteTest {
|
|||
@Test
|
||||
public void testRejectUnknownUser() {
|
||||
HttpRequest authenticatedRequest =
|
||||
HttpRequest.GET("/secure")
|
||||
HttpRequest.GET("/basicSecure")
|
||||
.addHeader(Authorization.basic("joe", "0000"));
|
||||
|
||||
route.run(authenticatedRequest)
|
||||
|
|
@ -73,7 +100,7 @@ public class HttpBasicAuthenticationTest extends JUnitRouteTest {
|
|||
@Test
|
||||
public void testRejectWrongPassword() {
|
||||
HttpRequest authenticatedRequest =
|
||||
HttpRequest.GET("/secure")
|
||||
HttpRequest.GET("/basicSecure")
|
||||
.addHeader(Authorization.basic("sina", "1235"));
|
||||
|
||||
route.run(authenticatedRequest)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
package akka.http.scaladsl.server
|
||||
|
||||
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport
|
||||
import akka.http.scaladsl.server.directives.UserCredentials
|
||||
import akka.http.scaladsl.server.directives.Credentials
|
||||
import com.typesafe.config.{ ConfigFactory, Config }
|
||||
import akka.actor.ActorSystem
|
||||
import akka.stream.ActorMaterializer
|
||||
|
|
@ -23,7 +23,7 @@ object TestServer extends App {
|
|||
import Directives._
|
||||
|
||||
def auth: AuthenticatorPF[String] = {
|
||||
case p @ UserCredentials.Provided(name) if p.verifySecret(name + "-password") ⇒ name
|
||||
case p @ Credentials.Provided(name) if p.verify(name + "-password") ⇒ name
|
||||
}
|
||||
|
||||
val bindingFuture = Http().bindAndHandle({
|
||||
|
|
|
|||
|
|
@ -11,26 +11,37 @@ import akka.http.scaladsl.model.headers._
|
|||
import akka.http.scaladsl.server.AuthenticationFailedRejection.{ CredentialsRejected, CredentialsMissing }
|
||||
|
||||
class SecurityDirectivesSpec extends RoutingSpec {
|
||||
val dontAuth = authenticateBasicAsync[String]("MyRealm", _ ⇒ Future.successful(None))
|
||||
val doAuth = authenticateBasicPF("MyRealm", { case UserCredentials.Provided(name) ⇒ name })
|
||||
val authWithAnonymous = doAuth.withAnonymousUser("We are Legion")
|
||||
val dontBasicAuth = authenticateBasicAsync[String]("MyRealm", _ ⇒ Future.successful(None))
|
||||
val dontOAuth2Auth = authenticateOAuth2Async[String]("MyRealm", _ ⇒ Future.successful(None))
|
||||
val doBasicAuth = authenticateBasicPF("MyRealm", { case Credentials.Provided(identifier) ⇒ identifier })
|
||||
val doOAuth2Auth = authenticateOAuth2PF("MyRealm", { case Credentials.Provided(identifier) ⇒ identifier })
|
||||
val authWithAnonymous = doBasicAuth.withAnonymousUser("We are Legion")
|
||||
|
||||
val challenge = HttpChallenge("Basic", "MyRealm")
|
||||
|
||||
"basic authentication" should {
|
||||
"reject requests without Authorization header with an AuthenticationFailedRejection" in {
|
||||
Get() ~> {
|
||||
dontAuth { echoComplete }
|
||||
dontBasicAuth { echoComplete }
|
||||
} ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsMissing, challenge) }
|
||||
}
|
||||
"reject unauthenticated requests with Authorization header with an AuthenticationFailedRejection" in {
|
||||
Get() ~> Authorization(BasicHttpCredentials("Bob", "")) ~> {
|
||||
dontAuth { echoComplete }
|
||||
dontBasicAuth { echoComplete }
|
||||
} ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsRejected, challenge) }
|
||||
}
|
||||
"reject requests with an OAuth2 Bearer Token Authorization header with 401" in {
|
||||
Get() ~> Authorization(OAuth2BearerToken("myToken")) ~> Route.seal {
|
||||
dontOAuth2Auth { echoComplete }
|
||||
} ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The supplied authentication is invalid"
|
||||
header[`WWW-Authenticate`] shouldEqual Some(`WWW-Authenticate`(challenge))
|
||||
}
|
||||
}
|
||||
"reject requests with illegal Authorization header with 401" in {
|
||||
Get() ~> RawHeader("Authorization", "bob alice") ~> Route.seal {
|
||||
dontAuth { echoComplete }
|
||||
dontBasicAuth { echoComplete }
|
||||
} ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
|
||||
|
|
@ -39,7 +50,7 @@ class SecurityDirectivesSpec extends RoutingSpec {
|
|||
}
|
||||
"extract the object representing the user identity created by successful authentication" in {
|
||||
Get() ~> Authorization(BasicHttpCredentials("Alice", "")) ~> {
|
||||
doAuth { echoComplete }
|
||||
doBasicAuth { echoComplete }
|
||||
} ~> check { responseAs[String] shouldEqual "Alice" }
|
||||
}
|
||||
"extract the object representing the user identity created for the anonymous user" in {
|
||||
|
|
@ -51,7 +62,55 @@ class SecurityDirectivesSpec extends RoutingSpec {
|
|||
object TestException extends RuntimeException
|
||||
Get() ~> Authorization(BasicHttpCredentials("Alice", "")) ~> {
|
||||
Route.seal {
|
||||
doAuth { _ ⇒ throw TestException }
|
||||
doBasicAuth { _ ⇒ throw TestException }
|
||||
}
|
||||
} ~> check { status shouldEqual StatusCodes.InternalServerError }
|
||||
}
|
||||
}
|
||||
"bearer token authentication" should {
|
||||
"reject requests without Authorization header with an AuthenticationFailedRejection" in {
|
||||
Get() ~> {
|
||||
dontOAuth2Auth { echoComplete }
|
||||
} ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsMissing, challenge) }
|
||||
}
|
||||
"reject unauthenticated requests with Authorization header with an AuthenticationFailedRejection" in {
|
||||
Get() ~> Authorization(OAuth2BearerToken("myToken")) ~> {
|
||||
dontOAuth2Auth { echoComplete }
|
||||
} ~> check { rejection shouldEqual AuthenticationFailedRejection(CredentialsRejected, challenge) }
|
||||
}
|
||||
"reject requests with a Basic Authorization header with 401" in {
|
||||
Get() ~> Authorization(BasicHttpCredentials("Alice", "")) ~> Route.seal {
|
||||
dontBasicAuth { echoComplete }
|
||||
} ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The supplied authentication is invalid"
|
||||
header[`WWW-Authenticate`] shouldEqual Some(`WWW-Authenticate`(challenge))
|
||||
}
|
||||
}
|
||||
"reject requests with illegal Authorization header with 401" in {
|
||||
Get() ~> RawHeader("Authorization", "bob alice") ~> Route.seal {
|
||||
dontOAuth2Auth { echoComplete }
|
||||
} ~> 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))
|
||||
}
|
||||
}
|
||||
"extract the object representing the user identity created by successful authentication" in {
|
||||
Get() ~> Authorization(OAuth2BearerToken("myToken")) ~> {
|
||||
doOAuth2Auth { echoComplete }
|
||||
} ~> check { responseAs[String] shouldEqual "myToken" }
|
||||
}
|
||||
"extract the object representing the user identity created for the anonymous user" in {
|
||||
Get() ~> {
|
||||
authWithAnonymous { echoComplete }
|
||||
} ~> check { responseAs[String] shouldEqual "We are Legion" }
|
||||
}
|
||||
"properly handle exceptions thrown in its inner route" in {
|
||||
object TestException extends RuntimeException
|
||||
Get() ~> Authorization(OAuth2BearerToken("myToken")) ~> {
|
||||
Route.seal {
|
||||
doOAuth2Auth { _ ⇒ throw TestException }
|
||||
}
|
||||
} ~> check { status shouldEqual StatusCodes.InternalServerError }
|
||||
}
|
||||
|
|
@ -62,7 +121,7 @@ class SecurityDirectivesSpec extends RoutingSpec {
|
|||
val otherAuth: Directive1[String] = authenticateOrRejectWithChallenge { (cred: Option[HttpCredentials]) ⇒
|
||||
Future.successful(Left(otherChallenge))
|
||||
}
|
||||
val bothAuth = dontAuth | otherAuth
|
||||
val bothAuth = dontBasicAuth | otherAuth
|
||||
|
||||
Get() ~> Route.seal(bothAuth { echoComplete }) ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
package akka.http.impl.server
|
||||
|
||||
import akka.http.impl.util.JavaMapping
|
||||
import akka.http.javadsl.server.values.{ PathMatcher, BasicUserCredentials }
|
||||
import akka.http.javadsl.server.values.{ PathMatcher, BasicCredentials, OAuth2Credentials }
|
||||
import akka.http.scaladsl.model.StatusCodes.Redirection
|
||||
import akka.http.scaladsl.server.util.TupleOps.Join
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ import scala.language.implicitConversions
|
|||
import scala.annotation.tailrec
|
||||
import scala.collection.immutable
|
||||
import akka.http.javadsl.model.ContentType
|
||||
import akka.http.scaladsl.server.directives.{ UserCredentials, ContentTypeResolver }
|
||||
import akka.http.scaladsl.server.directives.{ Credentials, ContentTypeResolver }
|
||||
import akka.http.scaladsl.server.directives.FileAndResourceDirectives.DirectoryRenderer
|
||||
import akka.http.scaladsl.model.HttpHeader
|
||||
import akka.http.scaladsl.model.headers.{ HttpCookie, CustomHeader }
|
||||
|
|
@ -80,17 +80,40 @@ private[http] object RouteImplementation extends Directives with server.RouteCon
|
|||
authenticateBasicAsync(authenticator.realm, { creds ⇒
|
||||
val javaCreds =
|
||||
creds match {
|
||||
case UserCredentials.Missing ⇒
|
||||
new BasicUserCredentials {
|
||||
case Credentials.Missing ⇒
|
||||
new BasicCredentials {
|
||||
def available: Boolean = false
|
||||
def userName: String = throw new IllegalStateException("Credentials missing")
|
||||
def verifySecret(secret: String): Boolean = throw new IllegalStateException("Credentials missing")
|
||||
def identifier: String = throw new IllegalStateException("Credentials missing")
|
||||
def verify(secret: String): Boolean = throw new IllegalStateException("Credentials missing")
|
||||
}
|
||||
case p @ UserCredentials.Provided(name) ⇒
|
||||
new BasicUserCredentials {
|
||||
case p @ Credentials.Provided(name) ⇒
|
||||
new BasicCredentials {
|
||||
def available: Boolean = true
|
||||
def userName: String = name
|
||||
def verifySecret(secret: String): Boolean = p.verifySecret(secret)
|
||||
def identifier: String = name
|
||||
def verify(secret: String): Boolean = p.verify(secret)
|
||||
}
|
||||
}
|
||||
|
||||
authenticator.authenticate(javaCreds)
|
||||
}).flatMap { user ⇒
|
||||
addExtraction(authenticator.asInstanceOf[RequestVal[Any]], user)
|
||||
}
|
||||
|
||||
case OAuth2Authentication(authenticator) ⇒
|
||||
authenticateOAuth2Async(authenticator.realm, { creds ⇒
|
||||
val javaCreds =
|
||||
creds match {
|
||||
case Credentials.Missing ⇒
|
||||
new OAuth2Credentials {
|
||||
def available: Boolean = false
|
||||
def identifier: String = throw new IllegalStateException("Credentials missing")
|
||||
def verify(secret: String): Boolean = throw new IllegalStateException("Credentials missing")
|
||||
}
|
||||
case p @ Credentials.Provided(name) ⇒
|
||||
new OAuth2Credentials {
|
||||
def available: Boolean = true
|
||||
def identifier: String = name
|
||||
def verify(secret: String): Boolean = p.verify(secret)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ package akka.http.impl.server
|
|||
|
||||
import java.io.File
|
||||
import akka.http.javadsl.model.ws.Message
|
||||
import akka.http.javadsl.server.values.{ PathMatcher, HttpBasicAuthenticator }
|
||||
import akka.http.javadsl.server.values.{ PathMatcher, HttpBasicAuthenticator, OAuth2Authenticator }
|
||||
import akka.stream.javadsl.Flow
|
||||
|
||||
import scala.language.existentials
|
||||
|
|
@ -54,6 +54,7 @@ private[http] object RouteStructure {
|
|||
}
|
||||
case class Extract(extractions: Seq[StandaloneExtractionImpl[_]])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute
|
||||
case class BasicAuthentication(authenticator: HttpBasicAuthenticator[_])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute
|
||||
case class OAuth2Authentication(authenticator: OAuth2Authenticator[_])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute
|
||||
case class EncodeResponse(coders: immutable.Seq[Coder])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute
|
||||
case class DecodeRequest(coders: immutable.Seq[Coder])(val innerRoute: Route, val moreInnerRoutes: immutable.Seq[Route]) extends DirectiveRoute
|
||||
|
||||
|
|
|
|||
|
|
@ -12,22 +12,23 @@ import scala.concurrent.Future
|
|||
import scala.reflect.ClassTag
|
||||
|
||||
/**
|
||||
* Represents existing or missing HTTP Basic authentication credentials.
|
||||
* Represents existing or missing Http Basic authentication credentials.
|
||||
*/
|
||||
trait BasicUserCredentials {
|
||||
trait BasicCredentials {
|
||||
/**
|
||||
* Were credentials provided in the request?
|
||||
*/
|
||||
def available: Boolean
|
||||
|
||||
/**
|
||||
* The username as sent in the request.
|
||||
* The identifier as sent in the request.
|
||||
*/
|
||||
def userName: String
|
||||
def identifier: String
|
||||
|
||||
/**
|
||||
* Verifies the given secret against the one sent in the request.
|
||||
*/
|
||||
def verifySecret(secret: String): Boolean
|
||||
def verify(secret: String): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -37,7 +38,7 @@ trait BasicUserCredentials {
|
|||
*/
|
||||
abstract class HttpBasicAuthenticator[T](val realm: String) extends AbstractDirective with ExtractionImplBase[T] with RequestVal[T] {
|
||||
protected[http] implicit def classTag: ClassTag[T] = reflect.classTag[AnyRef].asInstanceOf[ClassTag[T]]
|
||||
def authenticate(credentials: BasicUserCredentials): Future[Option[T]]
|
||||
def authenticate(credentials: BasicCredentials): Future[Option[T]]
|
||||
|
||||
/**
|
||||
* Creates a return value for use in [[authenticate]] that successfully authenticates the requests and provides
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.http.javadsl.server.values
|
||||
|
||||
import akka.http.impl.server.{ ExtractionImplBase, RouteStructure }
|
||||
import akka.http.javadsl.server.{ AbstractDirective, RequestVal, Route }
|
||||
import akka.http.scaladsl.util.FastFuture
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
/**
|
||||
* Represents existing or missing OAuth 2 authentication credentials.
|
||||
*/
|
||||
trait OAuth2Credentials {
|
||||
/**
|
||||
* Were credentials provided in the request?
|
||||
*/
|
||||
def available: Boolean
|
||||
|
||||
/**
|
||||
* The identifier as sent in the request.
|
||||
*/
|
||||
def identifier: String
|
||||
|
||||
/**
|
||||
* Verifies the given secret against the one sent in the request.
|
||||
*/
|
||||
def verify(secret: String): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this class to provide an OAuth 2 Bearer Token authentication check. The [[authenticate]] method needs to be implemented
|
||||
* to check if the supplied or missing credentials are authenticated and provide a domain level object representing
|
||||
* the user as a [[RequestVal]].
|
||||
*/
|
||||
abstract class OAuth2Authenticator[T](val realm: String) extends AbstractDirective with ExtractionImplBase[T] with RequestVal[T] {
|
||||
protected[http] implicit def classTag: ClassTag[T] = reflect.classTag[AnyRef].asInstanceOf[ClassTag[T]]
|
||||
def authenticate(credentials: OAuth2Credentials): Future[Option[T]]
|
||||
|
||||
/**
|
||||
* Creates a return value for use in [[authenticate]] that successfully authenticates the requests and provides
|
||||
* the given user.
|
||||
*/
|
||||
def authenticateAs(user: T): Future[Option[T]] = FastFuture.successful(Some(user))
|
||||
|
||||
/**
|
||||
* Refuses access for this user.
|
||||
*/
|
||||
def refuseAccess(): Future[Option[T]] = FastFuture.successful(None)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
protected[http] final def createRoute(first: Route, others: Array[Route]): Route =
|
||||
RouteStructure.OAuth2Authentication(this)(first, others.toList)
|
||||
}
|
||||
|
|
@ -29,10 +29,10 @@ trait SecurityDirectives {
|
|||
*/
|
||||
type AuthenticationResult[+T] = Either[HttpChallenge, T]
|
||||
|
||||
type Authenticator[T] = UserCredentials ⇒ Option[T]
|
||||
type AsyncAuthenticator[T] = UserCredentials ⇒ Future[Option[T]]
|
||||
type AuthenticatorPF[T] = PartialFunction[UserCredentials, T]
|
||||
type AsyncAuthenticatorPF[T] = PartialFunction[UserCredentials, Future[T]]
|
||||
type Authenticator[T] = Credentials ⇒ Option[T]
|
||||
type AsyncAuthenticator[T] = Credentials ⇒ Future[Option[T]]
|
||||
type AuthenticatorPF[T] = PartialFunction[Credentials, T]
|
||||
type AsyncAuthenticatorPF[T] = PartialFunction[Credentials, Future[T]]
|
||||
|
||||
/**
|
||||
* Extracts the potentially present [[HttpCredentials]] provided with the request's [[Authorization]] header.
|
||||
|
|
@ -55,8 +55,8 @@ trait SecurityDirectives {
|
|||
*/
|
||||
def authenticateBasicAsync[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] =
|
||||
extractExecutionContext.flatMap { implicit ec ⇒
|
||||
authenticateOrRejectWithChallenge[BasicHttpCredentials, T] { basic ⇒
|
||||
authenticator(UserCredentials(basic)).fast.map {
|
||||
authenticateOrRejectWithChallenge[BasicHttpCredentials, T] { cred ⇒
|
||||
authenticator(Credentials(cred)).fast.map {
|
||||
case Some(t) ⇒ AuthenticationResult.success(t)
|
||||
case None ⇒ AuthenticationResult.failWithChallenge(challengeFor(realm))
|
||||
}
|
||||
|
|
@ -83,6 +83,49 @@ trait SecurityDirectives {
|
|||
else FastFuture.successful(None))
|
||||
}
|
||||
|
||||
/**
|
||||
* A directive that wraps the inner route with OAuth2 Bearer Token authentication support.
|
||||
* The given authenticator determines whether the credentials in the request are valid
|
||||
* and, if so, which user object to supply to the inner route.
|
||||
*/
|
||||
def authenticateOAuth2[T](realm: String, authenticator: Authenticator[T]): AuthenticationDirective[T] =
|
||||
authenticateOAuth2Async(realm, cred ⇒ FastFuture.successful(authenticator(cred)))
|
||||
|
||||
/**
|
||||
* A directive that wraps the inner route with OAuth2 Bearer Token authentication support.
|
||||
* The given authenticator determines whether the credentials in the request are valid
|
||||
* and, if so, which user object to supply to the inner route.
|
||||
*/
|
||||
def authenticateOAuth2Async[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] =
|
||||
extractExecutionContext.flatMap { implicit ec ⇒
|
||||
authenticateOrRejectWithChallenge[OAuth2BearerToken, T] { cred ⇒
|
||||
authenticator(Credentials(cred)).fast.map {
|
||||
case Some(t) ⇒ AuthenticationResult.success(t)
|
||||
case None ⇒ AuthenticationResult.failWithChallenge(challengeFor(realm))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A directive that wraps the inner route with OAuth2 Bearer Token authentication support.
|
||||
* The given authenticator determines whether the credentials in the request are valid
|
||||
* and, if so, which user object to supply to the inner route.
|
||||
*/
|
||||
def authenticateOAuth2PF[T](realm: String, authenticator: AuthenticatorPF[T]): AuthenticationDirective[T] =
|
||||
authenticateOAuth2(realm, authenticator.lift)
|
||||
|
||||
/**
|
||||
* A directive that wraps the inner route with OAuth2 Bearer Token authentication support.
|
||||
* The given authenticator determines whether the credentials in the request are valid
|
||||
* and, if so, which user object to supply to the inner route.
|
||||
*/
|
||||
def authenticateOAuth2PFAsync[T](realm: String, authenticator: AsyncAuthenticatorPF[T]): AuthenticationDirective[T] =
|
||||
extractExecutionContext.flatMap { implicit ec ⇒
|
||||
authenticateOAuth2Async(realm, credentials ⇒
|
||||
if (authenticator isDefinedAt credentials) authenticator(credentials).fast.map(Some(_))
|
||||
else FastFuture.successful(None))
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifts an authenticator function into a directive. The authenticator function gets passed in credentials from the
|
||||
* [[Authorization]] header of the request. If the function returns ``Right(user)`` the user object is provided
|
||||
|
|
@ -134,24 +177,29 @@ object SecurityDirectives extends SecurityDirectives
|
|||
|
||||
/**
|
||||
* Represents authentication credentials supplied with a request. Credentials can either be
|
||||
* [[UserCredentials.Missing]] or can be [[UserCredentials.Provided]] in which case a username is
|
||||
* [[Credentials.Missing]] or can be [[Credentials.Provided]] in which case an identifier is
|
||||
* supplied and a function to check the known secret against the provided one in a secure fashion.
|
||||
*/
|
||||
sealed trait UserCredentials
|
||||
object UserCredentials {
|
||||
case object Missing extends UserCredentials
|
||||
abstract case class Provided(username: String) extends UserCredentials {
|
||||
def verifySecret(secret: String): Boolean
|
||||
sealed trait Credentials
|
||||
object Credentials {
|
||||
case object Missing extends Credentials
|
||||
abstract case class Provided(identifier: String) extends Credentials {
|
||||
def verify(secret: String): Boolean
|
||||
}
|
||||
|
||||
def apply(cred: Option[BasicHttpCredentials]): UserCredentials =
|
||||
def apply(cred: Option[HttpCredentials]): Credentials = {
|
||||
cred match {
|
||||
case Some(BasicHttpCredentials(username, receivedSecret)) ⇒
|
||||
new UserCredentials.Provided(username) {
|
||||
def verifySecret(secret: String): Boolean = secret secure_== receivedSecret
|
||||
new Credentials.Provided(username) {
|
||||
def verify(secret: String): Boolean = secret secure_== receivedSecret
|
||||
}
|
||||
case None ⇒ UserCredentials.Missing
|
||||
case Some(OAuth2BearerToken(token)) ⇒
|
||||
new Credentials.Provided(token) {
|
||||
def verify(secret: String): Boolean = secret secure_== token
|
||||
}
|
||||
case None ⇒ Credentials.Missing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import SecurityDirectives._
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue