回答

10

一個非常簡單的方法是使用動作組合。作爲一個例子,看一看Guillaume Bort提供的這個Gist:https://gist.github.com/guillaumebort/2328236。如果你想在異步操作中使用它,你可以寫下類似的東西:

def BasicSecured[A](username: String, password: String)(action: Action[A]): Action[A] = Action.async(action.parser) { request => 
    request.headers.get("Authorization").flatMap { authorization => 
    authorization.split(" ").drop(1).headOption.filter { encoded => 
     new String(org.apache.commons.codec.binary.Base64.decodeBase64(encoded.getBytes)).split(":").toList match { 
     case u :: p :: Nil if u == username && password == p => true 
     case _ => false 
     } 
    } 
    }.map(_ => action(request)).getOrElse { 
    Future.successful(Unauthorized.withHeaders("WWW-Authenticate" -> """Basic realm="Secured Area"""")) 
    } 
} 

SSL與基本認證沒有任何關係。您可以直接或通過前端HTTP服務器(如ngnix)使用API​​的HTTPS。有關此主題的Play文檔中有非常好的細節。

+0

我已將此代碼的重新複製副本添加到單獨的答案中。謝謝,正如預期的那樣,這項工作的機制很好。我不喜歡在字符串拆分中使用「」和「:」,因爲簡單的字符分隔符就足夠了,並且避免了正則表達式的開銷。另外,帶謂詞的過濾器可以用'collect'方法排列在單行上。重寫的主要動機是煩人的三角形空白,因爲代碼越來越深。這是要求理解:-) –

+0

帶':'的密碼將被分割多次並被拒絕。 – Niklas

2

對於斯卡拉,Secure Social可能是最好的解決方案。您會在給定的鏈接中找到大量的文檔和示例。 你也可以看看Play2-auth作爲另一個有效的選項。

您會在Play 2 Modules列表中找到更多可能性。

如果您想要/需要烘焙您自己的解決方案,那麼查看現有解決方案的代碼以獲取靈感和想法可能仍然有用。儘管如此,除非你真的需要它(和/或真正知道你在做什麼),否則我對任何與安全有關的一般建議都不是自己實現它。

順便說一句,這裏絕對沒有關於REST的具體信息。你本質上是保護你的控制器方法,所以無論他們的調用是否由REST調用觸發都沒有關係。

3

如果我們只是在談論基本身份驗證,您不需要任何外部模塊。基本上,你可以使用action composition來實現它。

Here就是一個完整的例子。

如果您還需要授權,您可以簡單地將前面的示例與Deadbolt組合起來。它將允許您提供對某些客戶羣的訪問並拒絕他人訪問。

SSL支持與認證沒有任何關係。但是,在Play Documentation

2

中解釋也可以使用過濾器。以下是基於Play 2.5。

import org.apache.commons.codec.binary.Base64 

override def apply(nextFilter: RequestHeader => Future[Result]) 
       (requestHeader: RequestHeader): Future[Result] = { 

val auth = requestHeader.headers.get("Authorization") 
val invalidResult = Future.successful(
    Unauthorized.withHeaders("WWW-Authenticate" -> """Basic realm="Secured"""") 
) 

if (auth.isEmpty) { 
    invalidResult 
} 
else { 
    val credentials = new String(Base64.decodeBase64(auth.get.split(" ").drop(1).head.getBytes)).split(":") 

    if (credentials.length < 2) { 
    invalidResult 
    } 
    else { 
    for { 
     authVerify <- verify(credentials(0), credentials(1)) 
     r <- { 
     if (authVerify) { 
      nextFilter(requestHeader).map { result: Result => result } 
     } 
     else { 
      invalidResult 
     } 
     } 
    } yield { 
     r 
    } 
    } 
} 
} 

def verify(username: String, password: String): Future[Boolean] 
3

基本上,我已經從@centr那裏得到了答案,並試圖讓它更具可讀性。看看你是否喜歡這個版本的相同代碼。徹底測試,按預期工作。

def BasicSecured[A](username: String, password: String)(action: Action[A]): Action[A] = Action.async(action.parser) { request => 
    val submittedCredentials: Option[List[String]] = for { 
     authHeader <- request.headers.get("Authorization") 
     parts <- authHeader.split(' ').drop(1).headOption 
    } yield new String(decodeBase64(parts.getBytes)).split(':').toList 

    submittedCredentials.collect { 
     case u :: p :: Nil if u == username && p == password => action(request) 
    }.getOrElse { 
     Future.successful(Unauthorized.withHeaders("WWW-Authenticate" -> """Basic realm="Secured Area"""")) 
    } 
    }