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

了解 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.mfbz.cn/a/161337.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android跨进程通信,IPC,RPC,Binder系统,C语言应用层调用

文章目录 Android跨进程通信,IPC,RPC,Binder系统,C语言应用层调用()1.概念2.流程3.bctest.c3.1 注册服务,打开binder驱动3.2 获取服务 4.binder_call Android跨进程通信,IPC&#xf…

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

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

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

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

模块一、任务一.数据分析概述

一、module1 预测未来-总统大选 样本偏差 二、module2 优化现状-化妆品销售 1、数据分析师从业务类型上划分 2、目标:总销量 达到 目标销量 3、固定基本流程 (1)确定 一、目标值节节升高,是否合理?根据什么定的&…

zabbix-proxy分布式监控

Zabbix是一款开源的企业级网络监控软件,可以监测服务器、网络设备、应用程序等各种资源的状态和性能指标。在大型环境中,如果只有一个Zabbix Server来监控所有的节点,可能会遇到性能瓶颈和数据处理难题。 为了解决这个问题,Zabbi…

爱拖延怎么办?如何改变拖延症?

拖延症是我们日常生活中多见的问题,也是不怎么受重视的问题,大多数人都会认为拖延不是什么大问题,办事拖拉怎么也不可能和心理疾病扯上关系。这里小猫测试网分不同情况来讨论。 偶尔的拖延没什么关系,建议忘掉这种偶然性拖延&…

[C国演义] 第二十一章

第二十一章 最长公共子序列不相交的线 最长公共子序列 力扣链接 单个数组的子序列问题 – dp[i] -- 以nums[i] 为结尾的所有子序列中, xxx xxx. 然后状态转移方程根据 最后一个位置的归属问题进行讨论 两个数组的子序列问题 – 以小见大, 分别分析nums1中的一个区间 和 nums…

山西电力市场日前价格预测【2023-11-19】

1.日前价格预测 预测说明: 如上图所示,预测明日(2023-11-19)山西电力市场全天平均日前电价为591.63元/MWh。其中,最高日前电价为1500.00元/MWh,预计出现在16:45~20:45。最低日前电价为268.57元/MWh&#x…

JAVAEE 初阶 多线程基础(一)

多线程基础 一.线程的概念二.为什么要有线程三.进程和线程的区别和关系四.JAVA的线程和操作系统线程的关系五.第一个多线程程序1.继承Thread类 一.线程的概念 一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行着多份代码 同…

竞赛选题 疫情数据分析与3D可视化 - python 大数据

文章目录 0 前言1 课题背景2 实现效果3 设计原理4 部分代码5 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 大数据全国疫情数据分析与3D可视化 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐&#xff0…

zabbix告警 邮件告警 钉钉告警

邮件告警添加主机组添加模板添加主机在模板中添加监控项在模板中添加触发器添加动作,远程执行命令给用户绑定告警媒介类型 钉钉告警安装python依赖模块python-requests配置钉钉告警配置脚本zabbix_ding.conf在目录/var/log/zabbix中创建钉钉告警日志文件zabbix_ding…

自动化测试 —— 如何优雅实现方法的依赖!

在 seldom 3.4.0 版本实现了该功能。 在复杂的测试场景中,常常会存在用例依赖,以一个接口自动化平台为例,依赖关系: 创建用例 --> 创建模块 --> 创建项目 --> 登录。 用例依赖的问题 •用例的依赖对于的执行顺序有严格…

Dart笔记:glob 文件系统遍历

Dart笔记 文件系统遍历工具:glob 模块 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/details/13442…

机器学习笔记 - 隐马尔可夫模型的简述

隐马尔可夫模型是一个并不复杂的数学模型,到目前为止,它一直被认为是解决大多数自然语言处理问题最为快速、有效的方法。它成功地解决了复杂的语音识别、机器翻译等问题。看完这些复杂的问题是如何通过简单的模型得到描述和解决,我们会由衷地感叹数学模型之妙。 人类信息交流…

Pandas 将DataFrame中单元格内的列表拆分成单独的行

使用 explode 函数 import pandas as pddata {month: [1, 2],week: [[i for i in range(2)], [i for i in range(3)]]} df pd.DataFrame(data) print(df)df df.explode(week) print(df)

【数据结构】栈与队列面试题(C语言)

我们再用C语言做题时,是比较不方便的,因此我们在用到数据结构中的某些时只能手搓或者Ctrlcv 我们这里用到的栈或队列来自栈与队列的实现 有效的括号 有效的括号,链接奉上。 解题思路: 先说结论: 因为我们是在讲栈与…

[C国演义] 哈希的使用和开闭散列的模拟实现

哈希的使用和开闭散列的模拟实现 1. 使用1.1 unordered_map的接口1.2 unordered_set的接口 2. 哈希底层2.1 概念2.2 解决哈希冲突 3. 实现3.1 开放寻址法3.2 拉链法 1. 使用 1.1 unordered_map的接口 构造 void test1() {// 空的unordered_map对象unordered_map<int, in…

【Proteus仿真】【Arduino单片机】LM35温度计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用PCF8574、LCD1602液晶、LM35传感器等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示传感器检测温度。 二、软件设计 /* 作者&a…

Jmeter 吞吐量Per User作用

第一点&#xff1a;Per User仅在Total Execution时生效 第二点&#xff1a;Per User 选中后 聚合报告中将统计的的样本数将变成线程组配置的线程数*吞吐量控制器配置的执行样本数量&#xff08;前提是线程组配置执行接口的次数线程数*循环数 大于吞吐量控制器配置的执行样本数…

【脑与认知科学】【n-back游戏】

请参考课堂内容&#xff0c;设计一种测试工作记忆的实验方法&#xff0c;并选择三位同学作为被试测试工作记忆。请画出实验流程图&#xff0c;叙述实验测试目标&#xff0c;并分析实验结果。 举例&#xff1a;一般我们选择n_back来测试对数字或字母的记忆&#xff0c;选择色块实…