上一篇讨论了SSL/TLS安全连接,主要是一套在通信层面的数据加密解决方案。但我们更需要一套方案来验证客户端。要把不能通过验证的网络请求过滤掉。
OAuth2是一套行业标准的网络资源使用授权协议,也就是为用户提供一种授权凭证,用户凭授权凭证来使用网络资源。申请凭证、然后使用凭证进行网络操作流程如下:
实际上OAuth2是一套3方授权模式,但我们只需要资源管理方授权,所以划去了1、2两个步骤。剩下的两个步骤,包括:申请令牌,使用令牌,这些在官方文件中有详细描述。用户身份和令牌的传递是通过Http Header实现的,具体情况可参考RFC2617,RFC6750
简单来说:用户向服务器提交身份信息申请令牌,下面是一个HttpRequest样例:
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded
上面Basic后面一串代码就是 user+password的加密文,它的产生方法示范如下:
final case class BasicHttpCredentials(username: String, password: String) extends jm.headers.BasicHttpCredentials { val cookie = { val userPass = username + ':' + password val bytes = userPass.getBytes(`UTF-8`.nioCharset) Base64.rfc2045.encodeToChar(bytes, false) } def render[R <: Rendering](r: R): r.type = r ~~ "Basic " ~~ cookie override def scheme: String = "Basic"
override def token: String = String.valueOf(cookie) override def params: Map[String, String] = Map.empty }
注:在OAuth2版本中如果使用https://,则容许明文用户和密码。
服务端在返回的HttpResponse中返回令牌access_token:
{"access_token":"2e510027-0eb9-4367-b310-68e1bab9dc3d", "token_type":"bearer", "expires_in":3600}
注意:这个expires_in是应用系统自定义内部使用的参数,也就是说应用系统必须自备令牌过期失效处理机制。
得到令牌后每个使用网络资源的Request都必须在Authorization类Header里附带这个令牌,如:
GET /resource HTTP/1.1 Host: server.example.com Authorization: Bearer 2e510027-0eb9-4367-b310-68e1bab9dc3d
Bearer后就是服务端返回的令牌值。我们还是设计一个例子来示范整个授权使用过程。先看看下面一些基本操作代码:
object JsonMarshaller extends SprayJsonSupport with DefaultJsonProtocol { case class UserInfo(username: String, password: String) case class AuthToken(access_token: String = java.util.UUID.randomUUID().toString, token_type: String = "bearer", expires_in: Int = 3600) case class AuthUser(credentials: UserInfo, token: AuthToken = new AuthToken(expires_in = 60 * 60 * 8), loggedInAt: String = LocalDateTime.now().toString) val validUsers = Seq(UserInfo("johnny", "p4ssw0rd"),UserInfo("tiger", "secret")) val loggedInUsers = mutable.ArrayBuffer.empty[AuthUser] def getValidUser(credentials: Credentials): Option[UserInfo] = credentials match { case p @ Credentials.Provided(_) => validUsers.find(user => user.username == p.identifier && p.verify(user.password)) case _ => None } def authenticateUser(credentials: Credentials): Option[AuthUser] = credentials match { case p @ Credentials.Provided(_) => loggedInUsers.find(user => p.verify(user.token.access_token)) case _ => None } implicit val fmtCredentials = jsonFormat2(UserInfo.apply) implicit val fmtToken = jsonFormat3(AuthToken.apply) implicit val fmtUser = jsonFormat3(AuthUser.apply) }
validUers: Seq[UserInfo] 模拟是个在服务端数据库里的用户登记表,loggedInUsers是一个已经通过验证的用户请单。函数 getValidUser(credentials: Credentials) 用传人参数Credentials来获取用户信息Option[UserInfo]。Credentials是这样定义的:
object Credentials { case object Missing extends Credentials abstract case class Provided(identifier: String) extends Credentials { /** * First appli