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,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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue