赞
踩
Azure Active Directory (Azure AD) 是 Microsoft 推出的基于云的全面的标识和访问管理服务,它将核心目录服务、高级标识监管、安全防护和应用程序访问管理相结合,而且还为开发人员提供标识管理平台,以便基于集中的策略和规则为应程序提供访问控制,而且它还可以与本地部署的AD轻松集成,完全支持第三方标识提供程序。它包含的内容如下:
类别 | 说明 |
---|---|
应用程序管理 | 使用应用程序代理、单一登录、“我的应用”门户(也称“访问面板”)和软件即服务 (SaaS) 应用来管理云应用和本地应用。 有关详细信息,请参阅如何提供对本地应用程序的安全远程访问和应用程序管理文档。 |
Authentication | 管理 Azure Active Directory 自助密码重置、多重身份验证、自定义禁止密码列表和智能锁定。 有关详细信息,请参阅 Azure AD 身份验证文档。 |
企业对企业 (B2B) | 管理来宾用户和外部合作伙伴,同时保持对自己公司数据的控制。 有关详细信息,请参阅 Azure Active Directory B2B 文档。 |
企业对客户 (B2C) | 自定义并控制用户在使用应用时如何注册、登录并管理其配置文件。 有关详细信息,请参阅 Azure Active Directory B2C 文档。 |
条件性访问 | 管理对云应用进行的访问。 有关详细信息,请参阅 Azure AD 条件访问文档。 |
针对开发人员的 Azure Active Directory | 生成应用,以便进行所有 Microsoft 标识的登录,以及获取令牌来调用 Microsoft Graph、其他 Microsoft API 或自定义 API。 有关详细信息,请参阅 Microsoft 标识平台(针对开发人员的 Azure Active Directory)。 |
设备管理 | 管理云设备或本地设备访问企业数据的方式。 有关详细信息,请参阅 Azure AD 设备管理文档。 |
域服务 | 在不使用域控制器的情况下将 Azure 虚拟机加入域。 有关详细信息,请参阅 Azure AD 域服务文档。 |
企业用户 | 使用组和管理员角色管理许可证分配、访问应用以及设置委托。 有关详细信息,请参阅 Azure Active Directory 用户管理文档。 |
混合标识 | 使用 Azure Active Directory Connect 和 Connect Health 提供单一用户标识,以便针对所有资源进行身份验证和授权,而不考虑位置(云或本地)。 有关详细信息,请参阅混合标识文档。 |
标识治理 | 通过员工、业务合作伙伴、供应商、服务和应用访问控制管理组织的标识。 还可执行访问评审。 有关详细信息,请参阅 Azure AD 标识治理文档和 Azure AD 访问评审。 |
标识保护 | 检测影响组织标识的潜在漏洞,配置用于响应可疑操作的策略,然后采取相应的解决措施。 有关详细信息,请参阅 Azure AD 标识保护。 |
Azure 资源的托管标识 | 在 Azure AD 中为 Azure 服务提供可以对任何 Azure AD 支持的身份验证服务(包括 Key Vault)进行身份验证的自动托管标识。 有关详细信息,请参阅什么是 Azure 资源的托管标识?。 |
Privileged Identity Management (PIM) | 管理、控制和监视组织内的访问。 此功能包括访问 Azure AD、Azure 和其他 Microsoft Online Services(例如 Office 365 或 Intune)中的资源。 有关详细信息,请参阅 Azure AD Privileged Identity Management。 |
报表和监视 | 了解环境中的安全性和使用模式。 有关详细信息,请参阅 Azure Active Directory 报表和监视。 |
本文包含的内容:(1)应用程序管理 (2)身份验证 (3)Azure AD For Development。阅读本文将会了解:
Azure Active Directory (Azure AD) 为云和本地应用程序提供单一标识系统,以此简化了应用程序的管理方式。 可将软件即服务 (SaaS) 应用程序、本地应用程序和业务线 (LOB) 应用添加到 Azure AD。 然后,用户只需登录一次,即可安全无缝地访问这些应用程序,以及 Microsoft 提供的 Office 365 和其他商务应用程序。通俗点说,就是用户只需要用一个帐号且只需要登录一次就可以访问如下所有的资源。
但上图中可以看出,企业中所有的用户(用户名和密码)、组群、电脑、应用、组织单元等都由Azure AD来统一进行管理。通常只有小公司或初创公司会这么做,但对于一个大公司来说(或者历史原因),企业或组织内部都有本地的目录服务,企业内部都是通过Active Directory进行管理,并设置有单点登录,如下图。
Microsoft Intune 就是手机端的公司门户,通过这个东东来管理你手机的单点登录的。
那如何将企业的资源和Azure公有云结合呢?那需要对本地Active Directory和Azure Active Directory进行整合,实现云上和企业内部统一的单点登录。使用Azure AD Connect可将Azure AD与本地目录集成,它实际上是一个同步服务,将本地目录的信息与Azure AD进行定时同步。这里同步分为4种,如下截图:
下图是在本地服务器上安装Azure AD Connect的截图
下图是在本地服务器上安装ADFS、域服务、域控制器的截图
看下图,在安装Azure AD Connect的时候,Azure AD与ADFS就建立起了信任关系,Azure AD无法对用户身份进行认证,但ADFS可以,ADFS对用户认证通过后将ADFS颁发的Token返回给Azure AD,如果Azure AD判断它是合法有效的,Azure AD将自己颁发的Token返回给用户。
为了进一步的理解认证流程,再贴一张图如下:
有几种方法可以配置应用程序以实现单一登录。 选择哪种单一登录方法取决于为应用程序配置的身份验证方式。
此流程图有助于确定哪种单一登录方法最适合你的情况。点击这里了解更多
但本文这里只重点讲解OAuth.
Jwt(Json Web Token)它是一种基于Json用于安全的信息传输标准,Jwt有两个用途,其一是用于数据交互,因为Jwt是被签名的,可以保证数据的完整性。另外就是用来携带用户信息进行身份验证。
Jwt包含三个部分:
最终三个部分均使用Base64Url的方式进行编码后使用符号“.”进行分隔,以下是一个完整Jwt的例子:
访问理解OAuth 2.0,他写得已经足够好了。
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。
本文这里使用是国内的Azure,由世纪互联运营,选择Azure AD服务后,点击应用注册->新注册
在概述和身份验证中注意如下几点:
应用程序ID(Application id or client id),代表你的应用程序的唯一标识
租户ID(tanent id),类似于你在Azure AD中租用了一个空间,这个空间里可以有很多应用,租户ID就是它的编号。
获取Token只需要一个URL,如下是参数,直接在浏览器中输入下面的URL(注意参数要替换成你注册应用时的参数)
- // 拆开后的参数如下:
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize //在上图中的终节点中可以找到
- ?response_type=token id_token //期望返回id token及access token
- &scope=openid email profile //scope,期望能返回这些claim
- &client_id=83e967a2-4b87-4a5c-aad8-01897aeda929 //即应用程序id
- &redirect_uri=http://localhost:4200 //重定向URL
- &response_mode=fragment //token将会fragment方向附加到重定向URL,请注意,此请求使用 response_mode=fragment(仅用于演示)。 建议使用 response_mode=form_post
- &state=123 //静态值,随便填
- &nonce=456 //静态值,随便填
-
- //直接在浏览器中打开,注意参数要替换成你注册应用时的参数
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize?response_type=token%20id_token&scope=openid%20email%20profile&client_id=83e967a2-4b87-4a5c-aad8-01897aeda929&redirect_uri=http%3A%2F%2Flocalhost%3A4200&response_mode=fragment&state=123&nonce=456
它会跳转到微软的登录界面,输入用户名和密码,然后就可以在URL的后面看到获取的Token了。Token被附加在redirect_uri的后面。
由于我本地并没有端口为4200的程序,当然无法访问此页面,但不影响我拿到Token。
这里有两个注意事项:
将URL中的内容复制并整理,内容如下:
- http://localhost:4200/#
- access_token=eyJ0eXAiOiJKV1QiLCJub25jZSI6IjRkcVg3Mm5Nek1LNEZQTTJhZUtwaTJsbzF1a1A5STExVW5WMWNKQWYzTHciLCJhbGciOiJSUzI1NiIsIng1dCI6IkRuWEFmelc4Mk8wbHd0bjVQcjdkbjBjbkxXdyIsImtpZCI6IkRuWEFmelc4Mk8wbHd0bjVQcjdkbjBjbkxXdyJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy5jaGluYWNsb3VkYXBpLmNuL2EwOWZhOTk5LWY4NzYtNGY0Zi04OGRkLThhMTk4M2MwNjZkYS8iLCJpYXQiOjE1ODkwMjA3NzEsIm5iZiI6MTU4OTAyMDc3MSwiZXhwIjoxNTg5MDI0NjcxLCJhY3IiOiIxIiwiYWlvIjoiQVNRQTIvOEhBQUFBT3JzeTgxSk5vS0NmM29IL2V0THJ3U0FPV1lLa1laRll4YVVjdDMzQ0JoOD0iLCJhbXIiOlsicHdkIl0sImFwcF9kaXNwbGF5bmFtZSI6Ik15RnJvbnRlbmRBcHAiLCJhcHBpZCI6IjgzZTk2N2EyLTRiODctNGE1Yy1hYWQ4LTAxODk3YWVkYTkyOSIsImFwcGlkYWNyIjoiMCIsImZhbWlseV9uYW1lIjoiQ29uZyIsImdpdmVuX25hbWUiOiJXdSIsImlwYWRkciI6IjExNi4yMzguMjQ0LjI2IiwibmFtZSI6Ild1IENvbmciLCJvaWQiOiI0YjhmZDEzMS0zMDlkLTQwZjAtOWY3Zi01MWFlMDY5NWUzZDIiLCJwbGF0ZiI6IjMiLCJwdWlkIjoiMjAwM0JGRkQ4MTZCODIyQiIsInNjcCI6Im9wZW5pZCBwcm9maWxlIFVzZXIuUmVhZCBlbWFpbCIsInNpZ25pbl9zdGF0ZSI6WyJrbXNpIl0sInN1YiI6IlBJYl9tbzd2bTRKNmU0X214ZUhpakZVUks3cmd6V19sSUxNU2pvcTJIOVkiLCJ0aWQiOiJhMDlmYTk5OS1mODc2LTRmNGYtODhkZC04YTE5ODNjMDY2ZGEiLCJ1bmlxdWVfbmFtZSI6Ind1Y29uZzYwQHd1Y29uZy5wYXJ0bmVyLm9ubXNjaGluYS5jbiIsInVwbiI6Ind1Y29uZzYwQHd1Y29uZy5wYXJ0bmVyLm9ubXNjaGluYS5jbiIsInV0aSI6Ik5FOXgxZFVKWjAyenJadlJqSzRjQUEiLCJ2ZXIiOiIxLjAiLCJ4bXNfc3QiOnsic3ViIjoiRzdXUGNreFpTNFMxbXZBX3FTM25uaHVRTlJBaDAwNFM2Umd6NkRmZkhnOCJ9LCJ4bXNfdGNkdCI6MTUxNDI1NzQ4Nn0.A9XQL7ogpLRo_b-jr9xn7WPXztoCFnV59MGDc3ni3PnfF2vOKUuxLC5qYFArCJdLD-TgYHBd9oR34tMyveUOqLpsNQnVT8h-EYxBKvCHWyWNZvt3C0k2wWEn7ZZcGSd8PIv7YSVKPsB9rFtSw44f97KXjw4mWVaHy5ZWrbbTG4EtCY3eM4hKwaHFYE-0pAJoES4AyyC9G-aANaSH2jTuUNpdTll7PJY3Too2m3x06e-65x63D01FgtB4uuckQXuK6MteNaggTT1DZfg-tCWaTrnnXhU-7bY8JpLxsiyi1RV1M0Q7xhbfxajHPIght7HZ3lRQ9g82HGmUF6AbQDUtxw
- &token_type=Bearer
- &expires_in=3599
- &scope=openid profile email 00000003-0000-0000-c000-000000000000/User.Read
- &id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkRuWEFmelc4Mk8wbHd0bjVQcjdkbjBjbkxXdyJ9.eyJhdWQiOiI4M2U5NjdhMi00Yjg3LTRhNWMtYWFkOC0wMTg5N2FlZGE5MjkiLCJpc3MiOiJodHRwczovL2xvZ2luLnBhcnRuZXIubWljcm9zb2Z0b25saW5lLmNuL2EwOWZhOTk5LWY4NzYtNGY0Zi04OGRkLThhMTk4M2MwNjZkYS92Mi4wIiwiaWF0IjoxNTg5MDIwNzcxLCJuYmYiOjE1ODkwMjA3NzEsImV4cCI6MTU4OTAyNDY3MSwiYWlvIjoiQVRRQXkvOEhBQUFBNGdHYzlXUnE2VXpIWE1ZcVRwUkJya1o1L1dwTVlKQUprMDNZaWZxSDFyMWRTWnJwTFVFdmg4dE8vREV0eEVYOSIsImF0X2hhc2giOiJ0cWhRUjdDbktoQWI2bG5UTHJOb2lBIiwibmFtZSI6Ild1IENvbmciLCJub25jZSI6IjQ1NiIsIm9pZCI6IjRiOGZkMTMxLTMwOWQtNDBmMC05ZjdmLTUxYWUwNjk1ZTNkMiIsInByZWZlcnJlZF91c2VybmFtZSI6Ind1Y29uZzYwQHd1Y29uZy5wYXJ0bmVyLm9ubXNjaGluYS5jbiIsInN1YiI6Ikc3V1Bja3haUzRTMW12QV9xUzNubmh1UU5SQWgwMDRTNlJnejZEZmZIZzgiLCJ0aWQiOiJhMDlmYTk5OS1mODc2LTRmNGYtODhkZC04YTE5ODNjMDY2ZGEiLCJ1dGkiOiJORTl4MWRVSlowMnpyWnZSaks0Y0FBIiwidmVyIjoiMi4wIn0.6l3FGgFH7RLV8hNTgghEGbSauHnnVq7AVnFQAQpnMXelRse_GxtQq1OiMEJFZ5cARMrsjkjrqMlIGNUlgg88U9MnhGKjwv49lchYaAboxxS8Z7QDihpr6E01qTS8c-3TilYuft8M4Kc-5sbsbxgeLYXHYil5eZWfd-lQRbRUEWNjv7j68rEPJQmYLVReQyUFNb1u5YLTXAuiwg7fhrNbQC6mtTWY1EVTuaVl6jLpaZlQVsbsuy5ojBi_8jhY_bTin-DLFx_-eCpAFISEczrHy0ek9loHQ6hjNBTvCeQ-mOFEnxW_UoETnsFgpOylaF-BkvrgbL_cKrZwJLJ5EnwlNg
- &state=123
- &session_state=71002d76-a94d-4943-ad25-22e86ac9bc70
可以看到,不仅拿到了id_token,也拿到了access token。我们对token进行解析,力推工具:https://jwt.ms/
这里有3个注意事项:
microsoft graph(国内版)本身微软内置的应用程序,这里使用Postman调用。https://graph.microsoft.com/v1.0/me
将Token解析后可以看到iss(issue),值为https://sts.chinacloudapi.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/,我们在它的后面加上.well-known/openid-configuration,最终的值为:
https://sts.chinacloudapi.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/.well-known/openid-configuration
在浏览器中直接访问此网站,得到的内容如下:
点开此链接如下:
此链接就是公钥的链接。 RSA通常用于非对称加密,这里有两把密钥(公钥和私钥),公钥就在上图中,全网都可以访问,私钥放在Azure AD中,归Azure AD拥有,其他人不可见。如果是公钥加密,私钥来解密,那它叫加密;如果是私钥加密,公钥来解密,那它叫签名。所以Azure AD使用私钥来生成Token,后台API拿到公钥对Token进行解密,token中的第三部分就是属于签名。
后台API对此Token进行解密并不需要将Token发回给Azure AD进行解密,那如何对这个Token进行验证呢?
第一步:Token中第一部分中,可以看到kid="DnXAfzW82O0lwtn5Pr7dn0cnLWw" 和 alg="RS256",分别表示公钥的id及算法, 这里我们使用上面三把公钥中的第一把。
第二步:有了公钥、算法、就可以对此Token进行解密了,解密后的result内容就是token中的payload。尝试着阅读如下代码即可了解它是如何解密Token的,但通常我们不需要自己写代码,已经有很多第三方库帮我们实现好了。
- import jwt
- import json
- import urllib.request
- from jwt.algorithms import get_default_algorithms
-
-
- response = urllib.request.urlopen('https://login.chinacloudapi.cn/common/discovery/keys')
- key_json = json.dumps(json.loads(response.read())['keys'][0])
-
- def token_verify(token):
- token = token.replace('Bearer ', '')
- rsa = get_default_algorithms()['RS256']
- cert = rsa.from_jwk(key_json)
- try:
- result = jwt.decode(token, cert, algorithms=['RS256'], audience='00000003-0000-0000-c000-000000000000')
- print(result)
- return result
- except jwt.DecodeError:
- return False

Azure中点击右上角你的头像可以切换【目录】,一个【目录】下包含多个【订阅】,一个【订阅】下包含多个【资源组】,一个【资源组】中包含多个【资源】,你创建的虚拟机、数据库等都是属于某个【资源组】下。微软是按【订阅】来收费用的,你需要单独付费才能购买【订阅】的。
Azure中几乎所有的服务都是存在于某一个【资源组】(resource group)下,除了Azure AD之外。Azure AD可以切换【目录】,如下图横框所示。
介绍一下左边的菜单:
(1)用户和组
对用户和组里的管理,这里简单说一下【多重身份验证】(MFA),点击【多重身份验证】按钮,对此用户启用多重身份验证后,用户登录的时候不仅需要输入密码,而且还需要对手机号(如果你设置的话)进行验证,这里说的多重实际上就是两重。
启用多重身份验证后,用户输入密码登录后,还需要输入手机号的截图。
(2)企业应用程序
如果想使用同一个帐号访问企业内部应用程序或外部应用程序,可以在这里新增应用程序,前提是这些应用程序必须支持AAD验证。下图可以看出我们可以使用同一个帐号登录OneDrive或OneNote 应用。
(3)应用注册
这项目是本文介绍的重点,当企业创建的新应用程序希望与AAD集成时,那我们必须在这里注册我们的应用程序,下一节将重点介绍它。
(4)Azure AD Connect
实现本地域服务与Azure AD定时同步,联合身份验证服务(ADFS) 实现本地Active Directory和Azure Active Directory进行整合,实现云上和企业内部统一的单点登录。
(5)自定义域名
当我们创建一个租户的时候,Azure AD就已经默认帮我们创建了一个可用的域名,这里是以.partner.onmschina.cn结尾的,但通常在企业中需要使用企业自定义的域名,在这里可以添加新的域名。
创建完成后,Azure AD需要对此域名进行验证,确认这个域名是否归你所有,所以我们还需要在购买域名的服务提供商中域名解析的地方填写如上内容,这里记录内类填入是使用TXT, 下图是使用阿里的域名解析界面。
点击验证按钮后,此域名的状态会显示成已验证。
之后我们就可以使用新的域名了,我们创建一个新用户试试。
这样我们就有了两个帐号了,上图中是第二个帐号是我的主帐号,它具有全局管理员(最大的)的权限, 刚才我们创建了一个新用户,我们可以对他分配一些必要的角色,那以后我们就可以只使用此帐号进行后续的操作。
进入Azure AD,点击【应用注册】,创建两个应用,命名为MyFrontendApp和MyBackendApp
前端应用将会使用隐含模块(Implict type) 从Azure AD中获取Token,然后向后端API发起请求获取数据。 这里会涉及到两个问题,一是前端如何获取Token,后端如何对Token进行认证。
先进入MyBackendApp后端应用,添加任意scope(scope名随便定义),那此应用的API将会被公开(暴露),我们这里添加了两个scope(读和写)。
MyFrontendApp应用中,点击【添加权限】->【委托的权限】来添加下面绿框架中的两个权限,管理员【同意】(Consent)后,前端应用就拥有调用后端API的权限了,包含读和写。
注意前端的重定向URL是http://localhost:4200,access token和id token都已启用,因为我们是单页应用。
对于Azure门户的操作讲到这里,具体的代码可以参考这里。
在上图中添加权限的时候,可以选择两种不同的权限【委托的权限】和【应用程序权限】, 分别对应【带用户信息的token】和【不带用户信息的token】,对比一下如下两张图
对于一个前后端分离的项目,它本是属于同一项目,有必要像上面一样创建两个应用吗?
答:对于前后端分离的项目,你可以只需要创建一个项目,也可以创建两个项目,都可行得通。因为你既可以把它当作同一个项目来看,也可以当作两个项目来看。但笔者较懒习惯于只创建一个项目,这里创建两个是为了方便演示,原因有二:一是为了向读者清晰地展示应用是如何暴露API及授权的。二是如果将前后端两项目共用一个MyFrontendApp应用的话,那MyBackendApp可当作第三方应用,方便向读者展示如何调用第三方应用的API。
上面两个应用中我只使用MyFrontendApp,前后端共用同一个Client id
(1) 前端获取token
前端项目中我们一般采用微软的msdl及adal(旧的)框架来获取token,当前端要访问后端的资源之前,不再需要去Azure AD取Token了,因为在你登录的时候,这个token和id token已经返回给你了,并且存储在浏览器的缓存中,为了更加清楚地解释它是如何获取aud指向后端应用的token,贴出如下链接
获取Token只需要一个URL,如下是参数,直接在浏览器中输入下面的URL(注意参数要替换成你注册应用时的参数)
- // 拆开后的参数如下:
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize //在上图中的终节点中可以找到
- ?response_type=token id_token //期望返回id token及access token
- &scope=openid 83e967a2-4b87-4a5c-aad8-01897aeda929/.default
- &client_id=83e967a2-4b87-4a5c-aad8-01897aeda929 //即应用程序id
- &redirect_uri=http://localhost:4200 //重定向URL
- &response_mode=fragment //token将会fragment方向附加到重定向URL,请注意,此请求使用 response_mode=fragment(仅用于演示)。 建议使用 response_mode=form_post
- &state=123 //静态值,随便填
- &nonce=456 //静态值,随便填
-
- //直接在浏览器中打开,注意参数要替换成你注册应用时的参数
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize?response_type=token%20id_token&scope=openid%2083e967a2-4b87-4a5c-aad8-01897aeda929%2F.default&client_id=83e967a2-4b87-4a5c-aad8-01897aeda929&redirect_uri=http%3A%2F%2Flocalhost%3A4200&response_mode=fragment&state=123&nonce=456
注意这里的scope,如果scope只包含openid,那获取得token对应的aud=00000003-0000-0000-c000-000000000000,即国内版graph,如果scope除了openid,还包含其他scope(如:83e967a2-4b87-4a5c-aad8-01897aeda929/.default),那获取得token对应的aud=83e967a2-4b87-4a5c-aad8-01897aeda929
(2) 后端验证token
这里将后端API(以dotnetcore项目为例)的配置信息贴出来如下,这里ClientId设置为83e967a2-4b87-4a5c-aad8-01897aeda929,表示后台只接受aud=83e967a2-4b87-4a5c-aad8-01897aeda929的Token才能调用本API
- "AzureAd": {
- "Instance": "https://login.partner.microsoftonline.cn/",
- "ClientId": "83e967a2-4b87-4a5c-aad8-01897aeda929",
- "Domain": "wucong.partner.onmschina.cn",
- "TenantId": "a09fa999-f876-4f4f-88dd-8a1983c066da"
- }
上面两个应用程序的Client Id如下:
(1) 前端获取token
前端项目中我们一般采用微软的msdl及adal(旧的)框架来获取token,当前端要访问后端的资源之前,前端会以静默(silent)的方式(即以一个hidden frame)向Azure AD取得aud=4bf9068a-6653-4550-920d-5fa61e332af3的Token,为了更加清楚地解释它是如何获取aud指向后端应用的token,贴出如下链接
获取Token只需要一个URL,如下是参数,直接在浏览器中输入下面的URL(注意参数要替换成你注册应用时的参数)
- // 拆开后的参数如下:
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize //在上图中的终节点中可以找到
- ?response_type=token id_token //期望返回id token及access token
- &scope=openid 4bf9068a-6653-4550-920d-5fa61e332af3/.default
- &client_id=83e967a2-4b87-4a5c-aad8-01897aeda929 //即应用程序id
- &redirect_uri=http://localhost:4200 //重定向URL
- &response_mode=fragment //token将会fragment方向附加到重定向URL,请注意,此请求使用 response_mode=fragment(仅用于演示)。 建议使用 response_mode=form_post
- &state=123 //静态值,随便填
- &nonce=456 //静态值,随便填
-
- //直接在浏览器中打开,注意参数要替换成你注册应用时的参数
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize?response_type=token%20id_token&scope=openid%204bf9068a-6653-4550-920d-5fa61e332af3%2F.default&client_id=83e967a2-4b87-4a5c-aad8-01897aeda929&redirect_uri=http%3A%2F%2Flocalhost%3A4200&response_mode=fragment&state=123&nonce=456
注意这里的scope,包含后台API的scope(如:4bf9068a-6653-4550-920d-5fa61e332af3/.default),那获取得token对应的aud=4bf9068a-6653-4550-920d-5fa61e332af3
(2) 后端验证token
这里将后端API(dotnetcore项目)的配置信息贴出来如下,这里ClientId设置为4bf9068a-6653-4550-920d-5fa61e332af3,表示后台只接受aud=4bf9068a-6653-4550-920d-5fa61e332af3的Token才能调用本API
- "AzureAd": {
- "Instance": "https://login.partner.microsoftonline.cn/",
- "ClientId": "4bf9068a-6653-4550-920d-5fa61e332af3",
- "Domain": "wucong.partner.onmschina.cn",
- "TenantId": "a09fa999-f876-4f4f-88dd-8a1983c066da"
- }
后端接收Token后,需要处理的内容:
(1)验证token,包含aud的验证,aud是可以有三种形式:
(2)既然有两种类型的token,token中既可能带有scp,也可能带有roles,后台对这两种权限都需要作处理
但这里不得不将一些问题进行澄清。
(1)前端单页应用可以调用第三方的API吗?
答:当然可以。当前端要访问后端或第三方的资源之前,前端会以静默(silent)的方式向Azure AD取得aud=<第三方API的client id>的Token,然后再调用第三方API。 如果将前后端两项目共用一个MyFrontendApp应用的话,就不再需要去Azure AD取Token了,因为在你登录的时候,这个token和id token已经返回给你了,并且存储在浏览器的缓存中。
(2)前端单页应用可以调用第三方的API时,权限是如何传递的?
答:通过scope, aud指向第三方API的client id, scp包含权限,第三方API拿到此Token后先对aud进行认证,再通过scp进行授权。
(3)后台API可以调用第三方的API吗?
答:当然可以。一般通过grant_type=client_credentials的方式获取token(不带用户信息)。
(4)后台API可以调用第三方的API时,权限是如何传递的?
答:有两种方式:第一种通过roles, 后台API以grant_type=client_credentials的方式获取token,token(不带用户信息)中包含roles。第二种通过scope,前端将带有用户信息的token(命名为token1)发送给后台API,而后台API会调用第三方API,后台API将token1作为参数输入,以代理流方式请求新的token(命名为token2),这样后台API就可以拿着token2向第三方API发起请求。注意:拿token1不能直接调用第三方API,因为aud不匹配,token1带有用户信息;拿token2可以调用第三方API,token2带有用户信息。更多信息请参考第七章第七节。
适用于[桌面应用、移动应用、Web应用(带服务端的那种)]。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动,由于单页应用没有后台服务器,所以它不适用于单页应用。
几乎大部分手机APP都是使用授权码模式,token有效时间通常最多1小时,而refresh_token的有效时间可以长达至少半年,这也是为什么你的手机应用几乎只需要登录一次,而后不需要再登录了,原因就是授权码模式可以获取refresh_token,它将refresh_token存储在手机中或者文件里,通过refresh_token来换取token。但单页应用是没有refresh_token的,因为将refresh_token存在浏览器端是不安全的,用户在浏览器端是可以看到token的,有效时间短,当token失效时,用户是需要重新登录的。
我们以上面的MyFrontendApp为例,展示一下如何获取token,步骤如下:
第一步:获取授权码.此链接在浏览器打开
- //拆开后的URL
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize
- ?response_type=code //code表示是授权码模式中先获取code
- &scope=openid email profile
- &client_id=83e967a2-4b87-4a5c-aad8-01897aeda929
- &redirect_uri=http://localhost:4200
- &response_mode=query
- &state=123
- &nonce=456
-
- //使用如下链接在浏览器中打开
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize?client_id=83e967a2-4b87-4a5c-aad8-01897aeda929&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A4200&response_mode=query&scope=openid&state=12345
code将会以query的形式展示在url中。
格式化的的结果如下:
- http://localhost:4200/?code=OAQABAAIAAABTYj0VqP05TZ6xV9yGkWlGZ0W1apgtlDVDxiIo9s_pTBhQ0lFDdKRNZqF-QAfwuVuMDTtKgPbPLlBkY9rqVoQ88X--dMm5EjvQgSs5jWxD_6TQ4YhsKAof7mDx2plZoAZLpH-IqMKDA3WU_9v47RyADPgnFW71ZxR_c6TeElPzs9q8RX_4HOm40J1BpCtjkQtn_rjsp8lGjB4YHsX-T6TecnnwkjD4gVS3GiAD_F2IaBjanADl55tXLRDElvW_B2k7S1HRqFbaqMMzcDCox9fj80YdjZuDq8_RwdTNZyC5U0uthQMEhYLtPKJmTBg4xsU5HAvmpTs6-7tL6rEo246tqWC0ko7Tm1T0-yHzSkw0TzIcbc5lXMApD2kwDLTGYV7wZ95oaGTnaQixHsdxpi-THUMoeUmFzpVz_JZCmegaO-8vlN_U6BLAXgQtiIBjsIyon0iDCQAghWFm2dmC1QVjOJZDgyIzqM_88BVKv3tbDqZwmv-sIMt__cklsT_7MRvfHfs0iWY3weMizetrVGxhtnW4JZLUlSqj-c1B0Y4yIoP1sd87WuxD1j5gcxCgS3UyavWxPgZOgrTDloWO3VGWIAA
- &state=12345
- &session_state=71002d76-a94d-4943-ad25-22e86ac9bc70
第二步:创建一个客户端密码(client secret)
第三步: 获取授权码code用一次就会失效,有效时间约10分钟。这里用Postman打开,如下,可以看到我们可以拿到Token了。
注意:这一步是通过客户端的后台服务器,与Azure AD("服务提供商",也叫idp)的认证服务器进行互动,也就是说对于用户在浏览器端是看不到token。
这里是以代码形式展示请求的参数,方便读者copy
- POST https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/token
- Content-Type: application/x-www-form-urlencoded
-
- client_id=<your client_id>
- &scope=openid // 在v2.0中必须要指定scope, openid 表示默认生成aud为graph(国内版)的token
- &grant_type=authorization_code
- &redirect_uri=<your redirect_uri>
- &client_secret=<your client_secret>
- &code=<your code>
最后生成的Token解析出来如下:
这里有一个疑问,为什么没有refresh_token呢,是因为我们没在scope中指定offline_access, 参考这里
下图是获取到refresh_token的Postman截图,由于授权码只能使用一次,所以我需要重新执行上述第一步和第三步操作。
这里是以代码形式展示请求的参数,方便读者copy
- POST https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/token
- Content-Type: application/x-www-form-urlencoded
-
- client_id=83e967a2-4b87-4a5c-aad8-01897aeda929
- &scope=openid offline_access
- &grant_type=authorization_code
- &redirect_uri=http://localhost:4200
- &client_secret=<client_secret>
- code=<code>
我们直接拿上面的refresh token进行这一步操作,它是通过refresh token来换取新的access token,这里scope是可选的,如果你指定了scope,那它就会生成【委托的权限】的token,即包含用户信息及scp,如果你不指定scope,那它就会生成【应用程序权限】的Token,即不包含用信息但包含roles的token。
这里是以代码形式展示请求的参数,方便读者copy
- POST https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/token
- Content-Type: application/x-www-form-urlencoded
-
- grant_type=refresh_token
- &refresh_token=<refresh_token>
- &client_id=83e967a2-4b87-4a5c-aad8-01897aeda929
- &client_secret=<client_secret>
- &scope=openid
生成出来的Token并解析,如下图所示:
第六节中已经介绍了单页应用,它就是使用的简化模式,这里就不在赘述了。
它是通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
这里也不介绍,我们用得不多
服务与服务之间相互调用就需要用到此模式,比如:两个后台的API 与API之间的调用。
我们以上面的MyFrontendApp和MyBackendApp为例,展示一下如何获取token。如下图可以看使用是使用grant_type=client_credentials
这里是以代码形式展示请求的参数,方便读者copy
- POST https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/token
- Content-Type: application/x-www-form-urlencoded
-
- grant_type=client_credentials
- &client_id=83e967a2-4b87-4a5c-aad8-01897aeda929
- &client_secret=gEZLk4]@Wj]Bq.yGXMQAHiOLwCvaf135
- &scope=4bf9068a-6653-4550-920d-5fa61e332af3/.default
为什么使用/.default, 参考MSAL JS与ADAL JS的差异
Token解析出来的结果如下,它的aud=4bf9068a-6653-4550-920d-5fa61e332af3,是指向MyBackendApp的client id的。
它是可以通过后端的认证,但是通不过授权,因为它没有roles这个claim,我们需要给它添加【应用程序权限】,下图是其他应用的一个截图,因为【应用程序权限】需要全局管理员的权限才能设置,下图绿框中的就类型就属于应用程序权限。
一旦加上了【应用程序权限】后,再进行上述grant_type=client_credentials的请求就可以拿到Token,结果如下图:
它适用于WebApplication这样的应用,即网页和API混合在一起,且有后台服务器的那种,比如DotNet中的MVC。由于现在用得比较少,我这里贴上链接供大家参考。
当然本文也提供了相应例子供读者参考,点击这里访问源码中,DotNetCore3-WebApp-OpenIdConnect文件夹就是例子。
假定已在应用程序中使用 OAuth 2.0 授权代码授权流或其他登录流对用户进行身份验证。 此时,应用程序有一个访问令牌, 其中包含用户的声明,并同意访问中间层 web API (API a)。 现在,API A 需要向下游 Web API (API B) 发出经过身份验证的请求。点击这里访问更详细的说明。
接下来的步骤构成了 OBO 流,并在下图中进行说明。
aud
声明)向 API A 发出请求。在此方案中,中间层服务无需用户干预,就要获取用户对访问下游 API 的许可。 因此,在身份验证过程的同意步骤中会提前显示授权访问下游 API 的选项。 若要了解如何为应用设置此选项,请参阅为中间层应用程序获得同意。
我们以上面的MyFrontendApp、MyBackendApp和Microsoft Graph API为例,这里我们就不另创建第三个应用了,我们以Microsoft Graph API作为第三方API。基本流程如下:
下面将展示1、2(不演示)、3、4步。
第一步:直接访问一个URL获取Token
- // 拆开后的参数如下:
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize //在上图中的终节点中可以找到
- ?response_type=token id_token //期望返回id token及access token
- &scope=openid 4bf9068a-6653-4550-920d-5fa61e332af3/backend.read //scope,注意最后一个scope
- &client_id=83e967a2-4b87-4a5c-aad8-01897aeda929 //即应用程序id
- &redirect_uri=http://localhost:4200 //重定向URL
- &response_mode=fragment //token将会fragment方向附加到重定向URL,请注意,此请求使用 response_mode=fragment(仅用于演示)。 建议使用 response_mode=form_post
- &state=123 //静态值,随便填
- &nonce=456 //静态值,随便填
-
- //直接在浏览器中打开,注意参数要替换成你注册应用时的参数
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize?response_type=token%20id_token&scope=openid%204bf9068a-6653-4550-920d-5fa61e332af3%2Fbackend.read&client_id=83e967a2-4b87-4a5c-aad8-01897aeda929&redirect_uri=http%3A%2F%2Flocalhost%3A4200&response_mode=fragment&state=123&nonce=456
拿到Token_1并解析如下,可以看出aud=4bf9068a-6653-4550-920d-5fa61e332af3, 是后台MyBackendApp的Client Id,它带scp且包含用户身份的token。
第二步:使用此Token_1发起请求调用后台MyBackendApp的API,这里就不演示了。
第三步:在后台MyBackendApp的API中,此API 想继续使用Token_1调用第三方的API是会抛401未授权的错误,因为只有token中的aud指向第三方API都能call通。所以我们需要拿Token_1换取能访问第三方API的Token_2,如下是Postman的截图
这里是以代码形式展示请求的参数,方便读者copy
- POST https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/token
- Content-Type: application/x-www-form-urlencoded
-
- grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer // 固定值
- &client_id=4bf9068a-6653-4550-920d-5fa61e332af3 // 后台MyBackendApp的client id
- &client_secret=hur9_rrYFI]T98L4]wgrdHlVsTc@Gycp // 后台MyBackendApp的client secret
- &scope=00000003-0000-0000-c000-000000000000/.default //第三方API的appid or app id url
- &requested_token_use=on_behalf_of // 固定值
- &assertion=eyJ0eXAiOiJKV1QiLCXXXXXX // 从前台得到的token
将Token_2进行解析如下
可以看出aud=00000003-0000-0000-c000-000000000000是graph(国内版)的app id,那我们可以使用此token访问graph(国内版)的api
这里是以代码形式展示请求的参数,方便读者copy
- GET https://microsoftgraph.chinacloudapi.cn/v1.0/me
- authorization: Bearer eyJ0eXAiXXXXX
其实我们还可以使用证书访问令牌请求,更多信息请点击这里
可以看出,代理流模式与客户端模式(Client Credentials Grant)的最大差异在于代理流模式中的token是包含用户信息的,而后者不包含。
如下图,可以看到这里有两个版本(v1.0和v2.0),无论使用哪一个版本都是可以正常使用的,但版本之间的差异可能会给用户带来不少的困难,虽然官网推荐大家使用V2.0,但依然有不少企业依然使用V1.0。
本文上述所有的章节都是以V2.0来讲解的,授权和令牌都是使用如下两个endpoint.
但如果用户使用V1的话,那传参的内容中的所有的scope都得换成resource,这里只举其中一个客户授权模式,如下两张图对比一下差异,至于其他模式也是需要scope都得换成resource,这里就不再演示了。
解析Token发现他们的aud都指向graph(国内版)的app id.
想查看更多差异,请点击这里
通常app id(即Client id)它是一个GUID,一串长长的编号,用户不便于记忆,所以我们可以给此应用设置一个App id Url,如下图:
如果此应用有自己的域名,可以设置成这样:https://example.com
我们将客户授权模式下的scope改成https://microsoftgraph.chinacloudapi.cn/.default
然后解析它的Token如下, 可以发现在它的aud=https://microsoftgraph.chinacloudapi.cn 指向得是graph app id url, 之前aud=00000003-0000-0000-c000-000000000000 指向得是graph app id。 无论我们使用哪一种aud,都能正确地返回结果,因为在graph api中已经处理好了这种情况,但对我们自定义的API,要想开发给其他应用使用,我们需要处理这种情况。
在上述章节中token中payload里面的信息是固定的,有时候我们希望token里面包含更多的信息,比如:groups,程序通过此groups判断是否拥有相应的权限,如果返回的token中的groups中包含类似于"cong.secrity.group.demo",那此用户将有管理员的权限。 当然它的功能不仅限于此,主要包含如下:
本文这里只讲第一个,接下面我将新增两个optional claims,并在token中显示。
第一步: 在应用MyFrontendApp(client id: 83e967a2-4b87-4a5c-aad8-01897aeda929)配置两个新增的claims: ctry(用户所在的国家) 和groups(用户所在的组), 这样,在id_token的payload中包含ctry和groups,在access_token的payload中包含groups。
配置完成后,我们可以在manifest中看中看到如下json对象
第二步:创建一个Group,成员包含我,注意这里的group id是c160ac77-a368-4193-801e-201643746ee9
使用隐含模块访问如下链接,获取token
- // 拆开后的参数如下:
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize //在上图中的终节点中可以找到
- ?response_type=token id_token //期望返回id token及access token
- &scope=openid 83e967a2-4b87-4a5c-aad8-01897aeda929/.default
- &client_id=83e967a2-4b87-4a5c-aad8-01897aeda929 //即应用程序id
- &redirect_uri=http://localhost:4200 //重定向URL
- &response_mode=fragment //token将会fragment方向附加到重定向URL,请注意,此请求使用 response_mode=fragment(仅用于演示)。 建议使用 response_mode=form_post
- &state=123 //静态值,随便填
- &nonce=456 //静态值,随便填
-
- //直接在浏览器中打开,注意参数要替换成你注册应用时的参数
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize?response_type=token%20id_token&scope=openid%2083e967a2-4b87-4a5c-aad8-01897aeda929%2F.default&client_id=83e967a2-4b87-4a5c-aad8-01897aeda929&redirect_uri=http%3A%2F%2Flocalhost%3A4200&response_mode=fragment&state=123&nonce=456
注意这里面的scope 必须包含指向client id: 83e967a2-4b87-4a5c-aad8-01897aeda929的scope, 即83e967a2-4b87-4a5c-aad8-01897aeda929/.default,原因如下,或者参考链接, 如果不包含此scope,那获取的access token是指向graph API的,此API可没有配置ctry 和 groups。
我们将access token进行解析如下:
可以看出它是带用户信息的access token,包含groups claims,因为我们在token配置中的access里只包含groups,可以看到第二个group的id就是我们刚创建的group。
接下来,我们尝试在使用Open ID Connect的方式来获取用户信息(参考源码),获取User的信息,它里面的Claims里面是包含ctry 和 groups。
我们创建的组名可能类似于"cong.test.admin"或"cong.test.read",应用程序通过组名来授权,但token只返回了group id,有没有办法返回group name呢?
这里token是不能直接返回group name的,笔者建议如果组名不是强制要用的话,可以直接将group id写入配置文件, 通过group id来授权。
如果你有其它目的想使用group name的话,我们只能通过Graph API来获取。步骤如下:
第一步:在应用MyFrontendApp(client id: 83e967a2-4b87-4a5c-aad8-01897aeda929)授权访问Graph API的组的权限(下图是管理员未授权,登录的时候,AAD将会弹出一个consent界面要求用户授权)
第二步:在浏览器中访问如下网站,在consent页中,用户点击“同意授予”后,在url的fragment上获取访问Graph API的token,注意这里的token中payload里是不包含groups的
- // 拆开后的参数如下:
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize //在上图中的终节点中可以找到
- ?response_type=token id_token //期望返回id token及access token
- &scope=openid Group.Read.All
- &client_id=83e967a2-4b87-4a5c-aad8-01897aeda929 //即应用程序id
- &redirect_uri=http://localhost:4200 //重定向URL
- &response_mode=fragment //token将会fragment方向附加到重定向URL,请注意,此请求使用 response_mode=fragment(仅用于演示)。 建议使用 response_mode=form_post
- &state=123 //静态值,随便填
- &nonce=456 //静态值,随便填
-
- //直接在浏览器中打开,注意参数要替换成你注册应用时的参数
- https://login.partner.microsoftonline.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize?response_type=token%20id_token&scope=openid%20Group.Read.All&client_id=83e967a2-4b87-4a5c-aad8-01897aeda929&redirect_uri=http%3A%2F%2Flocalhost%3A4200&response_mode=fragment&state=123&nonce=456
第三步:访问graph API获取group name,如下图。
这里是以代码形式展示请求的参数,方便读者copy
- GET https://microsoftgraph.chinacloudapi.cn/v1.0/groups?$orderby=displayName&$select=id,displayName HTTP/1.1
- Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6IjNXXXXXX
当我们可以更直观的方法查看组信息,访问国际版graph-explorer,如下图所示
前端:只有Angular框架的单页应用,包含adal(旧的)与msdl。
后端API:dotnetcore、nodejs、python三种语言的源代码
点击这里参考
后续总结
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。