转载请注明:http://blog.csdn.net/HEL_WOR/article/details/51660979
在描述鉴权和流控之前,可能需要先描述为什么需要搭建开放平台。
开放平台最先由FB推出,而后在2012年左右,国内比较大型的互联网公司都开始搭建自己的开放平台。搭建属于自己的开放平台的原因,一般是以下几点:
1.借助第三方满足用户的零碎需求
2.借助第三方提升自己的影响力
3.作为渠道获得第三方应用带来的红利
4.加深与用户的粘连
OAuth2.0协议是一个协议,不是具体的框架或者实现,这个协议定义了一个流程,保证这个流程的实现相应的保证权限管理的成功。
OAuth2.0中主要规定了三方。
1.第三方程序
2.用户
3.保存用户信息的服务器
OAuth2.0协议出现是为了解决第三方程序可以获取保存在服务器上的用户的信息但用户又能不将自己的账号密码告知第三方程序。
这个过程通过3个流程来实现。
1.第三方程序向平台注册应用,获取AppKey,Appsecret。
2.第三方程序向平台提供的接口1发起请求,平台向用户索要账号密码,验证通过后返回给第三方程序一个code(临时授权码)。
3.第三方程序拿着这个code再次向平台提供的接口2发起请求,并需一同提供AppKey,Appsecret,平台校验通过后返回accessToken,第三方程序就可以拿着这个accessToken开始真正的业务操作了。
code的出现,是为了防止中间人攻击,即,平台需要第三方程序证明是其需要获取用户的信息,而不是拦截下信息的其他程序。
除了accessToken,一些开放平台,比如微信,会一同返回refreshToken,其用来刷新accessToken。
以新浪开放平台和微信开放平台为例,描述鉴权的实现流程.
新浪API:
所以,我们可以定义三个接口:
public String generateRequestToken(String appKey) throws AuthorizeException;
此接口用于获取code
public OpenRefreshToken generateAccessToken(String appKey, final String appSecret, String requestToken) throws AuthorizeException;
此接口用于获取accessToken。
如果参见新浪api和微信api的授权返回值,其分别如下:
新浪API
{
"accesstoken":"ACCESSTOKEN",
"expires_in":"1234",
"uid":"123456"
}
微信API
{
"accessToken":"ACCESS_TOKEN",
"expires_in":"7200",
"refresh_token":"REFRESHTOKEN",
"open_id":"OPEN_ID",
"scope":"SCOPE"
}
不同的平台根据其业务的不同其返回的值也不同。
设计的接口的返回对象将在上层逻辑中被处理再以JSON格式返回给第三方应用。
public OpenRefreshToken refreshAccessToken(String appSecret, String refreshToken) throws AuthorizeException;
此接口用于刷新accesstoken,除了需要传入refreshtoken外,还需要传入appkey和appsecret以验证第三方程序的身份,其与refreshtoken是否匹配,注:这一点上不同平台可能有不同。
以微信的刷新授权令牌流程为例(AS-REDIS为个人猜测):
最后根据业务的不同提供常规的业务接口,但是这个接口中需要带上accessToken。
按照OAuth2协议,接口的设计就这样定下来了。对于权限判断的实现,其实可以看出来appkey,code,accesstoken,refreshtoken之间的映射关系,根据协议的实现流程,我们能看出:
appkey -> code
code -> accesstoken
accesstoken <-> refreshtoken
1.通过appkey能获得code
2.通过code能获得accesstoken
3.通过accesstoken能获得refreshtoken,否则任何一个refreshtoken都可用于刷新
4.通过refreshtoken能获得accesstoken,要让refreshtoken知道该刷新谁。
但在接口的实现中,如果保存这种映射关系。我们需要验证信息在这OAuth2协议中交互的3步中被传递。
用户可访问的接口,应该在返回code时就被确定下来,并被缓存下来用于后续的第三方应用接口鉴权。
应用的appkey,appsecret也应在返回code前被缓存下来,用于code的验证,accesstoken的验证。
accesstoken的超时时间,refreshtoken的超时时间,也应被缓存下来用于请求到达时的超时判断。
1.每个appkey对应一个code对象,每个code对象生成一个code,将code返回给第三方应用。这是交互的第一步。
2.第三方应用带上appkey,appsecret,code请求获取accesstoken的接口,通过code找到code对象,比对code对象中缓存的appkey,appsecret以确认该code是用户授权给该应用获得的,验证成功后生成accesstoken对象和refreshtoken对象,让accesstoken对象成为refreshtoken对象的一个属性,设置超时时间,返回accestoken对象/refreshtoken对象生成的accesstoken码/refreshtoken码,这是交互的第二步。
3.刷新accesstoken,用户带上refrehtoken码/第三方应用调用业务接口,带上accesstoken。
映射关系可以通过K-V内存数据库完成,由于这些验证数据都是有状态的,将有状态的数据集中放入缓存或数据库,以保证应用的无状态,便于横向扩展。
那么映射关系如下,以redis作为缓存数据库:
appkey,用户授权 -> 应用相关信息类A(比如appkey,UID,可访问接口)
code码 -> code对象,包括类A
accesstoken -> accesstoke对象,包括类A,超时时间,生成时间
refreshtoken -> refreshtoken对象,包括类A,包括accesstoken对象,超时时间,生成时间
最近在负责公司开发平台授权和流控部分,授权按照OAuth2协议实现,以下是code,accesstoken,refreshtoken的主要流程,方便对以上内容的理解,上述内容可能会显得有些抽象。
/**
* api开放平台授权、鉴权实现
*
* @author zhenghao:
* @version 1.0 2016/5/27
*/
@Service("asAuthorizeService")
public class AsAuthorizeServiceImpl implements AsAuthorizeService {
@Autowired
private ValidatorService validatorService;
@Autowired
private CacheService cacheService;
@Autowired
private OpenTokenService openTokenService;
@Autowired
private OpenSecretService openSecretService;
/**
*
* @param appKey
* @param appSecret
* @return
* @throws AuthorizeException
* @desription 后期的用户验证将在此逻辑中完成
* 通过AppSecret获取OpenConsumer对象
* 通过获取结果以及验证AppKey与OpenConsumer对象中appkey是否一致完成验证
* 验证通过后创建OpenRequestToken对象并返回code(临时授权码)
* code与generateAccessToken函数中参数:requestToken是一个含义
*/
@Override
public String generateRequestToken(String appKey, String appSecret) throws AuthorizeException {
OpenConsumer openConsumer = validatorService.validateAppKeyAndAppSecret(appKey, appSecret);
OpenRequestToken openRequestToken = new OpenRequestToken();
openRequestToken.setOpenConsumer(openConsumer);
openRequestToken.setTimeStamp(System.currentTimeMillis());
String code = openRequestToken.generate();
cacheService.setRequestToken(code, openRequestToken);
return code;
}
/**
*
* @param appKey
* @param appSecret
* @param requestToken 临时令牌
* @return
* @throws AuthorizeException
* @Description 验证RequestToken是否为向用户请求授权的客户端发出
* 验证成功后创建AccessToken和RefreshToken
* 将AccessToken,RefreshToken存于Reis,将AccessToken另存于数据库
* 出于实时性的考虑,存Redis未采用异步操作
* 返回的OpenRefreshToken将在API层做处理
*/
@Override
public OpenRefreshToken generateAccessToken(String appKey, final String appSecret, String requestToken) throws AuthorizeException {
OpenRequestToken openRequestToken = validatorService.validateRequestToken(appKey, appSecret,requestToken);
final OpenAccessToken openAccessToken = new OpenAccessToken();
openAccessToken.setOpenConsumer(openRequestToken.getOpenConsumer());
openAccessToken.setTimeStamp(System.currentTimeMillis());
final String accessToken = openAccessToken.generate();
OpenRefreshToken openRefreshToken = new OpenRefreshToken();
openRefreshToken.setAccessToken(openAccessToken);
openRefreshToken.setTimeStamp(openAccessToken.getTimeStamp());
openRefreshToken.setOpenConsumer(openAccessToken.getOpenConsumer());
String refreshToken = openRefreshToken.generate();
cacheService.setOpenRefreshToken(refreshToken, openRefreshToken);
cacheService.setOpenAccessToken(accessToken, openAccessToken);
new Thread(new Runnable()
{
@Override
public void run() {
OpenTokenPo openTokenPo = openTokenService.getOpenTokenByAppSecret(appSecret);
if(openTokenPo == null){
openTokenPo = new OpenTokenPo();
openTokenPo.setCreateTime(new Date(openAccessToken.getTimeStamp()));
OpenSecretPo openSecretPo = openSecretService.getOpenSecretByAppSecret(appSecret);
openTokenPo.setSecret(openSecretPo);
}
openTokenPo.setAccessToken(accessToken);
openTokenPo.setExpiredTime(new Date(openAccessToken.getTimeStamp() + openAccessToken.getExpiredTime()));
openTokenPo.setUpdateTime(openTokenPo.getCreateTime());
openTokenService.saveOrUpdateOpenToken(openTokenPo);
}
}).start();
return openRefreshToken;
}
/**
*
* @param appKey
* @param appSecret
* @param refreshToken 刷新令牌
* @return
* @throws AuthorizeException
* @Desription 以下:AccessToken表示服务器端OpenAccessToken对象,accesstoken表示返回给客户端的令牌
* 刷新accesstoken,通过重置TimeStamp,重新生成accesstoken完成
* 未使用如果accesstoken未超时则续期,accsstoken值不变,超时则重新生成accesstoken的逻辑
* 对于新生成的accesstoken,需要立即保存到redis等待用户请求
* 对于accesstoken刷新带来的redis中删除旧的OpenAccessToken,更新redis中OpenRefreshToken(因为嵌套),
* 更新DB中OpenToken记录,可以异步执行。
*/
@Override
public OpenRefreshToken refreshAccessToken(String appKey, final String appSecret, final String refreshToken) throws AuthorizeException {
OpenRefreshToken openRefreshToken = validatorService.validateRefreshToken(appKey, appSecret, refreshToken);
final OpenAccessToken requiredRefreshOpenAccessToken = (OpenAccessToken) openRefreshToken.getAccessToken();
final String oldAccessToken = requiredRefreshOpenAccessToken.getToken();
requiredRefreshOpenAccessToken.setTimeStamp(System.currentTimeMillis());
final String newAccessToken = requiredRefreshOpenAccessToken.generate();
openRefreshToken.setAccessToken(requiredRefreshOpenAccessToken);
cacheService.removeOpenAccessToken(oldAccessToken);
cacheService.setOpenAccessToken(newAccessToken, requiredRefreshOpenAccessToken);
cacheService.setOpenRefreshToken(refreshToken, openRefreshToken);
new Thread(new Runnable()
{
@Override
public void run() {
OpenTokenPo openTokenPo = openTokenService.getOpenTokenByAppSecret(appSecret);
openTokenPo.setAccessToken(newAccessToken);
openTokenPo.setUpdateTime(new Date(requiredRefreshOpenAccessToken.getTimeStamp()));
openTokenPo.setExpiredTime(new Date(requiredRefreshOpenAccessToken.getTimeStamp() + requiredRefreshOpenAccessToken.getExpiredTime()));
openTokenService.saveOrUpdateOpenToken(openTokenPo);
}
}).start();
return openRefreshToken;
}