授权服务是如何颁发授权码和访问令牌的?_访问令牌 授权书-程序员宅基地

技术标签: OAuth2.0  

授权服务如何生成访问令牌?
访问令牌过期了而用户又不在场的情况下,又如何重新生成访问令牌?

授权服务的工作过程

在 xx让我去公众号开放平台给它授权数据时,你是否好奇?开放平台怎么知道 xx 是谁?他合法备案了吗?万一是个病毒软件咋办?

所以,授权的前提是xx要去开放平台备案,即注册。之后,开放平台就会给xx软件app_idapp_secret等,方便后面授权时的各种校验。

注册时,三方软件也会请求受保护资源的可访问范围。比如,xx能否获取我的公众号半年前的文章,能否获取每个文章的所有信息(比如标题、封面、标签)等。即scope。

注册后,xx过来让平台把我的文章数据都给xx,平台核实后确认xx合法。

咱们上节课讲过,在授权码许可类型中,授权服务的工作,可以划分为两大部分,一个是颁发授权码code,一个是颁发访问令牌access_token。

颁发授权码code流程

过程 1:颁发授权码code

授权服务负责准备工作和生成授权码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的流程就开始了,主要包括验证权限范围(第二次)、处理授权请求生成授权码code和重定向至第三方软件这三大步。

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

步骤二生成授权页面前授权服务进行的第一次校验,是对比xx请求的权限范围和注册时的权限。

为什么又要校验一次

因为这相当于一次用户的输入权限。我选择了权限范围给授权服务,对权限的校验,凡输入性数据都会涉及合法性检查。

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

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

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

我同意授权后,授权服务会校验响应类型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的值,即过程二。

过程二:颁发访问令牌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的阶段已存储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值来请求授权服务。

第三步-生成访问令牌access_token值

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没有约束访问令牌内容的生成规则,可以生成一个UUID存储,让授权服务和受保护资源共享该数据,也可将一些必要信息通过结构化处理放入令牌本身。我们将包含一些信息的令牌,称为结构化令牌,简称JWT。

至此,授权码许可类型下授权服务的两大主要过程,也就是颁发授权码和颁发访问令牌的流程,我就与你讲完了。

颁发授权码和颁发访问令牌,就是授权服务的核心。

刷新令牌

为何需要刷新令牌?

在生成访问令牌的时附加过期时间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值,这种机制可以在无须用户参与的情况下用于生成新的访问令牌。正如我们讲到的小明使用小兔软件的例子,当访问令牌过期的时候,刷新令牌的存在可以大大提高小明使用小兔软件的体验。

授权还要有授权范围,不能让第三方软件获得比注册时权限范围还大的授权,也不能获得超出了用户授权的权限范围,始终确保最小权限安全原则

若access_token未超时,那么进行refresh_token有两种方式

  1. 不会改变access_token,但超时时间会刷新,相当于续期access_token
  2. 更新access_token的值,我们建议【统一更新access_token的值】。

延期access_token并不是一个最好的方式,尽管有的开放平台是这么做的。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_33589510/article/details/109147671

智能推荐

书籍推荐-数据中台:数字中国战略关键技术设施_周傲英-程序员宅基地

文章浏览阅读2.3w次,点赞95次,收藏75次。数据中台是企业开展数据要素相关实践和数字化转型的关键基础设施,拥有一个高效的数据中台是任何企业都无法忽视的事情,通过数据中台,企业可以构建自己的数据基座,让数据流转起来,形成闭环,为业务创新源源不断地赋能,在把自身隐藏的或被低估的潜能激发出来的同时,加速企业数字化转型,重构数字战斗力。_周傲英

微信小程序内部那些事_微信小程序document-程序员宅基地

文章浏览阅读3k次。微信小程序分为“视图层”和“逻辑层”。视图层是在 webview 中渲染,逻辑层则用 JavaScriptCore 来渲染。其中视图层可以有多个,但逻辑层则只有一个。小程序中视图层负责页面渲染,逻辑层负责逻辑处理、状态管理、请求和接口调用。逻辑层和视图层的通信是通过微信的 JSBridge 实现的。逻辑层数据变化通过 JSB 通知到视图层,触发视图层更新;当视图层触发事件则继续通过 JSB 将事件通知到逻辑层做处理。WeixinJSB 在开发者工具、IOS 和 Android 的实现机制不同。在调用 Na_微信小程序document

如何删除基址的重定位表----记一次对RAR软件的基址重定位删除实践-程序员宅基地

文章浏览阅读287次。工具: 1.PETool 2.HxD 步骤: 1.PE查看rar程序的结构: 重定位的具体信息如下: 2.删除.reloc的节区头 从地址278开始,删除到29c+3 这部分清零。 3.删除.reloc节区 ..._减少重定位表

应聘软件测试岗位需要掌握的基础知识与技能(面试常考内容)_测试工程师需要哪些知识储备-程序员宅基地

文章浏览阅读1.8w次,点赞212次,收藏676次。详细讲解应聘软件测试岗位所需要掌握的一些列基础知识与技能,包括软件测试的基本概念和基本流程、基础的网络知识、常用的数据库技能以及常用的Linux命令等。_测试工程师需要哪些知识储备

如何下载道客巴巴付费文档_万能文库下载器,一键下载付费文档-程序员宅基地

文章浏览阅读1.1w次。Photoshop视频教程1000G免费资源整合/仅限100个名额领取■来源:晚安夕颜 ID:iiixiyan终身会员办理今天小编为你精选了万能文库下载区支持游览器版本:360游览器,IE,,,支持系统:win/Mac获取方式:添加小编微信「XiaoBian08021314」注意大小写分享转发群免费获取自我困扰区对于学生党来说,经常会用到如百度文库、豆丁网等网站下载文档资料。特别是即..._如何下载道客巴巴收费ppt

c语言FD_SET头文件,select()函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET-程序员宅基地

文章浏览阅读2.1k次。―――――――――――――――――――――――――――――――――――――――2、select函数的接口比较简单:int select(int nfds, fd_set *readset, fd_set *writeset,fd_set* exceptset, struct tim *timeout);功能:测试指定的fd可读?可写?有异常条件待处理?参数:nfds需要检查的文件描述字个数(即检查..._c fd_set

随便推点

PotPlayer 无损截取视频片段_potplayer录制怎么无损画质-程序员宅基地

文章浏览阅读4.3w次,点赞9次,收藏5次。PotPlayer 无损截取视频片段_potplayer录制怎么无损画质

js大数字转换,将大额数字转换为万、千万、亿等_js如何分离出1000.1万9.5亿数字和单位-程序员宅基地

文章浏览阅读1.7k次。记录一下大额数据转换,bigNumberTransform (value) { const newValue = ['', '', ''] let fr = 1000 let num = 3 let text1 = '' let fm = 1 while (value / fr >= 1) { fr *= 10 num += 1 // console.log('数字', value / fr, 'num:', num) _js如何分离出1000.1万9.5亿数字和单位

利用Flexpaper实现对多篇pdf文章在线切换预览评分_pdf预览评分-程序员宅基地

文章浏览阅读439次。一、利用flexpaper显示pdf文件1.利用office10 将doc文件转为pdf2.利用swftools 将pdf 转为swf 这里需要注意的是参数 pdf2swf.exe -t 源文件 -s version=9 -o 目的文件 否则会出现加载flexpaper加载不上swf文件这个是网上的PSD2swfHelper.cs 工具类 using System.Web;_pdf预览评分

免费亚马逊云服务器AWS EC2使用流量查看_亚马逊aws免费观看-程序员宅基地

文章浏览阅读1.4w次。4. 打开“每个实际的指标”5. 默认“全部指标”-勾选“指标名称”为NetworkIn,NetworkOut的选项,我因为这个月总共开了两个实例,所以有两组网络输入和网络输出。6. 选择“绘成图表的指标”-界面上部选择“数字”-页面中间“统计数据”选择“总计”,箭头指向的两个时间段根据自己的需要选择查看。点击以在 Twitter 上共享(在新窗口中打开)点击以在 Facebook 上共享(在新窗..._亚马逊aws免费观看

memcache视频java,MemCache视频教程-程序员宅基地

文章浏览阅读1.4k次。@import url(http://www.blogjava.net/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);ed2k://|file|%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2PH...

php 7.4连接MySQL_php7.4 mysql-程序员宅基地

文章浏览阅读1.3w次,点赞15次,收藏80次。准备工作:安装php的IDE,如PhpStorm下载php并解压,官网:https://www.php.net/downloadsPhpStorm配置php开发环境1.1 打开PhpStorm,点击File->Settings:1.2 点击"Languages & Frameworks”,找到PHP1.3选择php版本,选择CLI Interpreter(客户端..._php7.4 mysql

推荐文章

热门文章

相关标签