03 | 授权服务:授权码和访问令牌的颁发流程是怎样的? 笔记

38 阅读5分钟

03 | 授权服务:授权码和访问令牌的颁发流程是怎样的?

授权服务的工作过程

小兔软件需要去到京东的平台那里”备案“注册,京东商家开放平台就会给小兔软件 app_id 和 app_secret 等信息,以方便后面授权时的各种身份校验,也会有请求资源的访问范围,比如只能访问3个月内的订单数据。
代码:


Map<String,String> appMap =  new HashMap<String, String>();//模拟第三方软件注册之后的数据库存储

appMap.put("app_id","APPID_RABBIT");
appMap.put("app_secret","APPSECRET_RABBIT");
appMap.put("redirect_uri","http://localhost:8080/AppServlet-ch03");
appMap.put("scope","nickname address pic");

授权服务的工作,可以划分为 颁发授权码 code、颁发访问令牌 access_token。
image.png

过程一:颁发授权码 code

在用户进行授权操作的时候,需要进行一些准备的工作。

第一步,验证基本信息

授权服务需要对回调地址做基本的校验,这个回调地址在client注册时就已经提供,因此可以检查是否被替换。(因为有些非法的请求,可能会伪造回调地址,钓鱼之类的)


if(!appMap.get("redirect_uri").equals(redirectUri)){
    //回调地址不存在
}

第二步,验证权限范围(第一次)。

第三方软件可以获得你的昵称、头像、性别、地理位置等. 比如是否授予小兔获取 3 个月以前的订单的访问权限。
第一次权限校验


String scope = request.getParameter("scope");
if(!checkScope(scope)){
    //超出注册的权限范围
}

第三步,生成授权请求页面。

授权服务上的页面
image.png

第四步,验证权限范围(第二次)。

第二次校验主要是用户选择的权限范围,我们也需要校验,判断是否合理。


String[] rscope =request.getParameterValues("rscope");

if(!checkScope(rscope)){
    //超出注册的权限范围
}

第五步,处理授权请求,生成授权码 code。

当小明同意授权之后,授权服务会校验响应类型 response_type 的值


String responseType = request.getParameter("response_type");
if("code".equals(responseType)){
  
}

在授权服务中,需要将生成的授权码 code 值与 app_id、user 进行关系映射,比如小明给小兔软件进行的授权

String code = generateCode(appId,"USERTEST");//模拟登录用户为USERTEST

private String generateCode(String appId,String user) {
  ...
  String code = strb.toString();
  codeMap.put(code,appId+"|"+user+"|"+System.currentTimeMillis());
  return code;
}

OAuth 2.0 规范建议授权码 code 值有效期为 10 分钟,并且一个授权码 code 只能被使用一次授权服务还需要将生成的授权码 code 跟已经授权的权限范围 rscope 进行绑定并存储

Map<String,String[]> codeScopeMap =  new HashMap<String, String[]>();

codeScopeMap.put(code,rscope);//授权范围与授权码做绑定

第六步,重定向至第三方软件。

生成授权码 code 值之后,授权服务需要将该 code 值告知第三方软件小兔.这个是前端的操作,重定向

Map<String, String> params = new HashMap<String, String>();
params.put("code",code);

String toAppUrl = URLParamsUtil.appendParams(redirectUri,params);//构造第三方软件的回调地址,并重定向到该地址

response.sendRedirect(toAppUrl);//授权码流程的“第二次”重定向

过程二:颁发访问令牌 access_token

当小兔拿着授权码 code 来请求的时候,授权服务需要为之生成最终的请求访问令牌

第一步,验证第三方软件是否存在。

接收到的 grant_type 的类型为 authorization_code。


String grantType = request.getParameter("grant_type");
if("authorization_code".equals(grantType)){
  
}

由于颁发访问令牌是通过后端通信完成的,所以这里除了要校验 app_id 外,还要校验 app_secret。

if(!appMap.get("app_id").equals(appId)){
    //app_id不存在
}

if(!appMap.get("app_secret").equals(appSecret)){
    //app_secret不合法
}

第二步,验证授权码 code 值是否合法。

授权服务在颁发授权码 code 的阶段已经将 code 值存储了起来.所以我们只需要校验

String code = request.getParameter("code");
if(!isExistCode(code)){//验证code值
  //code不存在
  return;
}
codeMap.remove(code);//授权码一旦被使用,须立即作废

确认过授权码 code 值有效以后,应该立刻从存储中删除当前的 code 值,以防止第三方软件恶意使用一个失窃的授权码 code 值来请求授权服务。

第三步,生成访问令牌 access_token 值。

access_token 的值原则:唯一性、不连续性、不可猜性
个访问令牌 access_token 表示某一个用户给某一个第三方软件进行授权。授权服务还需要将授权范围跟访问令牌 access_token 做绑定。

Map<String,String[]> tokenScopeMap =  new HashMap<String, String[]>();

String accessToken = generateAccessToken(appId,"USERTEST");//生成访问令牌access_token的值
tokenScopeMap.put(accessToken,codeScopeMap.get(code));//授权范围与访问令牌绑定

//生成访问令牌的方法
private String generateAccessToken(String appId,String user){
  
  String accessToken = UUID.randomUUID().toString();
  String expires_in = "1";//1天时间过期
  tokenMap.put(accessToken,appId+"|"+user+"|"+System.currentTimeMillis()+"|"+expires_in);

  return accessToken;
}

信息通过结构化的处理放入令牌本身。我们将包含了一些信息的令牌,称为结构化令牌,简称 JWT。

刷新令牌

比如令牌10分钟之后过期,总不能让用户10分钟再授权一次吧,所以就需要续约,刷新。
其实,颁发刷新令牌和颁发访问令牌是一起实现的,都是在过程二的步骤三生成访问令牌 access_token 中生成的。

Map<String,String> refreshTokenMap =  new HashMap<String, String>();

String refreshToken = generateRefreshToken(appId,"USERTEST");//生成刷新令牌refresh_token的值

private String generateRefreshToken(String appId,String user){

  String refreshToken = UUID.randomUUID().toString();

  refreshTokenMap.put(refreshToken,appId+"|"+user+"|"+System.currentTimeMillis());
  return refreshToken;
  
} 

访问令牌失效了,就使用刷新令牌。

使用刷新令牌

在 OAuth 2.0 规范中,刷新令牌是一种特殊的授权许可类型,是嵌入在授权码许可类型下的一种特殊许可类型。

第一步,接收刷新令牌请求,验证基本信息。

此时请求中的 grant_type 值为 refresh_token。

String grantType = request.getParameter("grant_type");
if("refresh_token".equals(grantType)){
  
}

使用刷新令牌也是需要校验合法性,


String grantType = request.getParameter("grant_type");
if("refresh_token".equals(grantType)){
  //该refresh_token值不存在
}

if(!appStr.startsWith(appId+"|"+"USERTEST")){
    //该refresh_token值不是颁发给该第三方软件的
}

需要注意,一个刷新令牌被使用以后,授权服务需要将其废弃,并重新颁发一个刷新令牌

第二步,重新生成访问令牌

总结

  1. 授权服务的核心就是,先颁发授权码 code 值,再颁发访问令牌 access_token 值。
  2. 在颁发访问令牌的同时还会颁发刷新令牌 refresh_token 值,这种机制可以在无须用户参与的情况下用于生成新的访问令牌。
  3. 授权还要有授权范围,不能让第三方软件获得比注册时权限范围还大的授权,也不能获得超出了用户授权的权限范围,始终确保最小权限安全原则。

若access_token未超时,那么进行refresh_token有两种方式,
(1)不会改变access_token,但超时时间会刷新,相当于续期access_token
(2)更新access_token的值,我们建议【统一更新access_token的值】。(不可以让“token一个更长的有效期”存在的) 原文