Day974.授权码和访问令牌的颁发流程 -OAuth 2.0

news2025/2/12 13:02:22

授权码和访问令牌的颁发流程

Hi,我是阿昌,今天学习记录的是关于授权码和访问令牌的颁发流程的内容。

授权服务就是负责颁发访问令牌的服务。更进一步地讲,OAuth 2.0 的核心是授权服务,而授权服务的核心就是令牌

为什么这么说呢?当第三方软件比如小兔,要想获取小明在京东店铺的订单,就必须先从京东商家开放平台的授权服务那里获取访问令牌,进而通过访问令牌来 “代表” 小明去请求小明的订单数据。这不恰恰就是整个 OAuth 2.0 授权体系的核心吗?

那么,授权服务到底是怎么生成访问令牌的,这其中包含了哪些操作呢?

还有一个问题是,访问令牌过期了而用户又不在场的情况下,又如何重新生成访问令牌呢?

一、授权服务的工作过程

先回想下小明给小兔软件授权订单数据的整个流程。

小兔软件先要让小明去京东商家开放平台那里给它授权数据,那这里是不是你觉得很奇怪?

总不能说,“嘿,京东,你把数据给小兔用吧”,那京东肯定会回复说,“小明,小兔是谁啊,没在咱家备过案,我不能给他,万一是骗子呢?”对吧,你想想是不是这个逻辑。

所以,授权这个大动作的前提,肯定是小兔要去平台那里“备案”,也就是注册。注册完后,京东商家开放平台就会给小兔软件 app_id 和 app_secret 等信息,以方便后面授权时的各种身份校验。同时,注册的时候,第三方软件也会请求受保护资源的可访问范围。比如,小兔能否获取小明店铺 3 个月以前的订单,能否获取每条订单的所有字段信息等等。这个权限范围,就是 scope

关于注册后的数据存储,使用如下 Java 代码来模拟:

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

为了更能表达授权码和访问令牌的存在,图中用深色将其标注了出来:

在这里插入图片描述

先看看颁发授权码 code 的流程

过程一:颁发授权码 code

在这个过程中,授权服务需要完成两部分工作,分别是准备工作和生成授权码 code。这个“准备”都包括哪些工作?

可以想到,小明在给第三方软件小兔打单软件进行授权的时候,会看到授权页面上有一个授权按钮,但是授权服务在小明看到这个授权按钮之前,实际上已经做了一系列动作。

这些动作,就是所谓的准备工作,包括验证基本信息、验证权限范围(第一次)和生成授权请求页面这三步。


第一步,验证基本信息。

验证基本信息,包括对第三方软件小兔合法性和回调地址合法性的校验。

在 Web 浏览器环境下,颁发 code 的整个请求过程,都是浏览器通过前端通信来完成,这就意味着所有信息都有被冒充的风险。

因此,授权服务必须对第三方软件的存在性做判断。同样,回调地址也是可以被伪造的。

比如,不法分子将其伪装成钓鱼页面,或者是带有恶意攻击性的软件下载页面。

因此从安全上考虑,授权服务需要对回调地址做基本的校验。

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

在授权服务的程序中,这两步验证通过后,就会生成或者响应一个页面(属于授权服务器上的页面),以提示小明进行授权。


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

既然是授权,就会涉及范围。比如,我们使用微信登录第三方软件的时候,会看到微信提示我们,第三方软件可以获得你的昵称、头像、性别、地理位置等。

如果你不想让第三方软件获取你的某个信息,那么可以不选择这一项。

同样在小兔中也是一样,当小明为小兔进行授权的时候,也可以选择给小兔的权限范围,比如是否授予小兔获取 3 个月以前的订单的访问权限。这就意味着,我们需要对小兔传过来的 scope 参数,与小兔注册时申请的权限范围做比对。

如果请求过来的权限范围大于注册时的范围,就需要作出越权提示。记住,此刻是第一次权限校验。

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

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

这个授权请求页面就是授权服务上的页面,如下图所示:

在这里插入图片描述
页面上显示了小兔注册时申请的 today、history 两种权限,小明可以选择缩小这个权限范围,比如仅授予获取 today 信息的权限。

至此,颁发授权码 code 的准备工作就完成了。强调说这也是准备工作,因为当用户点击授权按钮“approve”后,才会生成授权码 code 值和访问令牌 acces_token 值,“一切才真正开始”。

这里需要说明下:在上面的准备过程中,忽略了小明登录的过程,但只有用户登录了才可以对第三方软件进行授权,授权服务才能够获得用户信息并最终生成 code 和 app_id(第三方软件的应用标识) + user(资源拥有者标识)之间的对应关系。可以把登录部分的代码,作为附加练习。

小明点击“approve”按钮之后,生成授权码 code 的流程就正式开始了,主要包括验证权限范围(第二次)、处理授权请求生成授权码 code 和重定向至第三方软件这三大步。


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

在步骤二中,生成授权页面之前授权服务进行的第一次校验,是对比小兔请求过来的权限范围 scope 和注册时的权限做的比对。

这里的第二次验证权限范围,是用小明进行授权之后的权限,再次与小兔软件注册的权限做校验。

那这里为什么又要校验一次呢?因为这相当于一次用户的输入权限。小明选择了一定的权限范围给到授权服务,对于权限的校验要重视对待,凡是输入性数据都会涉及到合法性检查。

另外,这也是要求养成一种在服务端对输入数据的请求,都尽可能做一次合法性校验的好习惯

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 时使用。

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;
}

在生成了授权码 code 之后,按照上面所述绑定了响应的映射关系

这时,授权码是临时的、一次性凭证吗?因此,还需要为 code 设置一个有效期。OAuth 2.0 规范建议授权码 code 值有效期为 10 分钟,并且一个授权码 code 只能被使用一次。

不过根据经验呢,在生产环境中 code 的有效期一般不会超过 5 分钟。关于授权码 code 相关的安全方面的内容。

同时,授权服务还需要将生成的授权码 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 的流程全部完成。

当小兔获取到授权码 code 值以后,就可以开始请求访问令牌 access_token 的值了,也就是即将开始的过程二。


过程二:颁发访问令牌 access_token

在过程一中介绍了授权码 code 的生成流程,但小兔最终是要获取到访问令牌 access_token,才可以去请求受保护资源。而授权码呢,只是一个换取访问令牌 access_token 的临时凭证

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

这个过程主要包括验证第三方软件小兔是否存在、验证 code 值是否合法和生成 access_token 值这三大步。

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

此时,接收到的 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 值。

关于按照什么规则来生成访问令牌 access_token 的值,OAuth 2.0 规范中并没有明确规定,但必须符合三个原则:

  • 唯一性
  • 不连续性
  • 不可猜性。

Demo 中,使用 UUID 来作为示例的。和授权码 code 值一样,需要将访问令牌 access_token 值存储起来,并将其与第三方软件的应用标识 app_id 和资源拥有者标识 user 进行关系映射。

也就是说,一个访问令牌 access_token 表示某一个用户给某一个第三方软件进行授权。同时,授权服务还需要将授权范围跟访问令牌 access_token 做绑定。

最后,还需要为该访问令牌设置一个过期时间 expires_in,比如 1 天。

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 规范没有约束访问令牌内容的生成规则,所以有更高的自由度

  • 既可以像 Demo 中那样生成一个 UUID 形式的数据存储起来,让授权服务和受保护资源共享该数据;

  • 也可以将一些必要的信息通过结构化的处理放入令牌本身

将包含了一些信息的令牌,称为结构化令牌,简称 JWT


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

在阅读别人的授权流程代码,或者是使用诸如通过微信登录的第三方软件的时候,就会明白背后的原理了。同时,在自己搭建一个授权服务流程时,也会更加得心应手。这一切的原因,都在于颁发授权码和颁发访问令牌,就是授权服务的核心。

到这里,你应该还会注意到一个问题,在生成访问令牌的时候,还给它附加了一个过期时间 expires_in,这意味着访问令牌会在一定的时间后失效。访问令牌失效,就意味着资源拥有者给第三方软件的授权失效了,第三方软件无法继续访问资源拥有者的受保护资源了。

这时,如果还想继续使用第三方软件,就只能重新点击授权按钮,比如小明给小兔软件授权以后,正在愉快地处理他店铺的订单数据,结果没过多久,突然间小兔软件再次让小明进行授权。此刻,可以替小明感受一下他的心情。显然,这样的用户体验非常糟糕。为此,OAuth 2.0 中引入了刷新令牌的概念,也就是刷新访问令牌 access_token 的值

这就意味着,有了刷新令牌,用户在一定期限内无需重新点击授权按钮,就可以继续使用第三方软件。


二、刷新令牌

刷新令牌也是给第三方软件使用的,同样需要遵循先颁发再使用的原则。因此,我们还是从颁发和使用两个环节来学习刷新令牌。不过,这个颁发和使用流程和访问令牌有些是相同的,所以我只会和你重点讲述其中的区别。

1、颁发刷新令牌

颁发刷新令牌其实,颁发刷新令牌和颁发访问令牌是一起实现的,都是在过程二的步骤三生成访问令牌 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;
  
} 

看到这里你可能要问了,为什么要一起生成访问令牌和刷新令牌呢?其实,这就回到了刷新令牌的作用上了。刷新令牌存在的初衷是,在访问令牌失效的情况下,为了不让用户频繁手动授权,用来通过系统重新请求生成一个新的访问令牌。那么,如果访问令牌失效了,而“身边”又没有一个刷新令牌可用,岂不是又要麻烦用户进行手动授权了。所以,它必须得和访问令牌一起生成。到这里,我们就解决了刷新令牌的颁发问题。


2、使用刷新令牌

说到刷新令牌的使用,需要先明白一点。在 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值不是颁发给该第三方软件的
}

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

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

生成访问令牌的处理流程,与颁发访问令牌环节的生成流程是一致的。

授权服务会将新的访问令牌和新的刷新令牌,一起返回给第三方软件。

这里就不再赘述了。


三、总结

授权服务可以说是整个 OAuth 2.0 体系中的 “灵魂” 组件,任何一种许可类型都离不开它的支持,它也是最复杂的组件。

这是因为它将复杂性尽可能地“揽在了自己身上”,才使得诸如小兔这样的第三方软件接入 OAuth 2.0 的时候更加便捷。

  1. 授权服务的核心就是,先颁发授权码 code 值,再颁发访问令牌 access_token 值
  2. 在颁发访问令牌的同时还会颁发刷新令牌 refresh_token 值,这种机制可以在无须用户参与的情况下用于生成新的访问令牌。正如小明使用小兔软件的例子,当访问令牌过期的时候,刷新令牌的存在可以大大提高小明使用小兔软件的体验。
  3. 授权还要有授权范围,不能让第三方软件获得比注册时权限范围还大的授权,也不能获得超出了用户授权的权限范围,始终确保最小权限安全原则。比如,小明只为小兔软件授予了获取当天订单的权限,那么小兔软件就不能访问小明店铺里面的历史订单数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/639850.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

被冻结的层在训练过程中参与正向反向传递,只是这一层的梯度不再更新。||底层逻辑

被冻结的层可以前向传播,也可以反向传播,只是自己这一层的参数不更新,其他未冻结层的参数正常更新。 在微调期间&#xff0c;只有被激活的层的梯度会被计算和更新&#xff0c;而被冻结的层的梯度则会保持不变。 其实从数学上去理解也不难&#xff0c;但自己手推还是需要花点时…

《自然》:DeepMind推出AlphaDev或将加速全球计算

数字世界对计算和能源的需求正在不断增加。在过去的五十年中&#xff0c;人类主要依靠硬件层面的改进来满足这一点。然而&#xff0c;随着微芯片接近其物理极限&#xff0c;改进计算机运行代码&#xff0c;以使计算算力更强大和可持续&#xff0c;变得至关重要。对于每天运行数…

线程的生命周期

我是一个线程 第一回 初生牛犊 我是一个线程&#xff0c;我一出生就被编了个号: 0x3704&#xff0c;然后被领到一个昏暗的屋子里&#xff0c;在这里我发现了很多和我一模一样的同伴。 我身边…

一文教你如何在数据库中安全地存储密码

前言 作者&#xff1a;神的孩子在歌唱 大家好&#xff0c;我叫智 让我们先谈谈什么不该做。 不要以明文形式存储密码。任何具有数据库内部访问权限的人都可以看到它们。如果数据库受损&#xff0c;攻击者可以轻松获取所有密码。那么&#xff0c;我们应该如何在数据库中安全地存…

10个ai算法常用库java版

今年ChatGPT 火了半年多,热度丝毫没有降下来。深度学习和 NLP 也重新回到了大家的视线中。有一些小伙伴问我,作为一名 Java 开发人员,如何入门人工智能,是时候拿出压箱底的私藏的学习AI的 Java 库来介绍给大家。 这些库和框架为机器学习、深度学习、自然语言处理等提供了广…

OceanBase 安全审计之身份鉴别

本文主要以 MySQL 和 OceanBase 对比的方式&#xff0c;来介绍 OceanBase&#xff08;MySQL 模式&#xff09;安全体系中关于身份鉴别的相关内容&#xff0c;包括身份鉴别机制、用户名组成、密码复杂度、密码过期策略等。 作者&#xff1a;金长龙 爱可生测试工程师&#xff0c;…

快速掌握SQL语言——数据查询语言DQL

0️⃣前言 数据查询语言DQL是一种用于查询数据库中数据的语言&#xff0c;它是SQL的一部分&#xff0c;也是SQL中最常用的语言之一。 文章目录 0️⃣前言1️⃣介绍2️⃣使用3️⃣重要性4️⃣总结 1️⃣介绍 DQL&#xff08;Data Query Language&#xff09; 主要用于从数据库中…

为PyCharm IDE 配置三剑客:QtDesigner[可视化设计器]、PyUIC[可视化设计器ui文件转py代码]、PyRcc[资源文件转py代码]

过去一直在使用Eric6 PyQt5&#xff0c;然而最近它[已然是古董级的了]似乎有些不太正常&#xff0c; 像我这样有强迫症的人怎么可以容忍呢? 于是有了换IDE的想法&#xff0c;听说PyCharm是个高富帅&#xff0c;大家都很喜欢用它呢&#xff01;于是乎才有了这篇文章。 前提条…

【C++ 笔记五】STL 标准模板库 —— 容器基础进阶

【C 笔记五】STL 标准模板库 —— 容器基础进阶 文接上文 【C 笔记四】STL 标准模板库 —— 容器基础 文章目录 【C 笔记五】STL 标准模板库 —— 容器基础进阶I - 简单回顾1.1 - 序列式容器&#xff08;顺序容器&#xff09;1.2 - 关联式容器 (关联容器)1.3 - 访问方法/对外接…

面试-java常见问题

JVM 配置 程序计数器&#xff1a;当前线程所执行的字节码的行号指示器java虚拟机栈:临时变量元空间&#xff1a;类常量池&#xff0c;运行时常量池方法区&#xff1a;类信息&#xff0c;静态变量堆&#xff1a;对象实例&#xff0c;Sting常量池等 类加载过程 加载->链接&am…

使用javacv中的ffmpeg实现录屏

今天突发奇想&#xff0c;想自己写一个录屏的软件&#xff0c;上次写了一个专门录音的Demo&#xff0c;但是要把声音和视频放到一起合成一个mp4文件&#xff0c;着实有一点艰难&#xff0c;所以就打算使用ffmpeg来写一个&#xff0c;而这篇博客中会顺便谈一谈我碰到的各种坑。 …

JVM面试题50道

1.JDK、JRE、JVM关系&#xff1f; Jdk (Java Development Kit) : java语言的软件开发包。包括Java运行时环境Jre。 Jre &#xff08;Java Runtime Environment) :Java运行时环境&#xff0c;包括Jvm。 Jvm (Java Virtual Machine) :一种用于计算机设备的规范。 Java语言在不同…

JavaWeb小记——Tomcat

目录 Tomcat简介 Tomcat下载安装 Tomcat启动 Tomcat关闭 常见问题 项目发布 发布方式一 发布方式二 发布方式三 IDEA打war包 Tomcat和IDEA整合 IDEA发布动态项目 Tomcat简介 Tomcat是Apache基金组织下的一款免费的开源的且支持Servelet和JSP规范的服务器 Tomcat下…

Spark大数据处理学习笔记1.3 使用Scala集成开发环境

文章目录 一、学习目标二、搭建Scala的IntelliJ IDEA开发环境&#xff08;一&#xff09;启动IDEA&#xff08;二&#xff09;安装Scala插件&#xff08;三&#xff09;配置IDEA使用的默认JDK&#xff08;四&#xff09;创建Scala项目1、创建Scala项目 - ScalaDemo2、创建Scala…

跨平台潜能解锁:将Ionic框架与小程序容器相结合

Ionic是一个用于构建跨平台移动应用程序的开源框架。它结合了HTML、CSS和JavaScript等技术&#xff0c;帮助开发者创建具有原生应用体验的移动应用程序。Ionic提供了一套用户界面组件和工具&#xff0c;可用于构建高度交互和美观的移动应用界面。 Ionic基于Angular框架&#x…

为什么 Twitter 和 Facebook 的网站页面变得越来越像?

Twitter和Facebook这两个社交媒体平台在不同的领域取得了巨大的成功。Twitter以其独特的推文形式而闻名&#xff0c;而Facebook则以其广泛的社交网络和内容分享功能而著称。 然而&#xff0c;近年来&#xff0c;这两个平台在设计和布局上的相似之处越来越明显。为什么会出现这…

奖金高达534万!2023第四届全国人工智能大赛

2023第四届全国人工智能大赛 报名链接&#xff1a; https://www.datafountain.cn/special/NAIC2023?target13250069&specialNAIC2023 叮咚&#xff0c;已向您发送组队邀请&#xff01;今年最值得参与的第四届全国人工智能大赛开放报名了&#xff0c;3道赛题奖金534万&…

又一重点项目,石岩新能源产业园建面61.6万平,配27班学校

近日&#xff0c;宝安区城市更新和土地整备局发布&#xff0c;关于石岩街道总部经济园区城市更新单元(一期南及二期)“工业上楼”单元规划&#xff08;草案&#xff09;已通过专班会议审议的公告。 公告显示&#xff0c;项目申报主体为深圳市开宝安区投资管理集团有限公司&…

信息系统项目管理师(软考高项)备考总结

简介 信息系统项目管理师&#xff0c;计算机技术与软件&#xff08;高级&#xff09;专业技术资格。 相关考试简称软考&#xff0c;该资质业内简称高项。 证书价值 自行百度吧&#xff0c;决定考的肯定知道他能带来什么价值了。 笔者是因为从事软件开发&#xff0c;服务政…

js的一些工具函数以及方法

部分方法复制于&#xff1a;20 个 JS 工具函数助力高效开发 reduce 举例&#xff1a;数组求和 let a[1,3,6,5,7]; let init0;//累加的初始值&#xff0c;默认为0&#xff0c;可不写 let ba.reduce((pre,cur,index,arr)>{console.log(当前要加序号&#xff1a;,index);cons…