本文已收录在Github,关注我,紧跟本系列专栏文章,咱们下篇再续!
授权服务如何生成访问令牌?
访问令牌过期了而用户又不在场的情况下,又如何重新生成访问令牌?
xx让我去公众号开放平台给它授权数据时,你是否好奇?开放平台咋知道 xx 是谁?他合法备案了吗?病毒软件咋办?
所以,授权前提是xx要去开放平台备案,即注册。之后,开放平台就会给xx软件app_id
和app_secret
等,方便后续授权时的各种校验。
注册时,三方软件也会请求受保护资源的可访问范围。如xx能否获取我的公众号半年前的文章,能否获取每个文章的所有信息(比如标题、封面、标签)等。即scope。
注册后,xx过来让平台把我的文章数据都给xx,平台核实后确认xx合法。
上文说过,授权码许可类型中,授权服务的工作,可划分两大部分:
授权服务负责准备工作和生成授权码code。
验证基本信息、权限范围(第一次)和生成授权请求页面。
Web颁发code的整个请求过程,都通过浏览器由前端通信完成,即所有信息都可能被伪造,如回调地址,将其伪装成钓鱼页面,授权服务需要对回调地址做基本校验。
授权服务须对三方软件的存在性判断:
if(!appMap.get("redirect_uri").equals(redirectUri)) {
// 回调地址不存在
}
授权服务程序中,这两步验证通过后,会生成或响应一个页面(授权服务器上的页面),提示小明进行授权。
授权范围。如用微信登录三方软件时,微信提示我们,三方软件可获得你的昵称、头像、性别、地理位置等。如你不想让它们获取你的某信息,可不选择该项。
对xx传过来的scope参数,与小兔注册时申请的权限范围对比。
第一次权限校验:
String scope = request.getParameter("scope");
if(!checkScope(scope)) {
// 超出注册的权限范围
}
即授权服务上的页面,页面上显示注册时申请的权限,我可选择缩小这个权限范围。
至此,颁发授权码code准备工作完成。当用户点击授权按钮“approve”后,才会生成授权码code值和访问令牌acces_token
。
只有用户登录了才可对三方软件授权,授权服务才能够获得用户信息并最终生成code 和 app_id(第三方软件的应用标识) + user(资源拥有者标识)之间的对应关系。
我扫码同意后,生成授权码code的流程开始,主要包括:
步骤②生成授权页面前授权服务进行的第一次校验,是对比xx请求的权限范围和注册时的权限。
这相当于一次用户的输入权限。我选择了权限范围给授权服务,对权限的校验,凡输入性数据都涉及合法性检查。
String[] rscope =request.getParameterValues("rscope");
if(!checkScope(rscope)) {
// 超出注册的权限范围
}
我同意授权后,授权服务会校验响应类型response_type
的值。response_type有code和token两种类型的值。
用授权码流程举例,因此代码要验证response_type的值是否为code。
String responseType = request.getParameter("response_type");
if("code".equals(responseType)) {
...
}
授权服务中,需要将生成的授权码code值与app_id、user进行关系映射。即一个授权码code,表示某用户给某三方软件授权。同时要将code和这种映射关系保存,以便在生成访问令牌access_token时使用。
// 模拟登录用户为USERTEST
String code = generateCode(appId,"USERTEST");
private String generateCode(String appId,String user) {
...
String code = strb.toString();
codeMap.put(code,appId+"|"+user+"|"+System.currentTimeMillis());
return code;
}
生成授权码code后,也按照上面所述绑定了响应的映射关系。还需要为code设置有效期。
OAuth 2.0规范建议授权码code值有效期为10分钟,并且一个授权码code只能被使用一次。生产环境code有效期一般不超过5min。
授权服务还要将生成的授权码code跟已授权的权限范围rscope进行绑定并存储,以便后续颁发访问令牌时,能够通过code值取出授权范围并与访问令牌绑定。因三方软件最终是通过访问令牌来请求受保护资源。
Map<String,String[]> codeScopeMap = new HashMap<String, String[]>();
codeScopeMap.put(code,rscope);//授权范围与授权码做绑定
生成授权码code后,授权服务需要将该code值告知第三方软件。
颁发授权码code是前端通信完成,因此这里采用重定向。即第二次重定向
Map<String, String> params = new HashMap<String, String>();
params.put("code",code);
String toAppUrl = URLParamsUtil.appendParams(redirectUri,params);//构造第三方软件的回调地址,并重定向到该地址
response.sendRedirect(toAppUrl);//授权码流程的“第二次”重定向
至此,颁发授权码code的流程全部完成。xx获取到授权码code值后,就可请求访问令牌access_token的值,即过程二。
xx最终要获取访问令牌access_token,才可请求受保护资源。而授权码只是一个换取访问令牌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值,此时对比从request中接收到的code值和从存储中取出来的code值。在我们给出的课程相关代码中,code值对应的key是app_id和user的组合值。
String code = request.getParameter("code");
if(!isExistCode(code)){//验证code值
//code不存在
return;
}
codeMap.remove(code);//授权码一旦被使用,须立即作废
确认过授权码code值有效后,应立刻从存储中删除当前code值,以防止第三方软件恶意使用一个失窃的授权码code值来请求授权服务。
OAuth 2.0规范规定必须符合三个原则:唯一性、不连续性、不可猜性。UUID可考虑来作为示例的。
和授权码code值一样,需要存储访问令牌access_token值,并将其与三方软件应用标识app_id和资源拥有者标识user映射。也就是说,一个访问令牌access_token表示某一个用户给某一个第三方软件进行授权。
同时,授权服务还需要将授权范围跟访问令牌access_token做绑定。最后要为该访问令牌设置一个过期时间expires_in。
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;
}
OAuth 2.0没有约束访问令牌内容的生成规则:
至此,授权码许可类型下授权服务的两大主要过程,也就是颁发授权码和颁发访问令牌的流程,我就与你讲完了。
颁发授权码和颁发访问令牌,就是授权服务的核心。
在生成访问令牌的时附加过期时间expires_in
访问令牌会在一定的时间后失效。访问令牌失效,资源拥有者给第三方软件的授权失效,第三方软件无法继续访问资源拥有者的受保护资源。
如果还想继续使用三方软件,必须重新点击授权按钮,比如我给xx授权后,正在愉快地编写我公众号的文章呢,刚准备使用 xx 的导入文章功能,突然xx再次让我进行授权。此刻,我可很崩溃!
于是,OAuth 2.0中引入刷新令牌,即刷新访问令牌access_token的值。有了刷新令牌,用户在一定期限内无需重新授权,就可继续使用三方软件。
刷新令牌也是给第三方软件使用的,同样需要遵循先颁发再使用的原则。
颁发刷新令牌和颁发访问令牌一起实现,都在过程二的步骤三生成访问令牌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的值。
这其中的流程主要包括如下两大步骤。
请求中的grant_type值为refresh_token。
String grantType = request.getParameter("grant_type");
if("refresh_token".equals(grantType)){
}
和颁发访问令牌前的验证流程一样,也要验证第三方软件是否存在。这里需同时验证刷新令牌是否存在,目的就是要保证传过来的刷新令牌的合法性。
String refresh_token = request.getParameter("refresh_token");
if(!refreshTokenMap.containsKey(refresh_token)){
//该refresh_token值不存在
}
另外,我们还需要验证刷新令牌是否属于该第三方软件。授权服务是将颁发的刷新令牌与第三方软件、当时的授权用户绑定在一起的,因此这里需要判断该刷新令牌的归属合法性。
String appStr = refreshTokenMap.get("refresh_token");
if(!appStr.startsWith(appId+"|"+"USERTEST")){
//该refresh_token值不是颁发给该第三方软件的
}
一个刷新令牌被使用后,授权服务需要将其废弃,并重新颁发一个刷新令牌。
生成访问令牌的处理流程,与颁发访问令牌环节的生成流程一致。授权服务会将新的访问令牌和新的刷新令牌,一起返回给第三方软件。
授权服务的核心:先颁发授权码code值,再颁发访问令牌access_token值。
颁发访问令牌同时,还会颁发刷新令牌refresh_token值,这种机制可在无需用户参与case下用于生成新的访问令牌。正如小明使用小兔软件的例子,当访问令牌过期,刷新令牌的存在可大大提高小明使用小兔软件的体验。
授权还要有授权范围,不能让第三方软件获得比注册时权限范围还大的授权,也不能获得超出了用户授权的权限范围,始终确保最小权限安全原则。
Q:若access_token未超时,则进行refresh_token有啥方式?
A:两种:
延期access_token并非最好方式,尽管有的开放平台是这么做。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有