API 网关 OpenID Connect 实战:单点登录(SSO)如此简单

作者:戴靖泽,阿里云 API 网关研发,Higress 开源社区 Member

前言

随着企业的发展,所使用的系统数量逐渐增多,用户在使用不同系统时需要频繁登录,导致用户体验较差。单点登录(Single Sign-On,简称 SSO)正是为了解决这一问题。当用户登录一次后,即可获取所有系统的访问权限,不需要对每个单一系统逐一登录。

目前,SSO 的实现方案常见有以下几种:

  1. 基于 JWT: JWT(JSON Web Token)是一种用于在各方之间安全传递信息的开放标准,令牌中包含用户的身份信息和权限。然而,JWT 用于 SSO 时缺乏标准化方案,导致集成复杂,且令牌一旦签发无法撤销,可能影响安全性。
  2. 基于 CAS: CAS(Central Authentication Service)是一种基于中间件的开源单点登录解决方案,通常用于大学和大型企业。用户在一处登录后即可无缝访问所有与 CAS 集成的应用。但其实现较为复杂,对系统集成要求较高。
  3. 基于 SAML: SAML(Security Assertion Markup Language)是一种协议,用于在应用程序与 SSO 服务之间交换身份验证信息。它使用 XML 来交换用户标识数据,提供高安全性和灵活性,但配置和实施较为复杂,增加了开发和维护成本。
  4. 基于 OIDC: OIDC(OpenID Connect)是基于 OAuth 2.0 的身份验证层,允许用户通过多种客户端(如 Web 应用、移动应用等)进行身份验证。OIDC 具有标准化、简单易用、灵活性和安全性等优点,成为许多企业在实现单点登录时的首选。

OIDC 还有广泛第三方服务提供商的支持,如支付宝、钉钉、微信、GitHub 等。例如下图中 Sealos 应用的登录页面,可以跳转 Github,微信和 Google 进行第三方登录,使用的就是 OIDC 身份认证:

Higress 网关作为后端服务所有请求的入口,可以集成 OIDC 实现统一认证服务,所有后端服务不需要各自实现用户认证逻辑,而是统一通过网关进行用户身份的验证。这样简化了系统架构,减少了重复工作,并提高了安全性。

用户在网关配置 OIDC 认证鉴权,可以实现对资源的细粒度访问控制。并且可以方便地对接自建的身份认证服务,或者社交媒体账号等其他第三方账户登录,增强了业务的便利性。

基于此,我们实现了 OIDC 插件让用户在 Higress 网关可以零代码实现 SSO 单点登录。

OIDC 插件使用

后端样例服务部署

参考 Istio Bookinfo [ 1] 样例在集群中部署后端样例服务,用来调试 OIDC 插件功能,部署好之后可以通过下面的命令验证是否部署成功。

kubectl exec "$(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}')" -c ratings -- curl -sS productpage:9080/productpage | grep -o "<title>.*</title>"

通过自建身份服务使用 OIDC 插件

配置示例

部署 keycloak 身份认证服务

参考 keycloak-getting-started-docker [ 2] 使用 docker 快速部署 keycloak 身份认证服务,添加用户并创建 client。

🔔 注:需填写 Valid redirect URIs,Valid post logout URIs,Web origins 配置项,否则 OIDC Provider 会认为用户跳转的重定向 URL 或登出 URL 无效。

Higress 服务配置

在 Higress 服务来源中创建 Keycloak 固定地址服务。

Wasm 插件配置

redirect_url: 'http://foo.bar.com/oauth2/callback'
oidc_issuer_url: 'http://127.0.0.1:9090/realms/myrealm'
client_id: 'XXXXXXXXXXXXXXXX'
client_secret: 'XXXXXXXXXXXXXXXX'
scope: 'openid email'
cookie_secret: 'nqavJrGvRmQxWwGNptLdyUVKcBNZ2b18Guc1n_8DCfY='
service_name: 'keycloak.static'
service_port: 80
service_host: '127.0.0.1:9090'

插件效果演示

访问服务页面,未登陆的话进行跳转。

登陆成功跳转到服务页面。

访问登出跳转到登出页面。

http://foo.bar.com/oauth2/sign_out?rd=http%3A%2F%2F127.0.0.1:9090%2Frealms%2Fmyrealm%2Fprotocol%2Fopenid-connect%2Flogout

访问登出跳转到登出页面(携带 post_logout_redirect_uri 参数跳转指定 uri)。

http://foo.bar.com/oauth2/sign_out?rd=http%3A%2F%2F127.0.0.1:9090%2Frealms%2Fmyrealm%2Fprotocol%2Fopenid-connect%2Flogout%3Fpost_logout_redirect_uri%3Dhttp%3A%2F%2Ffoo.bar.com%2Ffoo

通过第三方服务提供商使用 OIDC 插件

配置示例

配置阿里云 OAuth 应用

参考 Web 应用登录阿里云 [ 3] 流程配置 OAuth 应用。

Higress 服务配置

为了让插件能够访问到 OIDC 服务提供商,需要在 Higress 服务来源中创建 Aliyun DNS 服务。

插件参数配置


redirect_url: 'http://foo.bar.com/oauth2/callback'
provider: aliyun
oidc_issuer_url: 'https://oauth.aliyun.com/'
client_id: 'XXXXXXXXXXXXXXXX'
client_secret: 'XXXXXXXXXXXXXXXX'
scope: 'openid'
cookie_secret: 'nqavJrGvRmQxWwGNptLdyUVKcBNZ2b18Guc1n_8DCfY='
service_name: 'aliyun.dns'
service_port: 443
插件效果演示

访问服务页面,未登陆的话进行跳转。

扫码登陆成功跳转到服务页面。

访问登出跳转到登出页面(阿里云登出后会重定向到登录页)。

http://foo.bar.com/oauth2/sign_out?rd=https%3A%2F%2Faccount.aliyun.com%2Flogout%2Flogout.htm

OIDC 插件方案选型

Istio Sidecar 容器方案

Istio 社区提出了使用 Sidecar 容器部署外部授权 [ 4] ,例如 Istio OIDC Authentication with OAuth2-Proxy [ 5] 中提到的部署成熟的 oauth2-proxy 方案,但是这种方案的缺陷首先是在外部授权服务与数据面之间会多一层请求调用,导致性能上会差,其次是外部授权服务会占用一定的 CPU 资源和内存资源,并且如果用户的需要配置多个服务提供商,这个方案也需要部署多个外部授权,因此这个方案灵活性不够强。

Envoy Filter 方案

现有 Envoy 社区开发的 C++ 原生的 OAuth2 Filter [ 6] 也可以实现 OIDC 身份认证的功能,目前社区正在积极开发中,Envoy 社区的 envoy gateway [ 7] 项目使用了该方案,现有的问题是不支持 OIDC 协议标准的 state 参数(Issue 35232 [ 8] )导致受 CSRF 攻击威胁,并且不支持对 cookie 中的令牌进行加密(Issue 23508 [ 9] ),因此这个方案存在安全漏洞导致 OAuth2 Filter 无法在生产环境中使用。

Wasm 插件方案

开源的 Higress 项目插件开发框架 [ 10] 提供了多种编程语言包括 Rust,C++,Golang 和 AssemblyScript(TypeScript 的 Wasm 方言)等编写 Wasm 插件,相比于 C++ 的生态,Rust,Go,TypeScript 等语言的开源库生态更丰富,其中开源的实现了 OIDC 功能的项目如下:

  • Golang:oauth2-proxy [ 11]
  • Rust:oauth2-rs [ 12]
  • TypeScript:angular-oauth2-oidc [1****3]

其中 oauth2-proxy 项目在开源社区中得到了广泛的使用和验证,同时拥有最活跃的社区支持,发布频繁的更新和改进,实现了大部分常见的 OIDC 服务提供商,也是目前 Istio Sidecar 容器方案中推荐的。Wasm 插件方案的优势体现在以下三个方面:

  1. 生产可用性: OIDC 协议涉及身份认证,稳定性和安全性至关重要。基于成熟的开源项目二次开发的插件,通常具有更高的可靠性
  2. 可扩展性: 不同企业实现 OIDC 协议时不一定会完全遵循标准,企业用户可以基于开源的插件代码进行二次开发,实现企业定制化的需求。
  3. 安全性: 插件运行在严格的沙箱虚拟环境中,即使代码出现异常也不会使得 Envoy 崩溃。

因此基于以上优势并考虑到前两个方案的灵活性不够强和安全漏洞问题,选择开发 Wasm 插件实现 OIDC 功能。

OIDC 插件原理

OIDC 插件基于 oauth2-proxy 项目的核心流程实现,由于在 Envoy 插件中发起外部请求需要通过异步调用,因此将 oauth2-proxy 项目的主流程中的同步调用改为跟 Envoy 中外部服务的异步调用,在回调函数中对响应进行处理,具体的代码参考 Higress 中的 OIDC 插件 [ 14] ,OIDC 插件的请求响应流程如图所示。

  1. 模拟用户访问对应服务 api。
curl --url "foo.bar.com/headers"
  1. Higress 重定向到 OIDC Provider 登录页同时携带 client_id、response_type、scope 等 OIDC 认证的参数并设置 csrf cookie 防御 CSRF 攻击。
curl --url "https://dev-o43xb1mz7ya7ach4.us.auth0.com/authorize"\
  --url-query "approval_prompt=force" \
  --url-query "client_id=YagFqRD9tfNIaac5BamjhsSatjrAnsnZ" \
  --url-query "redirect_uri=http%3A%2F%2Ffoo.bar.com%2Foauth2%2Fcallback" \
  --url-query "response_type=code" \
  --url-query "scope=openid+email+offline_access" \
  --url-query "state=nT06xdCqn4IqemzBRV5hmO73U_hCjskrH_VupPqdcdw%3A%2Ffoo" \
  --header "Set-Cookie: _oauth2_proxy_csrf=LPruATEDgcdmelr8zScD_ObhsbP4zSzvcgmPlcNDcJpFJ0OvhxP2hFotsU-kZnYxd5KsIjzeIXGTOjf8TKcbTHbDIt-aQoZORXI_0id3qeY0Jt78223DPeJ1xBqa8VO0UiEOUFOR53FGxirJOdKFxaAvxDFb1Ok=|1718962455|V1QGWyjQ4hMNOQ4Jtf17HeQJdVqHdt5d65uraFduMIU=; Path=/; Expires=Fri, 21 Jun 2024 08:06:20 GMT; HttpOnly"
  1. 用户在登录页进行登录。

  1. 携带授权重定向到 Higress 并携带了 state 参数用于验证 CSRF Cookie,授权 code 用于交换 Token。
curl --url "http://foo.bar.com/oauth2/callback" \
  --url-query "state=nT06xdCqn4IqemzBRV5hmO73U_hCjskrH_VupPqdcdw%3A%2Ffoo" \
  --url-query "code=0bdopoS2c2lx95u7iO0OH9kY1TvaEdJHo4lB6CT2_qVFm"
  1. 利用授权交换 id_token 和 access_token。
curl -X POST \
  --url "https://dev-o43xb1mz7ya7ach4.us.auth0.com/oauth/token" \
  --data "grant_type=authorization_code" \
  --data "client_id=YagFqRD9tfNIaac5BamjhsSatjrAnsnZ" \
  --data "client_secret=ekqv5XoZuMFtYms1NszEqRx03qct6BPvGeJUeptNG4y09PrY16BKT9IWezTrrhJJ" \
  --data "redirect_uri=http%3A%2F%2Ffoo.bar.com%2Foauth2%2Fcallback" \
  --data "code=0bdopoS2c2lx95u7iO0OH9kY1TvaEdJHo4lB6CT2_qVFm" \

返回的请求里包含了 id_token,access_token,refresh_token 用于后续刷新 access_token。

{
    "access_token": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiaXNzIjoiaHR0cHM6Ly9kZXYtbzQzeGIxbXo3eWE3YWNoNC51cy5hdXRoMC5jb20vIn0..WP_WRVM-y3fM1sN4.fAQqtKoKZNG9Wj0OhtrMgtsjTJ2J72M2klDRd9SvUKGbiYsZNPmIl_qJUf81D3VIjD59o9xrOOJIzXTgsfFVA2x15g-jBlNh68N7dyhXu9237Tbplweu1jA25IZDSnjitQ3pbf7xJVIfPnWcrzl6uT8G1EP-omFcl6AQprV2FoKFMCGFCgeafuttppKe1a8mpJDj7AFLPs-344tT9mvCWmI4DuoLFh0PiqMMJBByoijRSxcSdXLPxZng84j8JVF7H6mFa-dj-icP-KLy6yvzEaRKz_uwBzQCzgYK434LIpqw_PRuN3ClEsenwRgIsNdVjvKcoAysfoZhmRy9BQaE0I7qTohSBFNX6A.mgGGeeWgugfXcUcsX4T5dQ",
    "refresh_token": "GrZ1f2JvzjAZQzSXmyr1ScWbv8aMFBvzAXHBUSiILcDEG",
    "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imc1Z1ExSF9ZbTY0WUlvVkQwSVpXTCJ9.eyJlbWFpbCI6IjE2MDExNTYyNjhAcXEuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJpc3MiOiJodHRwczovL2Rldi1vNDN4YjFtejd5YTdhY2g0LnVzLmF1dGgwLmNvbS8iLCJhdWQiOiJZYWdGcVJEOXRmTklhYWM1QmFtamhzU2F0anJBbnNuWiIsImlhdCI6MTcxOTE5ODYzOCwiZXhwIjoxNzE5MjM0NjM4LCJzdWIiOiJhdXRoMHw2NjVkNzFlNzRjMTMxMTc3YmU2NmU2MDciLCJzaWQiOiJjdDJVOF9ZUS16VDdFOGkwRTNNeUstejc5ZGlWUWhhVSJ9.gfzXKJ0FeqzYqOUDLQHWcUG19IOLqkpLN09xTmIat0umrlGV5VNSumgWH3XJmmwnhdb8AThH3Jf-7kbRJzu4rM-BbGbFTRBTzNHeUajFOFrIgld5VENQ_M_sXHkTp0psWKSr9vF24kmilCfSbvC5lBKjt878ljZ7-xteWuaUYOMUdcJb4DSv0-zjX01sonJxYamTlhji3M4TAW7VwhwqyZt8dBhVSNaRw1wUKj-M1JrBDLyx65sroZtSqVA0udIrqMHEbWYb2de7JjzlqG003HRMzwOm7OXgEd5ZVFqgmBLosgixOU5DJ4A26nlqK92Sp6VqDMRvA-3ym8W_m-wJ_A",
    "scope": "openid email offline_access",
    "expires_in": 86400,
    "token_type": "Bearer"
}
  1. 将获得的 id_token 和 access_token 加密存储在 Cookie _oauth2_proxy 中,用于后续用户登录状态的验证,同时清除 Cookie _oauth2_proxy_csrf。
"Set-Cookie": [
    "_oauth2_proxy_csrf=; Path=/; Expires=Mon, 24 Jun 2024 02:17:39 GMT; HttpOnly",
    "_oauth2_proxy=8zM_Pcfpp_gesKFe4SMg08o5Iv0A8WAOQOmG1-vZBbQ56UggYVC0Cu-gFMEoxJZU5q1O5vqRlVBizlLetgVjRCksGVbttwl8tQ7h5YiyIubbbtvF1T4JzLh3QfzUUrwbB-VznOkh8qLbjAhddocecjBt4rMiDyceKXqMr4eO5TUEMx4vHtJYnTYalMeTYhGXk5MNSyrdZX9NnQnkdrCjiOQM13ggwob2nYwhGWaAlgzFSWkgkdtBy2Cl_YMWZ8_gKk9rDX289-JrJyGpr5k9O9RzRhZoY2iE3Mcr8-Q37RTji1Ga22QO-XkAcSaGqY1Qo7jLdmgZTYKC5JvtdLc4rj3vcbveYxU7R3Pt2vEribQjKTh4Sqb0aA03p4cxXyZN4SUfBW1NAOm4JLPUhKJy8frqC9_E0nVqPvpvnacaoQs8WkX2zp75xHoMa3SD6KZhQ5JUiPEiNkOaUsyafLvht6lLkNDhgzW3BP2czoe0DCDBLnsot0jH-qQpMZYkaGr-ZnRKI1OPl1vHls3mao5juOAW1VB2A9aughgc8SJ55IFZpMfFMdHdTDdMqPODkItX2PK44GX-pHeLxkOqrzp3GHtMInpL5QIQlTuux3erm3CG-ntlUE7JBtN2T9LEb8XfIFu58X9_vzMun4JQlje2Thi9_taI_z1DSaTtvNNb54wJfSPwYCCl4OsH-BacVmPQhH6TTZ6gP2Qsm5TR2o1U2D9fuVkSM-OPCG9l3tILambIQwC3vofMW6X8SIFSmhJUDvN7NbwxowBiZ6Y7GJRZlAk_GKDkpsdrdIvC67QqczZFphRVnm6qi-gPO41APCbcO6fgTwyOhbP3RrZZKWSIqWJYhNE3_Sfkf0565H7sC7Hc8XUUjJvP3WnjKS9x7KwzWa-dsUjV3-Q-VNl-rXTguVNAIirYK-qrMNMZGCRcJqcLnUF0V_J2lVmFyVsSlE3t0sDw2xmbkOwDptXFOjQL5Rb4esUMYdCBWFajBfvUtcZEFtYhD0kb6VcbjXO3NCVW5qKh_l9C9SRCc7TG1vcRAqUQlRXHacTGWfcWsuQkCJ3Mp_oWaDxs1GRDykQYxAn5sTICovThWEU2C6o75grWaNrkj5NU-0eHh3ryvxLmGLBOXZV9OQhtKShWmUgywSWMxOHOuZAqdAPULc8KheuGFjXYp-RnCbFYWePJmwzfQw89kSkj1KUZgMYwKEjSz62z2qc9KLczomv76ortQzvo4Hv9kaW6xVuQj5R5Oq6_WMBOqsmUMzcXpxCIOGjcdcZRBc0Fm09Uy9oV1PRqvAE4PGtfyrCaoqILBix8UIww63B07YGwzQ-hAXDysBK-Vca2x7GmGdXsNXXcTgu00bdsjtHZPDBBWGfL3g_rMAXr2vWyvK4CwNjcaPAmrlF3geHPwbIePT0hskBboX1v1bsuhzsai7rGM4r53pnb1ZEoTQDa1B-HyokFgo14XiwME0zE1ifpNzefjpkz1YY2krJlqfCydNwoKaTit4tD2yHlnxAeFF9iIrxzSKErNUFpmyLa7ge7V33vhEH-6k5oBTLE2Q2BrC6aAkLCcPwU9xv_SzBDQPRY0MEYv3kGF03Swo1crRbGh-aifYX9NiHDsmG6r1vAnx0MAOw2Jzuz2x6SSdfBrzlcoWBlrwiZzd9kAKq75n1Uy9uzZ8SRnkBrEZySHBwEbu196VklkRE0jqwC-e3wWNNuviSOfwkVeX-7QdOoO10yw9VK2sW52lFvIEf4chv_ta7bGfAZOWBjpktG6ZLD81SE6A88zpqG2SysSyNMp9hl-umG-5sFsjCn_c9E8bDvwkUOUVb9bNqhBDsZgR0BNPawiOZjmyfhzmwmWf-zgFzfFSV6BvOwNRi3sCOHTsWcuk9NBQ_YK8CpNkVl3WeIBSDfidimuC_QV9UWKs1GPk35ZRkM4zKtLY2JsBFWKaDy_P80TcOzcMBoP8gIBClXZ-WUqfE8s1yyc4jrq-qL1_wJ24ef1O9FktsbyZiDKXw2vnqsT8-g_hCeG-unrT1ZFscf8oNdqczARHX-K4vKH2k3uIqEx1M=|1719199056|2rsgdUIClHNEpxBLlHOVRYup6e4oKensQfljtmn4B80=; Path=/; Expires=Mon, 01 Jul 2024 03:17:36 GMT; HttpOnly"
]
  1. 携带 Authorization 的标头对应 access_token 访问对应 api。
curl --url "foo.bar.com/headers"
  --header "Authorization: Bearer eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiaXNzIjoiaHR0cHM6Ly9kZXYtbzQzeGIxbXo3eWE3YWNoNC51cy5hdXRoMC5jb20vIn0..WP_WRVM-y3fM1sN4.fAQqtKoKZNG9Wj0OhtrMgtsjTJ2J72M2klDRd9SvUKGbiYsZNPmIl_qJUf81D3VIjD59o9xrOOJIzXTgsfFVA2x15g-jBlNh68N7dyhXu9237Tbplweu1jA25IZDSnjitQ3pbf7xJVIfPnWcrzl6uT8G1EP-omFcl6AQprV2FoKFMCGFCgeafuttppKe1a8mpJDj7AFLPs-344tT9mvCWmI4DuoLFh0PiqMMJBByoijRSxcSdXLPxZng84j8JVF7H6mFa-dj-icP-KLy6yvzEaRKz_uwBzQCzgYK434LIpqw_PRuN3ClEsenwRgIsNdVjvKcoAysfoZhmRy9BQaE0I7qTohSBFNX6A.mgGGeeWgugfXcUcsX4T5dQ"
  1. 后端服务根据 access_token 获取用户信息并返回对应的 Http 响应。
{
    "email": "******",
    "email_verified": false,
    "iss": "https://dev-o43xb1mz7ya7ach4.us.auth0.com/",
    "aud": "YagFqRD9tfNIaac5BamjhsSatjrAnsnZ",
    "iat": 1719198638,
    "exp": 1719234638,
    "sub": "auth0|665d71e74c131177be66e607",
    "sid": "ct2U8_YQ-zT7E8i0E3MyK-z79diVQhaU"
}

总结

本文对 Higress 中开源的 OIDC Wasm 插件进行了介绍,现在 Higress 项目中 Wasm 插件支持使用 Go、C++、Rust、AssemblyScript 等语言编写,后续会支持更多的编程语言,有着更低的开发门槛,同时 Wasm 插件运行在隔离的沙箱环境中,具有更高的安全性,而 Wasm 本身作为一种高性能的可移植二进制指令格式不断的有新的进展和技术革新,未来网关场景的更多功能均可以考虑在 Wasm 插件中实现。

本文中的 OIDC Wasm 插件已在 Higress 项目中开源:https://github.com/alibaba/higress/tree/main/plugins/wasm-go/extensions/oidc

欢迎大家在 github 社区提出宝贵的建议,或者在 Higress 社区交流群(钉钉群号:30735012403)里一起沟通。

相关链接:

[1] Istio Bookinfo

https://istio.io/latest/docs/examples/bookinfo/

[2] keycloak-getting-started-docker

https://www.keycloak.org/getting-started/getting-started-docker

[3] Web 应用登录阿里云

https://help.aliyun.com/zh/ram/user-guide/access-alibaba-cloud-apis-from-a-web-application

[4] 外部授权

https://istio.io/latest/zh/docs/tasks/security/authorization/authz-custom/

[5] Istio OIDC Authentication with OAuth2-Proxy

https://medium.com/@lucario/istio-external-oidc-authentication-with-oauth2-proxy-5de7cd00ef04

[6] OAuth2 Filter

https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/oauth2_filter

[7] envoy gateway

https://github.com/envoyproxy/gateway

[8] Issue 35232

https://github.com/envoyproxy/envoy/issues/35232

[9] Issue 23508

https://github.com/envoyproxy/envoy/issues/23508

[10] 开发框架

https://github.com/alibaba/higress/tree/main/plugins

[11] oauth2-proxy

https://github.com/oauth2-proxy/oauth2-proxy

[12] oauth2-rs

https://github.com/ramosbugs/oauth2-rs

[13] angular-oauth2-oidc

https://github.com/manfredsteyer/angular-oauth2-oidc

[14] OIDC 插件

https://github.com/alibaba/higress/tree/main/plugins/wasm-go/extensions/oidc

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

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

相关文章

Python和MATLAB(Java)及Arduino和Raspberry Pi(树莓派)点扩展函数导图

&#x1f3af;要点 反卷积显微镜图像算法微珠图像获取显微镜分辨率基于像素、小形状、高斯混合等全视野建模基于探测器像素经验建模荧光成像算法模型傅里叶方法计算矢量点扩展函数模型天文空间成像重建二维高斯拟合天体图像伽马射线能量和视场中心偏移角标量矢量模型盲解卷积和…

每日OJ_牛客_求和(递归深搜)

目录 牛客_求和&#xff08;递归深搜&#xff09; 解析代码 牛客_求和&#xff08;递归深搜&#xff09; 求和_好未来笔试题_牛客网 解析代码 递归中每次累加一个新的数&#xff0c;如果累加和大于等于目标&#xff0c;结束递归。此时如果累加和正好等于目标&#xff0c;则打…

Quartz.Net_快速开始

简述 Quartz中主要分为三部分&#xff0c;JobDetail、Trigger、Scheduler&#xff0c;分别是任务、触发器、调度器&#xff0c;三者的关系为&#xff1a;Trigger控制JobDetail的执行时间和频率&#xff0c;而Scheduler负责将具体的Trigger与具体的JobDetail绑定 1.安装Quartz…

【无线通信发展史⑧】测量地球质量?重力加速度g的测量?如何推导单摆周期公式?地球半径R是怎么测量出来的?

前言&#xff1a;用这几个问答形式来解读下我这个系列的来龙去脉。如果大家觉得本篇文章不水的话希望帮忙点赞收藏加关注&#xff0c;你们的鼓舞是我继续更新的动力。 我为什么会写这个系列呢&#xff1f; 首先肯定是因为我本身就是一名从业通信者&#xff0c;想着更加了解自…

十大口碑最好开放式蓝牙耳机是哪些?五款热销好用产品测评!

​开放式耳机现在超火&#xff0c;成了时尚、好看又舒服的代名词&#xff0c;迅速俘获了一大波粉丝&#xff0c;成了耳机界的新宠儿。跟那些传统的入耳式耳机比起来&#xff0c;开放式耳机戴着更稳&#xff0c;对耳朵也更友好。不过&#xff0c;也有人觉得这玩意儿不值&#xf…

vue3集成sql语句编辑器

使用的是codemirror 安装 pnpm add codemirror vue-codemirror --savepnpm add codemirror/lang-sqlpnpm add codemirror/theme-one-dark使用 <template><codemirror v-model"configSql" placeholder"Code goes here..." ref"codemirrorR…

AIGC与数据分析融合,引领商业智能新变革(TOP企业实践)

AIGC与数据分析融合&#xff0c;引领商业智能新变革&#xff08;TOP企业实践&#xff09; 前言AIGC与数据分析融合 前言 在当今数字化时代&#xff0c;数据已成为企业发展的核心资产&#xff0c;而如何从海量数据中挖掘出有价值的信息&#xff0c;成为了企业面临的重要挑战。随…

云服务器内网穿透连接云手机配置ALAS

文章目录 服务器安装TailscaleNAT网络&#xff08;无独立IP&#xff09;云服务器安装Tailscale有固定IP的云服务器安装Tailscale 云手机安装Tailscale开启无线网络调试安装Tailscale ALAS连接云手机 上次写到服务器连接云手机时只说了有独立IP的&#xff0c;但有独立IP的云手机…

算法打卡——田忌赛马问题

问题简介&#xff1a;就是一个贪心的思想&#xff0c;下面上题目 要求示例输出输入 大体上先比较快马&#xff0c;田的快马与王的快马 其次比较田的慢马与王的慢马&#xff0c; 两处边界比较完全之后可以直接贪心了 几份示例的代码 代码一 #include <bits/stdc.h> …

个人旅游网(3)——功能详解——旅游路线功能

文章目录 一、旅游路线分类功能1.1、接口详解1.1.1、findAll 二、路线分类下的旅游路线功能2.2、接口详解2.2.1、findRouteListByCid 三、点击单条旅游路线查看其详情功能3.1、接口详解3.1.1、findRouteListByRid 四、分页功能4.1、导入依赖4.2、配置项的配置4.3、实现分页 一、…

每日一题~cf 970 div3 (A思维,B小模拟,C二分,D排列数建图成环,E 26个字母暴力+前缀和,F 逆元,G 数论gcd )

A 题意&#xff1a; 有 a 个1 ,b 个2.问是否能将这些数划分为两个数值相等的集合。 输出 YES 或者 NO —————— 问题等价于 将数组 分成两个数值相同的数组。所以sum 应该是偶数。也就是说 1 的个数是偶数。在i1的个数是偶数的情况下&#xff0c;将 2 分成两份&#xff0c;…

使用 EMQX 开源版的 Webhook 机制处理消息并存储数据

1、前言 EMQX 是一款强大的开源 MQTT 消息代理&#xff0c;它支持大量的连接和高吞吐量&#xff0c;适用于各种物联网应用。Webhook 是 EMQX 提供的扩展功能之一&#xff0c;用于将消息推送到外部的 HTTP 服务。在本文中&#xff0c;我们将介绍如何使用 EMQX 开源版的 Webhook …

整型数组按个位值排序

题目描述 给定一个非空数组(列表)&#xff0c;其元素数据类型为整型&#xff0c;请按照数组元素十进制最低位从小到大进行排序&#xff0c;十进制最低位相同的元司 相对位置保持不变。 当数组元素为负值时&#xff0c;十进制最低位等同于去除符号位后对应十进制值最低位。 输…

QT核心内容(9.6)

1> 手写unique_ptr智能指针 代码&#xff1a; #include <iostream> #include <cassert>using namespace std; template<typename T> class my_unique_ptr { private:T* ptr;// 禁止拷贝构造函数和拷贝赋值操作符my_unique_ptr(const my_unique_ptr&a…

手机扬声器音量总是不够大?试试“扬声器助推器”吧

手机的扬声器音量总是不够大&#xff0c;尤其是在嘈杂的环境中&#xff0c;音乐和视频的声音总是不太清晰。直到我发现了这款“扬声器助推器”&#xff0c;我的手机音质瞬间提升了好几个档次。 软件简介&#xff1a; “扬声器助推器”利用先进的音频处理技术&#xff0c;能够…

vivado error:Combinatorial Loop Alert:1 LUT cells form a combinatorial loop

VIVADO ERROR :Combinatorial Loop Alert&#xff1a;1 LUT cells form a combinatorial loop vivao生成bit流时发生报错&#xff0c;如下图所示定位原因解决 vivao生成bit流时发生报错&#xff0c;如下图所示 定位原因 在三段式状态机中&#xff0c;组合逻辑代码if else 语句…

STM32:TIM定时中断配置的最全库函数讲解笔记

声明&#xff1a;本博客为哔哩哔哩up主江协科技 “STM32入门教程”的听课笔记&#xff0c;仅供学习、参考使用&#xff0c;不得用作其他用途&#xff0c;违者必究。如有版权问题&#xff0c;请联系作者修改。 目录 一、综述 二、TIM库 初始化 2.1、TIM_DeInit 恢复缺省值 …

IPv6 Sec机制的深度解析与优势探讨

IPv6的sec机制&#xff0c;主要指的是IPv6协议中内置的安全机制&#xff0c;特别是通过IP Sec协议集来实现的。IPv6在设计之初就考虑到了安全性问题&#xff0c;并内置了对IP Sec的支持&#xff0c;这使得IPv6网络在安全性能上相比IPv4有了显著的提升。 IP Sec协议集主要由认证…

Android Studio打开Modem模块出现:The project ‘***‘ is not a Gradle-based project

花了挺长时间处理该问题&#xff0c;特记录如下&#xff1a;1.背景&#xff1a; 在Android studio 下导入一个新增的modem模块&#xff0c;如MPSS.DE.3.1.1\modem_proc\AAA, 目的是看代码方便一些&#xff0c;可以自由搜索各种关键字。但导入该项目时出现了如下错误&#xff1a…

好用的AI编程助手[豆包]

欢迎来到 Marscode 的世界&#xff01;这里将为你揭秘 Marscode&#xff0c;它的独特之处、应用领域等相关精彩内容等你来探索。 一、打开VS Code 二、选择 Extensions,搜索marscode 三、点击安装 四、点击使用 五、输入需要编写的代码 六、根据自己的需求修改代码 MarsCode 注…