import com.typesafe.scalalogging.slf4j.Logging import sun.misc.BASE64Decoder import play.api.mvc._ import scala.concurrent.Future import play.mvc.Results._ import play.api.libs.concurrent.Execution.Implicits.defaultContext object BasicAuthFilter extends Filter with Logging { private lazy val unauthResult = Results.Unauthorized.withHeaders(("WWW-Authenticate", "Basic realm=\"myRealm\"")) private lazy val passwordRequired = true private lazy val username = "someUsername" private lazy val password = "somePassword" private lazy val outsidePages = Seq("ping.html") //need the space at the end private lazy val basicSt = "basic " //This is needed if you are behind a load balancer or a proxy private def getUserIPAddress(request: RequestHeader): String = { return request.headers.get("x-forwarded-for").getOrElse(request.remoteAddress.toString) } private def logFailedAttempt(requestHeader: RequestHeader) = { logger.warn(s"IP address ${getUserIPAddress(requestHeader)} failed to log in, " + s"requested uri: ${requestHeader.uri}") } private def decodeBasicAuth(auth: String): Option[(String, String)] = { if (auth.length() < basicSt.length()) { return None } val basicReqSt = auth.substring(0, basicSt.length()) if (basicReqSt.toLowerCase() != basicSt) { return None } val basicAuthSt = auth.replaceFirst(basicReqSt, "") //BESE64Decoder is not thread safe, don't make it a field of this object val decoder = new BASE64Decoder() val decodedAuthSt = new String(decoder.decodeBuffer(basicAuthSt), "UTF-8") val usernamePassword = decodedAuthSt.split(":") if (usernamePassword.length >= 2) { //account for ":" in passwords return Some(usernamePassword(0), usernamePassword.drop(1).mkString(":"))) } None } private def isOutsideSecurityRealm(requestHeader: RequestHeader): Boolean = { val reqURI = requestHeader.uri if (reqURI.length() > 0) { //remove the first "/" in the uri return outsidePages.contains(reqURI.substring(1)) } false } def apply(nextFilter: (RequestHeader) => Future[SimpleResult])(requestHeader: RequestHeader): Future[SimpleResult] = { if (!passwordRequired || isOutsideSecurityRealm(requestHeader)) { return nextFilter(requestHeader) } requestHeader.headers.get("authorization").map { basicAuth => decodeBasicAuth(basicAuth) match { case Some((user, pass)) => { if (username == user && password == pass) { return nextFilter(requestHeader) } } case _ => ; } logFailedAttempt(requestHeader) return Future.successful(unauthResult) }.getOrElse({ logFailedAttempt(requestHeader) Future.successful(unauthResult) }) } }