Azure AD统一认证及用户数据同步开发指导

本文主要目的为:指导开发者进行自有服务与Azure AD统一认证的集成,以及阐述云端用户数据同步的实现方案。本文除了会介绍必要的概念、原理、流程外,还会包含Azure门户设置说明,以及使用Fiddler进行全流程的实操验证,同时还会结合实际的业务需求提出具体的方案建议及关键实现方法的说明,以此达到开发指导的效果。

1. 基础概念

1.1 Azure AD

Azure Active Directory,简称 Azure AD,是微软提供的云身份验证服务,用于管理用户身份和访问权限。它是基于标准的开放式身份协议构建的,可以与多种应用程序和服务集成,包括云应用程序、本地应用程序和移动应用程序。
在这里插入图片描述

1.1.1 Azure AD 主要功能

  • 身份验证和授权管理: Azure AD 提供了多种身份验证方式,包括用户名和密码、多重身份验证、SSO(单一登录)等,以确保用户身份的安全性。它还可以管理用户对资源的访问权限,并控制用户能够执行的操作。
  • 集成式身份管理: Azure AD 可以与其他标识提供者集成,包括本地 Active Directory、LDAP、SAML 和 OAuth,使用户可以使用他们已有的帐户登录到 Azure AD 中的应用程序和服务。
  • 应用程序集成: Azure AD 支持与各种应用程序和服务的集成,包括 Microsoft 365、Azure、SaaS 应用程序、自定义应用程序等。它提供了一种安全的方式来管理和控制用户对这些应用程序的访问权限。
  • 安全和合规性: Azure AD 提供了安全性和合规性功能,包括多重身份验证、条件访问、安全策略、审计日志等,以帮助组织保护其数据和资源。

总之,Azure AD 是一个灵活且功能强大的身份验证和访问管理平台,可以帮助组织实现身份验证、授权和安全管理的统一化。从本质上来说,Azure AD 是基于 OAuth 2.0 和 OpenID Connect 规范的。OAuth 2.0 是一种用于授权的开放标准,用于安全地委托访问令牌,而 OpenID Connect 则是在 OAuth 2.0 的基础上添加了身份验证的协议。Azure AD 结合了这两种标准,以提供身份验证和授权功能,支持多种身份验证方法,并提供与其他服务和应用程序的集成。

需要补充说明的是Microsoft 将 Azure Active Directory (Azure AD) 更名为 Microsoft Entra ID,以介绍产品的多云多平台功能、缓解与 Windows Server Active Directory 的混淆,并统一 Microsoft Entra 产品系列。详见官方文档:https://learn.microsoft.com/zh-cn/entra/

1.1.2 Azure AD 与 ADFS区别

Azure AD 和 Active Directory Federation Services(ADFS)都是由微软提供的身份认证解决方案,但它们在一些方面有所不同:

  • 部署位置
    Azure 统一认证是基于云的身份认证解决方案,完全托管在 Microsoft Azure 云平台上。
    ADFS 是一个本地部署的身份认证解决方案,通常部署在组织的本地网络中。
  • 适用范围
    Azure 统一认证是为云环境和混合云环境设计的,可以为云服务、SaaS 应用程序以及本地应用程序提供身份验证和访问控制。
    ADFS 主要用于提供单点登录 (SSO) 和基于标准的身份验证服务,通常用于本地应用程序和企业资源的身份认证。
  • 标准支持
    Azure 统一认证支持 OAuth 2.0 和 OpenID Connect 等开放标准,使其能够与多种应用程序和服务集成。
    ADFS 支持标准的身份验证协议,如SAML(Security Assertion Markup Language)和 WS-Federation(Web Services Federation)。
  • 管理和维护
    Azure 统一认证是一种完全托管的服务,由 Microsoft 管理和维护,无需组织自行管理基础设施。
    ADFS 需要组织自行部署、配置和维护,需要更多的 IT 管理工作。
  • 灵活性和扩展性
    Azure 统一认证提供了更高的灵活性和扩展性,可以通过添加和配置不同的功能组件来满足不同的身份验证和授权需求。
    ADFS 在本地部署方面更加灵活,可以根据组织的特定需求进行自定义和配置。

综上所述,Azure 统一认证适用于云环境和混合云环境,提供了更多的托管功能和集成选项,而 ADFS 则更适用于需要本地部署、自定义和控制的场景。关于ADFS及AD的部署配置可以参考:https://blog.csdn.net/camelials/article/details/134857159

1.2 微软Graph

Microsoft Graph 是微软提供的统一 API 平台,用于访问 Microsoft 365 中的各种数据和服务。它提供了一种统一的方式来访问多种 Microsoft 产品和服务的数据,包括 Office 365、Azure Active Directory、Exchange Online、SharePoint 等。
在这里插入图片描述

理解 Microsoft Graph 可以从以下几个方面来看:

  • 统一访问数据和服务:Microsoft Graph 提供了一个统一的终结点,使开发者可以通过一组 API 来访问各种 Microsoft 产品和服务中的数据和功能,而不需要与每个服务单独交互。
  • 多种数据类型支持:Microsoft Graph 支持访问多种类型的数据,包括用户信息、邮件、日历、文件、组织结构等。这些数据可以用于构建各种类型的应用程序和解决方案。
  • RESTful API:Microsoft Graph 的 API 是基于 RESTful 架构的,使用标准的 HTTP 方法和 URL 结构进行访问。这使得它易于使用和集成到现有的应用程序和开发工具中。
  • 权限控制和安全性:Microsoft Graph 提供了严格的权限控制机制,开发者需要通过 OAuth 2.0 认证来获取访问权限,并在访问数据时遵循权限范围的规定,确保数据的安全性和隐私保护。
  • 实现业务逻辑:通过 Microsoft Graph,开发者可以实现各种业务逻辑,例如获取用户信息、发送邮件、管理文件、创建团队等。它可以帮助开发者构建智能化、协作化的应用程序和解决方案。

总的来说,Microsoft Graph 提供了一种统一、灵活的方式来访问 Microsoft 365 中的数据和服务,为开发者提供了丰富的功能和资源,帮助他们构建创新的应用程序和解决方案。Microsoft Graph 中的主要服务和功能可以参考官方文档:https://learn.microsoft.com/zh-cn/graph/overview-major-services

2. 统一认证流程及实操验证

微软Graph分为中国版(由上海世纪互联运营)和国际版,两者大致一样,但是也存在一些差异,例如:Portal门户设置UI不同,终结点不同,一些接口的参数和约定不同等,这里以全球版的v1.0进行介绍。

2.1 前置条件

2.1.1 应用程序注册/设置

1、基本设置
在这里插入图片描述
应用程序主要基本设置如上图,其中租户ID(tenant_id)、应用程序ID(client_id)、客户端凭据(client_secret)、重定向URL(redirect_uri)等在后续的授权及微软Graph API调用的时候需要使用。例如:

### 登录授权终结点
https://login.microsoftonline.com/#{tenant}/oauth2/v2.0/authorize?client_id=#{client_id}&response_type=code&redirect_uri=#{redirect_uri}### 授权码获取Token
POST https://login.microsoftonline.com/#{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencodedclient_id=#{client_id}
&scope=User.Read
&redirect_uri=#{redirect_uri}
&grant_type=authorization_code
&client_secret=#{client_secret}
&code=#{auth_code}

2、API权限设置
在这里插入图片描述
1、可以把Microsoft Graph理解为访问Microsoft 365 和 Azure 云端资源的网关,若要访问这些资源则需要用户或管理员向其授予所需的权限。如果权限设置不当,则在实际的访问中就会遇到各种权限相关错误,例如:Authorization_RequestDenied(Insufficient privileges to complete the operation.),因此权限控制是实操过程中经常被提及和困惑的问题。详细可以参考微软官方文档:https://learn.microsoft.com/zh-cn/graph/permissions-reference

2、从上图中的权限类型可以看到有2种:委托和应用程序,委托可以理解为用户认证成功之后的委托授权访问,为了好理解,我把这称之为:AppUser权限(需要用户授权);应用程序则不需要用户授权,而是由管理员授权,为了好理解和区分,可以称之为:AppOnly权限。举个例子:

  • AppUser权限:用户登录认证成功之后返回一个授权码,用这个授权码获得的Token就可以访问该登录用户自己的信息和资源,例如他自己的Profile信息,但是无权访问别人的相关资源。这就是AppUser权限的委派授权。
  • AppOnly权限:自有服务(AppServer)直接请求Azure令牌终结点获取Token,然后用该Token请求微软Graph获取该租户下所有的用户数据。

在这里把开通Directory.Read.All和User.Read.All的AppOnly权限的原因是对于用户数据同步的方案,我们将会选择Delta Query方案。对于用户数据同步的方案微软其实提供了2种,后面会详细讲。

2.2 AppUser方式授权

在这里插入图片描述
在了解了AppUser方式授权和AppOnly授权的差异之后,对于上面的流程图就能很容易的理解了,下面对于一些关键点的开发指导做进一步的描述:

1、AppServer需要向客户端提供一个后去登录认证地址的接口。认证终结点地址可以在Azure应用程序中进行查看,如下图:
在这里插入图片描述
同时,认证终结点还需要携带一些参数,示例如下(相关含义,前面的《应用程序注册/设置》章节已经进行过描述):

https://login.microsoftonline.com/#{tenant}/oauth2/v2.0/authorize?client_id=#{client_id}&response_type=code&redirect_uri=#{redirect_uri}

2、客户端拿到登录认证地址即可进行登录,如下图。一般来说,客户端会使用系统浏览器访问Azure授权终结点,那么会遇到一个问题:此时已不在客户端App的进程之内,那么后续该如何重新回到App,或者如何通知App登录认证完成呢?问题先放着,后面会讲。
在这里插入图片描述

3、登录完成之后Azure授权终结点会向事先配置好的回调终结发送一个POST请求,该请求会包含一个授权码,表示认证通过可以授权。下面演示通过使用Fiddler抓包去查看授权码:
在这里插入图片描述
通过上面的方法,我们可以获得授权码。由于这里只是测试,因此我把回调地址配置成了一个不存在的:https://www.baidu.com/login,因此404,但是这并不影响我们通过用Fiddler抓包去查看授权码。

4、拿到授权码后就可以请求Azure令牌终结点去获取Token了,请求示例如下:

### token[authorization_code]
POST https://login.microsoftonline.com/#{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencodedclient_id=#{client_id}
&scope=User.Read
&redirect_uri=#{redirect_uri}
&grant_type=authorization_code
&client_secret=#{client_secret}
&code=#{auth_code}

应答示例如下:

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"
x-ms-request-id: 803bf994-b432-4ee8-8380-b1e370535b00
x-ms-ests-server: 2.1.17846.6 - SEASLR1 ProdSlices
x-ms-srs: 1.P
X-XSS-Protection: 0
Set-Cookie: fpc=Al_NmhqaTrVPmpaVIt5Vo10V53uDAQAAAD0mtN0OAAAA; expires=Sun, 19-May-2024 08:45:50 GMT; path=/; secure; HttpOnly; SameSite=None
Set-Cookie: x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly
Date: Fri, 19 Apr 2024 08:45:50 GMT
Content-Length: 2340{"token_type": "Bearer","scope": "User.Read profile openid email","expires_in": 5229,"ext_expires_in": 5229,"access_token": "eyJ0eXAiOiJKV1QiLCJub25jZSI6Ik..."
}

需要补充说明的是access_token其实就是一个json的jwt序列化字符串,https://jwt.ms/ 可以进行在线解码:
在这里插入图片描述
其实使用com.auth0.jwt.JWT也可以进行解码,但是并不推荐通过这样的方式去获取用户信息,因为目前尚不明确微软对于算法是如何约定的,通过穷尽尝试几种常用的算法成功解码,假如有天微软把算法变了,程序不就出Bug了吗?这里,只是向告诉大家access_token的本质其实就是一个类似ADFS的授权Claims Json信息,常见的 JWT 签名算法有:

  • HMAC256:使用 HMAC(Hash-based Message Authentication Code)和 SHA-256 算法生成签名。
  • RS256:使用 RSA(Rivest-Shamir-Adleman)加密算法和 SHA-256 哈希算法生成签名,需要使用 RSA 公钥/私钥对进行签名和验证。
  • ES256:使用 ECDSA(Elliptic Curve Digital Signature Algorithm)和 SHA-256 哈希算法生成签名,需要使用 ECDSA 公钥/私钥对进行签名和验证。
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;public class JWTDecoder {public static void main(String[] args) {String jwtToken = "your_jwt_token_here";String secretKey = "your_secret_key_here";try {Algorithm algorithm = Algorithm.HMAC256(secretKey);JWTVerifier verifier = JWT.require(algorithm).build();DecodedJWT decodedJWT = verifier.verify(jwtToken);// 获取 JWT 中的用户 UPNString upn = decodedJWT.getSubject();System.out.println("User UPN: " + upn);} catch (Exception e) {// 处理异常e.printStackTrace();}}
}

5、拿到AccessToken之后就可以请求微软Graph根据业务处理的需要去后去资源了,一般业务场景下都需要首先获得当前登录用户的Profile信息,请求示例如下:

### me
GET https://graph.microsoft.com/v1.0/me
Authorization: Bearer #{access_token}

微软Graph参考资源:

  • https://learn.microsoft.com/zh-cn/graph/api/overview?view=graph-rest-1.0
  • https://developer.microsoft.com/en-us/graph/graph-explorer

6、前面抛出了一个问题:客户端会使用系统浏览器访问Azure授权终结点那么后续该如何重新回到App,或者如何通知App登录认证完成了呢?要解决这个问题其实方案有很多,例如:
1)集成微软SDK;
2)通过服务端输出合适的JS将控制权交给重新交给客户端;
3)重定向到一个中间站点并通过url参数方式传值;
4)将处理结果放在header中;
5)服务端下发通知给客户端(双工App应用,服务端可以向客户端下发通知);
这里我推荐使用方案2,示例JS脚本如下:

<html><header></header><body></body><script language='javascript'>if (window.ad) {window.ad.end(0, 'ok');}</script>
</html>

这段 JavaScript 脚本首先检查当前页面是否存在名为 “ad” 的全局对象或变量。如果存在,它调用 “ad” 对象的 “end” 方法,并传入两个参数:0 和 ‘ok’,其中,0 代表状态码,‘ok’ 为描述(code + msg 表达认证回调的服务端处理结果)。

通过服务端输出合适的JS将控制权交给重新交给客户端,这其实是目前的一种经典做法,因为JS是运行在客户端的Local上。上述JS脚本代码通常在网页中嵌入的广告脚本中使用,当广告成功加载并展示后,广告服务商通常会调用类似的方法来通知页面,以便页面做出相应的处理。

2.3 AppOnly授权

在这里插入图片描述
在了解了AppUser授权的相关内容后,AppOnly的授权就会十分简单了:AppServer直接请求Azure令牌终结点获取Token。当然,这里需要事先在Azure门户中对API权限进行应用程序授权(前面已经介绍过),请求示例如下:

### token[client_credentials]
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencodedclient_id=#{client_id}
&redirect_uri=#{redirect_uri}
&grant_type=client_credentials
&client_secret=#{client_secret}
&scope=https://graph.microsoft.com/.default

应答示例如下(这里贴个实测的图来证明确实可行)。
在这里插入图片描述
最后,大家可以用这种方式下获得的Token去调用微软Graph(当然,要访问的资源需要事先由Azure管理员授权),后面会涉及获取租户下所有的用户数据,用的token就是这种。大家可以比较下与AppUser授权方式获取Token的差异,那么就会发现grant_type和scope的不同。关于scope=https://graph.microsoft.com/.default的说明,大家可以去查阅微软官网。

3. 用户数据同步方案

Azure提供2种变更数据获取方案:订阅(webhook)方案、Delta Query方案,综合利弊建议大家选择Delta Query方案

方案
订阅方案1、对于Azure第三方应用程序服务端实现简单,只需要接收数据变更通知并予以处理即可;1、webhook方式接收方无法自主控制频度和重试(对于订阅方式,Azure 平台会尽力发送数据通知,但并不保证数据的实时性或完整性,在设计应用程序时应考虑到这一点,并实施适当的容错机制)
2、Azure订阅会有费用成本(计费策略因子为:订阅的数量、通知的频率、数据传输量等),而且可以设置费用告警阈值。这些可能产生解释/沟通成本,同时可能会被客户挑战:为什么不使用无费用的Delta Query方案。
3、webhook订阅数据通知只限于增量变化数据的获取,首次全量数据同步仍然需要从微软Graph接口中获取。
Delta Query方案1、可以自主控制数据获取的频度和失败重试。
2、不涉及成本问题的客户挑战。
3、首次全量数据获取与增量数据获取的方案和处理统一(Delta Query接口有类似版本号的可选参数,不携带该参数则为首次全量获取)
1、Azure第三方应用程序服务端实现相对复杂:需要自行考虑频度,重试等问题。同时还需要自行维护数据版本信息以实现增量数据获取。
2、需要一次性对应用程序API权限进行授权(AppOnly授权方式)。

1、Delta Query方案其实是一个轮询方案,那么就会涉及定时任务,当前可选的框架很多:Spring Framework 的任务执行器(TaskExecutor)、Quartz 、XX-Job等。至于说Delta Query的频度,这个大家根据业务需要自行指定(一天一次也好,多少小时分钟一次也好)。

2、Delta Query方案大家需要理解如何实现数据的增量获取,我给大家演示下首次全量数据获取和非首次增量数据获取大家就明白了。

  • 首次全量数据获取
    在这里插入图片描述
  • 非首次增量数据获取
    在这里插入图片描述
    通过对比上面2个请求和应答就可以很容易的发现:

每次Delta Query请求应答都会返回一个deltatoken,其实可以把这个deltatoken理解为资源的版本。那么就很容易的能够理解:首次全量数据获取就不携带deltatoken,要获取某个版本之后的增量数据就携带deltatoken去请求即可。

最后需要说明的是:对于订阅和Delta Query其实是Azure对于很多资源的通用方案,因此不只是users可以使用,其他的资源也可以使用。前面说了,可以把微软Graph理解为访问云端资源的网关,用户是资源,邮件是资源,outlook等都是资源。
在这里插入图片描述

4. 总结

1、要实现Azure AD统一认证与自己业务的系统的集成,一般来说自身业务系统需要实现2个接口即可:1)获取登录认证Url接口(getAuthUrl);2)认证回调处理接口(authCallback);
2、自身业务系统需要维护自身业务系统UID与Azure AD的用户唯一标识(一般为用户的UPN,这也是AD规范中所推荐使用的),可以把这个过程称之为绑定(大家可以参考下IDasS厂商的一些共同做法),对于已经绑定的用户数据就可以支持定时与云端的数据同步。
3、要实现Azure AD用户数据与自身业务系统用户数据的同步,虽然微软提供了2种方案(订阅方案和Delta Query方案),但是结合一般业务场景下的利弊分析后,建议选择Delta Query方案,但是这也不是绝对的,如何选择还得具体问题具体分析。

上述内容调研和折腾了几天的时间,整个过程也都进行了实操验证,而且我也尽量把一些概念、原理也都给予一定的介绍,最后个人认为做调研以下两点非常重要:
1)知其然,知其所以然。
2)一定要上手实操验证。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

苹果电脑装虚拟机好用吗 苹果电脑装虚拟机要钱吗 Parallels对mac的损害 Parallels占用多大空间 PD19

在当今数字化的时代&#xff0c;人们对电脑系统跨设备互联的需求越来越高。作为拥有广泛用户群体的苹果电脑&#xff0c;许多用户会有在Mac系统中运行其他操作系统的需求。在这种情况下&#xff0c;安装虚拟机是一个较好的解决方案。那么接下来就给大家介绍苹果电脑装虚拟机好用…

ChatGPT助力测试领域!探索人工智能编写测试用例的新前景

简介 测试用例是测试人员的核心工作内容&#xff0c;是测试人员思想的“实现类”&#xff0c;其充分体现了测试的思路&#xff0c;可以为后续的测试行为提供指导&#xff0c;是测试人员了解业务的重要根据和质量之根本。如果测试用例设计得不完成&#xff0c;出现了遗漏&#x…

什么是NTFS文件系统 Paragon NTFS与Tuxera NTFS有何区别 paragon ntfs 优惠券

NTFS 英文全称New Technology File System&#xff0c;中文名叫新技术文件系统&#xff0c;是 WindowsNT 环境的文件系统。NTFS是Windows NT家族(如Windows 2000、Windows XP、Windows Vista、Windows 7和 windows 8.1等&#xff09;的限制级专用的文件系统(操作系统所在的盘的…

windows ubuntu 子系统:肿瘤全外篇,bam质控

各个环节的质控&#xff0c; raw和clean都要质控&#xff0c; 比对的各环节的bam文件都要质控&#xff0c; 使用qulima对wes的比对bam文件总结测序深度及覆盖率。 samtools flagstat L1_recalibrated_reads.bam 该命令将输出 BAM 文件的一些统计信息&#xff0c;包括总读取数、…

stm32HAL_GPIO输入

学会使用 GPIO 采集 KEY 的数据信息&#xff0c;这种信息采集技术在生活中常见于对大自 然环境的各种信息的采集。比如环境温度&#xff0c;湿度等等。我们这里以 key 为入门设 备。 一&#xff0c;什么是信息采集 比如环境温度&#xff0c;湿度等等。我们需要把这些温度&am…

Linux thermal框架介绍

RK3568温控 cat /sys/class/thermal/thermal_zone0/temp cat /sys/class/thermal/thermal_zone1/temp cat /sys/class/thermal/cooling_device0/cur_state cat /sys/class/thermal/cooling_device1/cur_state cat /sys/class/thermal/cooling_device2/cur_state thermal_zone…

Cloud微服务:Ribbon负载均衡

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ Ribbon负载均衡 一、Ribbon - 负载均衡原理、流…

Oracle进阶(2)——物化视图案例延伸以及序列、同义词

一、物化视图 物化视图&#xff08;Materialized View&#xff09;是 Oracle 数据库中的一个对象&#xff0c;它是一个预先计算和存储的查询结果集&#xff0c;类似于视图&#xff0c;但与视图不同的是&#xff0c;物化视图会将查询结果保存在物理存储中&#xff0c;而不是动态…

WPF2022终结版系列课程笔记 1 WPF 基本布局

本笔记为B站 微软系列技术教程 WPF项目实战合集(2022终结版) 项目记录 WPF 基本布局 WPF布局原则 一个窗口中只能包含一个元素 不应显示设置元素尺寸 不应使用坐标设置元素的位置 可以嵌套布局容器 WPF布局容器 StackPanel: 水平或垂直排列元素、Orientation属性分别: Hor…

STP学习的第一篇

1.STP的基本概念&#xff1a;根桥 &#xff08;1&#xff09;STP的主要作用之一是在整个交换网络中计算出一棵无环的“树”&#xff08;STP树&#xff09;。 &#xff08;2&#xff09;根桥是一个STP交换网络中的“树根”。 &#xff08;3&#xff09;STP开始工作后&#xf…

K8s: Ingress对象, 创建Ingress控制器, 创建Ingress资源并暴露服务

Ingress对象 1 &#xff09;概述 Ingress 是对集群中服务的外部访问进行管理的 API 对象&#xff0c;典型的访问方式是 HTTPIngress-nginx 本质是网关&#xff0c;当你请求 abc.com/service/a, Ingress 就把对应的地址转发给你&#xff0c;底层运行了一个 nginx但 K8s 为什么不…

OpenCV-基于阴影勾勒的图纸清晰度增强算法

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 实现原理 大家在工作和学习中&#xff0c;无论是写报告还是论文&#xff0c;经常有截图的需求&#xff0c;比如图表、图纸等&…