轻量级的资源授权:基于 OAuth 规范

news2024/12/23 9:40:02

了解 OAuth

感觉 OAuth 太负盛名了,以至于后来在 OIDC 反而难以企及前辈 OAuth。倒是大家谈论比较多的是 JWT(例如https://www.cnblogs.com/lyzg/p/6132801.html),——实际谈 JWT 就是在实现 OIDC,反而 OIDC 大家不怎么爱谈!但我们要知道的是,真正诠释这些的,做点单点登录的,——是 OIDC 规范,JWT 只是 OIDC 规范下的一种 Token 协议,再说句难听的,如果 JWT 不满足或者有问题,换别的 Token 实现规则也行。

这里再一次不厌其烦地强调:

  • 认证(Authentication),识别你是谁。即在网站上用来识别某个用户是否是注册过的合法用户。关于 OIDC 说的是这个。
  • 授权(Authorization),识别你能做什么。即在网站上用来识别某个用户是否有某方面的权限。本文正是讲这个的。

不过话说回来,OIDC 与 OAuth 看上去大体是相近,只是把应用场景稍作转换,另外就是返回 Token 的不同,OAuth 不限定 Token 具体实现如何,而 OIDC 推荐带用户信息的 JWT。所以,这么说,也不能怪人们总爱谈 JWT 而忽视 OIDC。

授权模式对比

OAuth2 提供了不同的 Grant Type 以适应不同的客户端类型以及应用场景,具 体有如下几种:

  • Authorization Code 授权码模式:主要是服务端类型的应用,这应用是比较安全的,是多数场景使用的模式。
  • Implicit 简化模式:浏览器应用或者移动 Apps。
  • Password 密码模式:通过用户名密码登录,适用于信任应用(自研应用)。
  • Client Credentials 客户端模式:只认证应用,无需用户授权服务端应用。

本文只对前后端授权常见的授权码模式和客户端凭证授权模式做详细介绍,其他授权模式可自行了解,这里只做简单介绍。

在这里插入图片描述

授权码流程 Authorization Code

OAuth 2.0 提供了四种具体的授权流程: 授权码流程(Authorization Code),隐式许可流程(Implicit),用户密码流程(Resource owner password credentials)和客户机凭据流程(Client Credentials)。其中授权码流程(authorization code)最常见、安全性也最高。授权码通过前端传送,令牌储存在后端,而且所有与资源服务器的通信都在后端完成,可以避免令牌泄漏。

标准授权码流程流程如下:

  1. 在授权服务器注册客户端,获得 client_id、client_secret
  2. 客户端访问授权服务器的授权页
  3. 用户在授权页,可以选择“授权”或者不授权,选择后会重定向到客户端的回调地址
  4. 客户端在回调地址拿到 code,通过 code 获取 token,通过 token 获取用户信息

在这里插入图片描述

授权确认页面

具体用户是如何授权的呢?一般来说,第三方应用向授权服务器发送用户授权请求时,授权服务器会自动检查当前用户有没有登录 (例如通过 cookie 机制),如果用户已登录则弹出一个授权确认页面,让用户点击按钮企确认是否授权。若授权服务器检测到当前用户没有登录,则先会弹出登录框让用户进行登录,用户输入用户名密码登录之后再让用户确认是否授权。

客户机应用应该先引导用户到授权确认的页面,询问用户选择是否同意对客户机应用授权,并指定允许其获取哪些资源权限。下面给出两个例子。

在这里插入图片描述

在这里插入图片描述

此阶段要提供下面的参数:

参数说明
redirect_uri用户登录成功后,授权服务器回传授权码等信息给户机应用的接口,相当于回调地址
response_type固定值 code,表示授权码流程
client_id客户机应用在授权服务器注册的 client_id
state随机值,每次请求都要变化。当授权服务器重定向到 redirect_uri 时,会原样返回给客户机应用,用于防止 CSRF、 XSRF。由于授权服务器会原样返回此参数,可将 state 值与用户在客户机应用登录前最后浏览的 URI 绑定,便于登录完成后将用户重定向回最后浏览的页面

这些参数会原封不动传到下面生成授权码的接口。发送请求的例子如下:

GET https://oauth_server.com/oauth/authorization
?response_type=code
&redirect_uri=http://client.com/callback
&scope=profile
&state=c7HBU6Sb1nAcWELJx6l2aU
&client_id=9c21477eb0a5e2191342

生成授权码

当上一步没有出现任何问题,然后用户点击了同意授权,授权确认页面会跳转到生成授权码的接口/oauth/authorization,该接口定义如下。

/**
 * 获取授权码(Authorization Code)
 *
 * @param responseType 授权模式,固定为 code
 * @param clientId     客户端标识符,表示 OAuth 客户端的唯一标识
 * @param redirectUri  重定向 URI,表示授权服务器将授权码发送到此 URI
 * @param scope        作用域,表示客户端请求的权限范围
 * @param state        用于防止 CSRF 攻击
 * @param req          请求对象
 * @param resp         响应对象
 */
@GetMapping("/authorization")
void authorization(@RequestParam("response_type") String responseType, @RequestParam("client_id") String clientId, @RequestParam("redirect_uri") String redirectUri, @RequestParam(required = false) String scope, @RequestParam String state, HttpServletRequest req, HttpServletResponse resp);

同样该接口也要求用户是已经登录了的,然后让生成的 code 与用户绑定(在缓存中保存这关系),最后将 code 以参数的形式附在 redirectUrl 地址上重定向到客户机应用。

此接口源码实现如下。

在这里插入图片描述

授权服务器会根据redirectUrl将用户重定向回到客户机应用的回调接口,并且还会在redirectURL后面附上两个应答参数:

  • code:授权码,代表用户的授权码
  • state:与第一步请求授权中的state值一模一样,原样返回

例如:

https://client.com/callback/?code=AB231DEF2134123kj89&state=987d43e51a262f

注意,授权服务器在重定向到redirectUrl时,应该根据 clientId 校验此 url 是否与注册中的 redirectUrl 一致。

即返回响应:

HTTP/1.1 302 Found
Location: http://client.com/callback
?code=SplxlOBeZQQYbYS6WxSbIA
&state=c7HBU6Sb1nAcWELJx6l2

授权码换取 AccessToken

下面客户机就可以凭获得的授权码 code 换取可以访问 API 的 AccessToken,客户机在服务端访问授权中心的这个/oauth/token接口。接口定义如下:

/**
 * 获取 Token
 *
 * @param authorization client 信息
 * @param grantType     授权码流程
 * @param code          授权码
 * @param state         不透明字符串
 * @return 令牌 Token
 */
@PostMapping("/token")
AccessToken token(@RequestHeader String authorization, @RequestParam("grant_type") String grantType, @RequestParam String code, @RequestParam String state);

上面已经介绍过了,这一步换取需要传递如下参数给token接口:

grantType:authorization_code。告诉授权服务器使用授权码流程
clientId:客户机应用 id
clientSecre:客户机秘钥,相当于应用的密码
code:上一步获得的用户授权码
state:随机码

发出请求时, 客户机应用需提供其在授权服务器注册的 client_id、client_secret,相当于客户机的用户名、密码,授权服务器根据这两个参数认证客户机的合法性。这两个参数比较敏感,不适宜明文直接传,应该通过 HTTP 的Authorization Header 来传递,即其加密成Base64UrlEncode(clientId:clientSecret)字符串,如下所示:

Authorization: Basic
MDAwMDAwMDA0NzU1REU0MzpVRWhrTDRzTmVOOFlhbG50UHhnUjhaTWtpVU1nWWlJNg==

实际请求如下:

POST https://oauth_server.com/oauth/token
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA

实现源码如下。

在这里插入图片描述

当授权服务器认证通过之后 ,/oauth/token接口会返回:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
	 "access_token":"2YotnFZFEjr1zCsicMWpAA",
	 "token_type":"bearer",
	 "expires_in":3600,
	 "scope":"profile",
	 "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}

出参说明:

  • access_token:必选,授权服务器签发给客户机应用的短期有效的访问令牌。
  • token_type:必选,令牌类型,详见下文access token的具体用法(Bearer token)。
  • expires_in:推荐,令牌有效期,单位为秒,3600秒即1小时。
  • scope:授权服务器批准的资源权限,如果与客户机应用申请的权限不同(即批准权限小于申请权限),则必须返回此 参数,如果相同,可以不用返回。
  • refresh_token:可选,授权服务器签发给客户机应用的较长有效期的更新令牌。由于 access_token有效期很短,到期后客户机应用可以使用refresh_token向授权服务器申请新的 access_token,从而避免重复找用户要授权。

安全考量

Insufficient Redirect URI validation: The risk of allowing to dynamically add arbitrary query parameters and fragments to the redirect_uri。这是一种 OAuth 2.0 和 OpenID Connect 1.0 实现缺陷模式,允许动态添加查询参数和片段到 redirect_uri。如果 redirect_uri 没有得到适当的验证,攻击者可以构造一个包含指向攻击者控制的服务器的 URL 的链接。这可以用来欺骗 AS 将授权代码发送给攻击者。如果用户在用户代理中打开此链接,AS 将重定向用户代理到恶意 URL。攻击者可以捕获伪造 URL 中传递的代码值,然后将其提交给 AS 令牌端点。如果您想测试 AS 是否容易受到不足的重定向 URI 验证,请使用 HTTP 拦截代理(例如 ZAP)捕获流量。启动 OAuth 流并在授权请求处暂停它。更改 redirect_uri 的值并观察响应。调查响应并确定是否接受了任意 redirect_uri 参数。如果 AS 将用户代理重定向到您指定的 redirect_uri,则 AS 未正确验证 redirect_uri。

更新 Token:通过 RefreshToken

用 RefreshToken 换发新的 AccessToken

客户机应保存expires_in值,在调用 api 之前,客户端应该先拿expires_in与当前时间做比较,若当前时间大于过期时间,则说明 AccessToken 已过期,需要重新换取新的 AccessToken 。如果客户端应用发现 AccessToken 到期,或者权限不足,可以使用 RefreshToken 向授权服务器的令牌接口请求新的 AccessToken 。

发出请求时,客户端应用同样需提供其在授权服务器注册的 client_id、client_secret,从而使授权服务器能对客户机应用的身份进行认证。请求例子:

POST https://abc.com/oauth/refresh_token_basic
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

接口定义如下。

/**
 * 通过 Refresh Token 刷新 Access Token
 * 这是通过头传输 client_id/client_secret
 *
 * @param grantType     必选,固定为 refresh_token
 * @param authorization 包含  client_id/client_secret 的头,用 Base64 编码
 * @param refreshToken  必选,Refresh Token
 * @return Token
 */
@PostMapping("/refresh_token")
    AccessToken refreshToken(@RequestParam("grant_type") String grantType, @RequestHeader("Authorization") String authorization, @RequestParam("refresh_token") String refreshToken);

实现如下。

在这里插入图片描述

相关问题

  • 刷新了新的 Token 之后,旧的 Token 还能用吗?
    当使用 RefreshToken 获取新的访问令牌(AccessToken)时,通常情况下旧的 AccessToken 将会被作废,不再有效。
  • RefreshToken 有超时的概念吗?
    RefreshToken 的有效期可以是长期的,通常不会自动过期。根据 OAuth 2.0 规范,RefreshToken 的过期时间是由授权服务器(Authorization Server)决定的。授权服务器可以为 RefreshToken 设置一个固定的过期时间,也可以让 RefreshToken 永不过期。这取决于授权服务器的配置和策略。

客户端凭证 Client Credentials 授权模式

ClientCredentials 是 OAuth 四大授权流程中最简单的一个流程。只需要用 client_id 和 client_secret 即可换取 AccessToken。这个流程只用来访问客户端拥有的资源而非用户拥有的资源,因为这个流程无须用户授权,跟用户无关,只需要客户端的认证凭证。

在这里插入图片描述
客户端凭证模式没有 RefreshToken 机制。如果 Token 快过期或者已过期,重新申请即可。 本方案中 Client 认证也可以刷新 Token。

资源服务器 SDK

AccessToken 如何传参

在 OAuth 中,不管是哪一类的客户端对保护资源的访问方式都是一样的:即每次请求携带一个 AccessToken 即可。那么客户机应用如何把 AccessToken 传递给资源服务器?OAuth 规范中定义了三种传递 AccessToken 的方式。

  1. 放在请求头中传递:在请求头Authorization字段中使用Bearer这一关键字传递。所谓 Bearer 是 OAuth 补充规范 RFC 6750 中的指定这样子的。Bearer token 不是一种 token 值的格式,而是一种规范的用法,OAuth 2.0 没有规定 token 值的内容、格式。
GET https://api.amazon.com/user/profile
Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA
  1. 放在 URI 的查询参数中传递:RFC 6750 不建议采用这种方式,但笔者觉得也应该有必要提供支持(在访问静态资源的时候)。
GET https://api.amazon.com/user/profile?access_token=2YotnFZFEjr1zCsicMWpAA
Cache-Control: no-store
  1. 放在表单请求体中传递:感觉没这必要。
POST https://api.amazon.com/user/profile
Content-Type: application/x-www-form-urlencoded

access_token=2YotnFZFEjr1zCsicMWpAA

一般默认 Bearer token 这种,也是推荐方式,安全性最高,原因如下:

  • 这个 header 通常不会被打印到 log 中
  • 这个 header 不会被缓存
  • 这个 header 不会被存储到浏览器中

如何校验 AccessToken?

资源服务器对传入的 Access token,不能一概都认为合法的,总得要校验一下。资源服务器本身不知道怎么校验,只能让授权服务器说了算,以授权服务器的校验为准。作为统一的认证中心,授权服务器无疑拥有最根本的用户状态记录,一切皆以授权服务器的为准——原则上是这样设计。实操上资源服务器与授权服务器之间的协调可以按以下方法去做。

  • 对于小型 Web 应用:资源服务器通常与授权服务器同为一体,自然能够通过读取数据库来校验。
  • 对于大型 Web 应用:授权服务器和资源服务器通常是独立部署的,有三种方式校验:
    1. 共享数据库,使得资源服务器能够通过读取数据库来校验。但出于安全目的一般不可行。
    2. 由授权服务器提供一个令牌校验接口,资源服务器请求该接口来校验。OAuth 2.0 在补充规范 RFC 7662 中定义了令牌校验接口(Introspection Endpoint)的相关标准。但每次访问资源的认证工作都要通讯授权服务器,性能成本会不会太高呢?对于授权服务器的性能是严重的考验。
    3. 对此,我们可以把 Token 放在 Redis 这类缓存中,让后共享 Redis 给资源服务器。Redis 通过长链接连接,总比 HTTP+数据库 消耗来得低。
    4. 授权服务器不校验,资源服务器本地对 AccessToken 进行解密、验签。资源服务器和授权服务器双方约定好自包含令牌的结构、签名密钥、加密方法,资源服务器按照约定规则自行校验,例如 JWT。但这类方式注销比较麻烦。授权服务器无法影响资源服务器的 AccessToken 校验。

如果 AccessToken 允许撤销的话,校验服务器就要需要 存储 Token 的状态,而不能采用解密、签名等方式。所以从这个角度来说客户机因为 AccessToken 有效期不会太长(一般3600秒),及时被撤销也不会长久存储它。

本方案采用第三、第四点校验 Token,即 HTTP 方式和 Redis 方式。先说说 HTTP 方式,请见接口/oauth/token/check如下:

/**
 * 验证访问令牌
 *
 * @param token AccessToken
 * @return 是否合法的 AccessToken
 */
@PostMapping("/token/check")
Boolean checkToken(@RequestParam String token);

无状态设计

传统认证基于 Cookie+Session 的方式,是有状态的;单机时代问题不大,但到了集群的时候,如何同步 Session 是件麻烦的事情。另外一个方法是绑定 Session 到指定某一台机器,但这样不仅带来复杂性,而且还不能彻底解决问题。因此,渐渐有了以下分野:

  • 服务端是无状态的:服务端组件不保存会话状态。
  • 服务端是有状态的:服务端组件保存了会话状态。

我们主张服务端无状态的设计。当服务端组件不保存任何会话状态时,伸缩将比较容易,直接增加/减少物理服务器的台数即可。《Web应用中的状态(会话状态、应用状态、有状态协议、无状态协议、REST无状态约束)》这文章分析得很透彻了。

注销 AccessToken

当用户在客户机应用:退出登录、修改密码、注销账号,或卸载了客户机应用时,客户机应用除了主动删除存储在本地的 AccessToken 及 RefreshToken,还需要通知授权服务器自己不再需要该用户的令牌,授权服务器将清除与该令牌相关的授权信息。这样可以防止被遗弃令牌的滥用,并改善用户体验,失效的授权将不再出现在授权服务器展示给用户的已授权客户机应用列表中。OAuth 在补充规范 RFC 7009 中定义了一个由授权服务器提供的撤销接口(Revocation Endpoint)来供客户机应用申请撤销 AccessToken 及 RefreshToken。

授权服务器也可以提供撤销授权接口,见接口/oauth/token/revoke如下:

/**
 * 撤销访问令牌
 *
 * @param token AccessToken
 * @return 是否成功
 */
@PostMapping("/token/revoke")
Boolean revokeToken(@RequestParam String token);

授权服务器在数据库、缓存中直接删除 AccessToken 及 RefreshToken 即可。

另外在客户机应用层面,也有集体批量注销这个客户机所属的 AccessToken 的需求,例如客户机应用下架了。

隐式许可流程(Implicit)模式

授权码模式是服务端类型的应用,用户无法看到源代码可以持有 clientSecret 秘钥,而浏览器/JavaScript/Native 应用由于用户可以直接看到源代码,所以授权服务器不能分配这种客户机 clientSecret。

在这里插入图片描述
访问获取授权服务地址:

/**
 * 隐式许可流程(Implicit)模式用户授权
 *
 * @param responseType 授权模式,固定为 token
 * @param clientId     客户端标识符,表示 OAuth 客户端的唯一标识
 * @param redirectUri  重定向 URI,表示授权服务器将授权码发送到此 URI
 * @param scope        作用域,表示客户端请求的权限范围
 * @param state        用于防止 CSRF 攻击
 * @param req          请求对象
 * @param resp         响应对象
 */
@GetMapping("/implicit_authorization")
void implicitAuthorization(@RequestParam("response_type") String responseType, @RequestParam("client_id") String clientId, @RequestParam("redirect_uri") String redirectUri, @RequestParam(required = false) String scope, @RequestParam String state, HttpServletRequest req, HttpServletResponse resp);

从上面参数可以看出看,此流程和授权码模式最大的区别是response_type的值是token

当用户点击确认授权按钮之后,授权服务器会自动重定向当前请求到redirect_uri指定的 url,并附带一个 token,如下面所示:

http://client.com/implicit_callback?access_token=ya29.AHES6ZSzX&token_type=Bearer&expires_in=3600

redirect_uri其实就是客户机应用地址,此时客户机就可以从redirect_uri中截取access_token,有了令牌客户机便可以访问资源服务器 API 获取用户信息了。

Implicit 流程没有 Refresh token,所以一旦 token 请求过期,就需要重新走一遍 implicit 整个流程。在实际操作中,如果access token已经过期,但当前用户还没有退出登录,第三方应用再重新申请 token 时,授权服务器一般都会直接颁发 AccessToken 无须再让用户确认,这样可以提高用户体验。

用户密码 Password 授权模式

Password 顾名思义直接使用用户的用户名、密码换取 AccessToken。一般只有用户非常信任的应用才会使用这种流程,比如 API 提供者发布的应用就可以使用这种流程,移动 Apps 开发可以采用这种模式,因为 API 提供者资源服务器 本身就属于移动 Apps。

在这里插入图片描述

Password 授权流程:

  1. 在用户界面,让用户输入自己的用户名和密码
  2. 用用户凭证换取 Token,输入参数grant_tye=passwordclient_id/client_secret/username/password
  3. 如果授权服务器认证用户凭证通过便直接返回 AccessToken 信息

如下接口定义:

/**
 * 用户密码 Password 授权模式
 *
 * @param grantType    必填,且固定是 password
 * @param clientId     客户机应用 id
 * @param clientSecret 应用客户端密钥
 * @param loginId      用户账号
 * @param password     密码
 */
@PostMapping("/password_authorization")
void passwordAuthorization(@RequestParam("grant_type") String grantType, @RequestParam("client_id") String clientId, @RequestParam("client_secret") String clientSecret,
                           @RequestParam String loginId, @RequestParam String password);

小结

感觉就是跳转来跳转去,便走完 OAuth 授权了。

参考

OAuth 相关流程,看着两篇文章就够了:《开放授权协议:Oauth2.0》、《详解 OAuth 2.0授权协议(Bearer token)》

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

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

相关文章

数智竞技何以成为“科技+体育”新样本?

文 | 智能相对论 作者 | 青月 “欢迎来到,钢铁突袭。” 三人一组,头戴VR设备,中国香港队和泰国队在数实融合的空间里捉对厮杀,通过互相射击对方能量铠甲获取积分。 虽然双方都展现出了极强的机动性,但显然中国香港队…

适合家电和消费类应用R7F101GEE4CNP、R7F101GEG4CNP、R7F101GEG3CNP、R7F101GEE3CNP新一代RL78通用微控制器

典型应用 • 电机控制 • 电源 • 照明 • 一般用途 • 消费类应用 • 家用电器 • 工业自动化 • 楼宇自动化 器件选型 1、R7F101GEE4CNP:16BIT MCU RL78/G24 64K 40HWQFN -40C 至 125C 2、R7F101GEG4CNP:16BIT MCU RL78/G24 128K 40HWQFN -40C 至 …

信号完整性分析基础知识之有损传输线、上升时间衰减和材料特性(十):有损传输线在时域中的表现

如果高频衰减大于低频衰减,随着信号传输,上升时间将会增加。上升时间通常定义为边沿在最终值的 10% 到 90% 之间过渡的时间。这假设信号的边缘轮廓看起来有点高斯分布,中间是最快的斜率区域。对于该波形,10%−90% 的上升时间是有意…

肖sir__linux讲解vim命令(3.1)

vim 命令 一、 vi/vim 编辑器共分为三种模式: 格式 :vim 文件名 命令模式(Command mode),“ESC”或ctrlc键 输入模式(Insert mode) 底线命令模式(Last line mode) …

c语言免杀火绒

文章目录 前记c加载器补充知识 前记 pyinstaller pyinstaller目前已经被杀疯了,简单打包一个hello a"hello" print(a)#pyinstaller -F -w b.py -n HipsMain.exe考虑Nuitka pip uninstall nuitka pip install nuitka pip install nuitka1.8.5 这里最新…

oracle21c安装报错【[INS-32014] 指定的 Oracle 基目录位置XXX无效】

一.问题 [INS-32014] 指定的 Oracle 基目录位置XXX无效 二.解决办法 安装包的文件放置不可以在中文字文件夹下面,改为英文【soft】,就可以成功安装完成了!

el-table树形数据隐藏子选择框

0 效果 1 代码 type是table数据中用来区分一级和二级的标识 // 隐藏子合同选择框 cellNone(row) {if (row.row.type 3 || row.row.type 4) {return "checkNone";} }, <style lang"scss" scoped>::v-deep {.checkNone .el-checkbox__input {displa…

Python采集智联招聘网站数据实现可视化数据

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 环境使用: Python Pycharm模块使用: selenium --> pip install selenium3.141.0 time csv驱动下载地址: https://googlechromelabs.github.io/chrome-for-te…

如何用html css js 画出曲线 或者斜线;

效果图 解题思路 将图片全部定位至中心点&#xff0c;然后x轴就变动translateX &#xff0c;y轴同理&#xff1b; 这里有两个问题 浏览器&#xff1a; 以左上角为原点0&#xff0c;0 越往下y越大 数学坐标系&#xff1a;以中心点为原点0&#xff0c;0 越往下y越小&#xff1…

1334. 阈值距离内邻居最少的城市/Floyd 【leetcode】

1334. 阈值距离内邻居最少的城市 有 n 个城市&#xff0c;按从 0 到 n-1 编号。给你一个边数组 edges&#xff0c;其中 edges[i] [fromi, toi, weighti] 代表 fromi 和 toi 两个城市之间的双向加权边&#xff0c;距离阈值是一个整数 distanceThreshold。 返回能通过某些路径…

组件插槽,生命周期,轮播图组件的封装,自定义指令的封装等详解以及axios的卖座案例

3.组件插槽 3-1组件插槽 注意 插槽内容可以访问到父组件的数据作用域,因为插槽内容本身就是在父组件模版中定义的 插槽内容无法访问子组件的数据.vue模版中的表达式只能访问其定义时所处的作用域,这和JavaScript的词法作用域是一致的,换言之: 父组件模版的表达式只能访问父组…

代码示例:基于JAX-WS和JAXB,其中http请求和响应的报文体都是xml数据

说明 基于JAX-WS编写了RESTful的web服务端点。 http请求和响应的报文体都是xml数据&#xff0c;服务端分别对应了用JAXB注解的请求和响应类。 只实现了服务端的代码示例 客户端使用了Postman 示例 要实现的目标&#xff1a;http请求和响应报文体的xml数据 http请求报文体的…

Idea安装完成配置

目录&#xff1a; 环境配置Java配置Maven配置Git配置 基础设置编码级设置File Header自动生成序列化编号配置 插件安装MyBtisPlusRestfulTooklkit-fix 环境配置 Java配置 Idea右上方&#xff0c;找到Project Settings. 有些版本直接有&#xff0c;有些是在设置下的二级菜单下…

【Linux系统编程十九】:(进程通信)--匿名管道/模拟实现进程池

【Linux系统编程十九】&#xff1a;匿名管道原理/模拟实现进程池 一.进程通信理解二.通信实现原理三.系统接口四.五大特性与四种情况五.应用场景--进程池 一.进程通信理解 什么是通信&#xff1f; 通信其实就是一个进程想把数据给另一个进程&#xff0c;但因为进程具有独立性…

小程序授权获取头像

wxml <view class"header"><text>头像</text><button class"butt" plain"true" open-type"chooseAvatar" bind:chooseavatar"chooseAvatar"><image src"{{HeadUrl}}" mode"&quo…

Java - 位运算的基本原理和用途

Java - 位运算的基本原理和用途 前言一. Java 位运算基本操作1.1 按位与 &1.2 按位或 |1.3 按位异或 ^1.4 按位取反 ~1.5 位移运算1.5.1 左移运算符 <<1.5.2 右移运算符 >>1.5.3 无符号右移运算符 >>> 二. 位运算实际运用2.1 判断奇偶性&#xff08;&…

DB9串口引脚介绍

一、公头和母头 图片示意源于网络: 二、 每个引脚的功能定义 公头&#xff1a;所有排针式的接头&#xff08;5针朝上&#xff0c;从左到右序号依次是1~9&#xff09; 母头&#xff1a;所有插槽式的接孔&#xff08;5孔朝上&#xff0c;从右到左序号依次是1~9&#xff09; 针…

计算机毕业设计选题推荐-掌心办公微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

Web与DNS综合实验

目录 题目&#xff1a; 步骤一&#xff1a;准备工作 步骤二&#xff1a;在server端搭建简单网页 步骤三&#xff1a;node1端的DNS解析配置 步骤五&#xff1a;node2端进行测试。 题目&#xff1a; 1.打开3个主机&#xff0c;server、node1、node2 2.server为web主机建立网…

typora使用PicGo自动上传图片到chevereto图床

typora使用PicGo自动上传图片到chevereto图床 近期发现&#xff0c;gitee图床不能用了。github又涉及科学上网。搜索了开源图床方案&#xff0c;找到了chevereto&#xff0c;使用起来还不错。分享给大家。 文章目录 typora使用PicGo自动上传图片到chevereto图床chevereto图床安…