未分类

REST服务下设计AccessToken

最近要设计一套API以提供给接入商使用(以下简称corp),正好可使用Play2REST天然良好的支持。但是在AccessToken的设计时费了下精力。参考了一些网上的设计,大同小异。最后参考了微信公众号的AccesToken设计方案,方案见:http://mp.weixin.qq.com/wiki/11/0e4b294685f817b95cbed85ba5e82b8f.html

我的方案

  • 客户端调用/api/token接口,传入client_id, client_secret请求参数以生成Access Token,调用成功将返回JSON,包含两个参数:access_tokenexpires_in
  • 每次HTTP请求,客户端都应在请求参数上附加access_token参数
  • Play Action中对access_token进行校验

因为使用Play2开发REST服务,对于Access Token的校验自然就想到了使用自定义Action来实现。在自定义Action中,对每次请求参数中的access_token将进行有效性校验,校验失败会返回错误。自定义Action代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case class ClientTokenRequest[A](clientToken: ClientAccessTokenInfo,
request: Request[A]) extends WrappedRequest[A](request)

object ClientAction
extends ActionBuilder[ClientTokenRequest]
with ActionTransformer[Request, ClientTokenRequest] {

override protected def transform[A](request: Request[A]): Future[ClientTokenRequest[A]] = {
request.getQueryString("access_token") match {
case Some(at) =>
ApiClientTokenService().getTokenInfoByAccessToken(at) match {
case Some(token) => Future.successful(new ClientTokenRequest(token, request))
case None => throw FjUnauthorizedException("access token invalid")
}
case None =>
throw FjUnauthorizedException("access token not exists")
}
}
}

ApiClientTOkenService().getTokenInfoByAccessToken()方法中,根据在url上的access_token参数在redis中查找相应键值。代码示例如下:

1
2
3
4
5
6
7
8
redisClients.withClient { client =>
client.get(Commons.API_CLIENT_TOKEN_REDIS_KEY_PREFIX + accessToken).flatMap(s => s.split('\n') match {
case Array(corpId, clientId) =>
Some(ClientAccessTokenInfo(accessToken, corpId, clientId))
case _ =>
None
})
}

对于AccessToken超期的设计,可以使用redis提供的EXPIRE功能(http://redis.io/commands/expire)。使用`scala-redis`库,它封装好了在scala下访问redis的各种API。在`set`方法中可以设置键的超时值。

1
2
3
4
5
6
7
8
9
10
11
def createClientTokenInfo(corp: Corporation) = {
val token = ApiClientTokenService.generateToken(corp)
redisClients.withClient { client =>
client.set(
Commons.API_CLIENT_TOKEN_REDIS_KEY_PREFIX + token.accessToken,
corp.id + '\n' + corp.client_id,
false,
Seconds(Settings.server.clientTokenTimeout.getSeconds + 10))
}
token
}

使用redis来保存Access Token有诸多好处:

  • 简单、快捷,不需要自己设计超期、并发等功能。简化代码
  • 无状态,便于系统横向扩展
分享到