一、简介
(一)Azure AD 简介
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 AD是如何集中地为云和本地应用程序提供单一标识系统
- 理解OAuth2.0、授权模式、如何获取Token并对其进行认证等
- 在AzureAD in Azure Console 注册并配置应用程序
- AzureAD提供标识服务的应用场景
- 源码
二、为云和本地应用程序提供单一标识系统
(一)体系结构
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种,如下截图:
- 第一种是直接将密码hash-hash同步到Azure AD, 即密码会被存储在Azure AD中,通常这种不合规
- 第三种是通过ADFS来登录,密码是不会被存储在Azure AD,在Azure AD验证时,请求会被重定向到企业自建的ADFS服务器上,验证通过后,将验证成功的Token返回。
下图是在本地服务器上安装Azure AD Connect的截图
下图是在本地服务器上安装ADFS、域服务、域控制器的截图
(二) Azure AD与ADFS是如何共同完成身份认证的?
看下图,在安装Azure AD Connect的时候,Azure AD与ADFS就建立起了信任关系,Azure AD无法对用户身份进行认证,但ADFS可以,ADFS对用户认证通过后将ADFS颁发的Token返回给Azure AD,如果Azure AD判断它是合法有效的,Azure AD将自己颁发的Token返回给用户。
为了进一步的理解认证流程,再贴一张图如下:
(三)单点登录有哪几种方式
有几种方法可以配置应用程序以实现单一登录。 选择哪种单一登录方法取决于为应用程序配置的身份验证方式。
- 云应用程序可以使用 OpenID Connect、OAuth、SAML、基于密码、链接或禁用的方法进行单一登录。
- 本地应用程序可以使用基于密码、集成身份验证、基于标头、链接或已禁用的方法进行单一登录。 当应用程序配置为应用程序代理时,本地选项适用。
此流程图有助于确定哪种单一登录方法最适合你的情况。点击这里了解更多
但本文这里只重点讲解OAuth.
三、OAuth简介
(一)JSON Web Token (JWT)
Jwt(Json Web Token)它是一种基于Json用于安全的信息传输标准,Jwt有两个用途,其一是用于数据交互,因为Jwt是被签名的,可以保证数据的完整性。另外就是用来携带用户信息进行身份验证。
Jwt包含三个部分:
- Header:包含了签名算法以及令牌类型(默认为JWT)。
- Playload:包含Jwt所携带的信息内容,Playload中包含了3种类型的Claim(声明)定义,分别是标准的,如iss(issuer,Jwt的发行者)、sub(subject,Jwt所代表的用户)、aud(audience,Jwt的接收者)、exp(expiration time,Jwt的过期时间),还有一些是公共约定的如:http://www.iana.org/assignments/jwt/jwt.xhtml,另外就是私有自定义的,这些用来存放具体的信息。
- Signature:包含了Header以及Playload的base64Url编码后的签名结果
最终三个部分均使用Base64Url的方式进行编码后使用符号“.”进行分隔,以下是一个完整Jwt的例子:
(二)OAuth四种模式
访问理解OAuth 2.0,他写得已经足够好了。
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
四、快速Demo如何使用AzureAD获取Token并对Token进行验证
(一)在Azure AD中注册一个新应用程序
本文这里使用是国内的Azure,由世纪互联运营,选择Azure AD服务后,点击应用注册->新注册
在概述和身份验证中注意如下几点:
应用程序ID(Application id or client id),代表你的应用程序的唯一标识
租户ID(tanent id),类似于你在Azure AD中租用了一个空间,这个空间里可以有很多应用,租户ID就是它的编号。
(二)如何获取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 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。
这里有两个注意事项:
- redirect_uri必须与在Azure AD注册应用时的重定义向URL一致, 这里值为http://localhost:4200。
- 对于单页应用,在Azure AD中必须打开访问令牌和ID令牌, 对于Web Application应用(带后台服务器),可以只需要打开ID Token。在如上的url中参数response_type=token id_token就与之对应,它会将token和id_token一起直接返回给你。当然你也可以只写response_type=token,它只会将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个注意事项:
- aud:即audience,非常重要,即表示这个Token对哪个资源有访问权限,‘00000003-0000-0000-c000-000000000000’ 它实际是microsoft graph(国内版)的Client id,也就是说,这个Token只能访问microsoft graph(国内版)的资源,或者说,只能调用以https://microsoftgraph.chinacloudapi.cn/开头的API
- iss:即issue,This token was issued by Azure Active Directory。
- scp:即scope,此token有哪些权限可以访问microsoft graph(国内版),这里可以读取邮箱、用户名等内容。
- amr: pwd:表示此token是用户使用用户名和密码登录过后产生的token。
(三)拿此Token访问第三方应用
microsoft graph(国内版)本身微软内置的应用程序,这里使用Postman调用。https://graph.microsoft.com/v1.0/me
(四)后端API如何对TOKEN进行验证
将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
(五)小结
- 实际上我们使用一个url就可以顺序地拿到了Token,仅此而已。
- 得到的Token只能访问aud的值中对应的资源,对于不同的资源一般我们应使用不同的Token。
- 可见它是属于OAuth中4种授权模式中的简化模式(implicit grant type),不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要对Token认证,仅仅只是取到Token。
- 单页应用(Angular/React/Vue)就是属于这种简化模式,只不过我们开发单页应用时,是使用相应的库对其进行了封装,以至我们看不清它的庐山真面目。
五、如何使用AzureAD
(一)名词解释
- tenant:Azure AD租户,如果Azure AD是一个酒店,那租户就是一间单独的客房。在此租户内,组织者或公司可以安全地使用自己的资源,与其他租户间保持隔离。
- id token:ID 令牌,授权服务器的授权终结点提供的 OpenID Connect 安全令牌,其中包含与最终用户资源所有者的身份验证相关的声明。 与访问令牌一样,ID 令牌也以数字签名的 JSON Web 令牌 (JWT) 来表示。 不过,与访问令牌不同的是,ID 令牌的声明并不用于与资源访问相关的用途。请不要将id token加入到API的头部去访问资源,id token仅表示当前用户的一种标识,id token只适用于OpenID Connect授权模式下,OpenID Connect要求带服务端的,如果是单页应用,不应该使用它。
- access token:访问令牌,由授权服务器颁发的一种安全令牌,可供客户端应用程序用来访问受保护的资源服务器,有效时间通常为半小时或1小时。
- refresh token:刷新令牌,由授权服务器颁发的一种安全令牌,可供客户端应用程序在访问令牌过期之前请求新的访问令牌,有效时间通常为半年或1年。
(二)Azure门户网站中 Azure AD的基本介绍
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, 下图是使用阿里的域名解析界面。
点击验证按钮后,此域名的状态会显示成已验证。
之后我们就可以使用新的域名了,我们创建一个新用户试试。
这样我们就有了两个帐号了,上图中是第二个帐号是我的主帐号,它具有全局管理员(最大的)的权限, 刚才我们创建了一个新用户,我们可以对他分配一些必要的角色,那以后我们就可以只使用此帐号进行后续的操作。
六、应用实战(前端单页应用与后端API)
(一)注册两个应用
进入Azure AD,点击【应用注册】,创建两个应用,命名为MyFrontendApp和MyBackendApp
- MyFrontendApp:是一个基于Angular的单页应用
- MyBackendApp:是一个基于DotNetCore3.1的WebApi项目
前端应用将会使用隐含模块(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】和【不带用户信息的token】,对比一下如下两张图
- 带用户信息的token: 通常包含upn/name/scp
- 不带用户信息的token: 通常包含roles, 表示两个服务或应用之前相互访问,与用户无关,一般使用grant_type=client_credentials的方式访问。如果没有roles,说明此token没有任何权限。
(三)在Azure AD中,创建一个应用还是2个应用?
对于一个前后端分离的项目,它本是属于同一项目,有必要像上面一样创建两个应用吗?
答:对于前后端分离的项目,你可以只需要创建一个项目,也可以创建两个项目,都可行得通。因为你既可以把它当作同一个项目来看,也可以当作两个项目来看。但笔者较懒习惯于只创建一个项目,这里创建两个是为了方便演示,原因有二:一是为了向读者清晰地展示应用是如何暴露API及授权的。二是如果将前后端两项目共用一个MyFrontendApp应用的话,那MyBackendApp可当作第三方应用,方便向读者展示如何调用第三方应用的API。
(四)如果在Azure AD中只创建一个应用,前后台两项目共用同一个Client Id
上面两个应用中我只使用MyFrontendApp,前后端共用同一个Client id
- MyFrontendApp's Client Id: 83e967a2-4b87-4a5c-aad8-01897aeda929
(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"
}
(五)如果在Azure AD中创建两个应用,分别用于前后端
上面两个应用程序的Client Id如下:
- MyFrontendApp's Client Id: 83e967a2-4b87-4a5c-aad8-01897aeda929
- MyBackendApp's Client Id: 4bf9068a-6653-4550-920d-5fa61e332af3
(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"
}
(六)将API暴露出来供第三方应用使用
后端接收Token后,需要处理的内容:
(1)验证token,包含aud的验证,aud是可以有三种形式:
- 一个GUID(如果上面前端传进来的token)
- 多个GUID (一个GUID的数组,如果是你自己创建的identity4项目,甚至可以取任意名字)
- 一个App ID URL(在上面第4节中,如果将4bf9068a-6653-4550-920d-5fa61e332af3/.default 换成api://4bf9068a-6653-4550-920d-5fa61e332af3/.default, 那后端的Client ID应该设置成api://4bf9068a-6653-4550-920d-5fa61e332af3)
(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带有用户信息。更多信息请参考第七章第七节。
七、Azure AD中各种授权模式的使用场景
(一)授权码模式(authorization code)
适用于[桌面应用、移动应用、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进行这一步操作,它是通过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并解析,如下图所示:
(三)简化模式(隐含模式 implicit)
第六节中已经介绍了单页应用,它就是使用的简化模式,这里就不在赘述了。
它是通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
(四)密码模式(Resource Owner Password Credentials Grant)
这里也不介绍,我们用得不多
(五)客户端模式(Client Credentials Grant)
服务与服务之间相互调用就需要用到此模式,比如:两个后台的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,结果如下图:
(六)Open ID Connect模式
它适用于WebApplication这样的应用,即网页和API混合在一起,且有后台服务器的那种,比如DotNet中的MVC。由于现在用得比较少,我这里贴上链接供大家参考。
- Openid Connect : https://docs.microsoft.com/zh-cn/azure/active-directory/saas-apps/openidoauth-tutorial
- Openid Connect :https://docs.microsoft.com/zh-cn/azure/active-directory/develop/v2-protocols-oidc
- Openid Connect :https://www.cnblogs.com/linianhui/p/openid-connect-core.html
- 微软Openid的例子和代码:https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/1-WebApp-OIDC
- https://stormpath.com/blog/openid-connect-user-authentication-in-asp-net-core
- 人家的代码及讲解:https://github.com/linianhui/oidc.example,https://www.cnblogs.com/linianhui/category/929878.html
当然本文也提供了相应例子供读者参考,点击这里访问源码中,DotNetCore3-WebApp-OpenIdConnect文件夹就是例子。
(七)代理流模式
假定已在应用程序中使用 OAuth 2.0 授权代码授权流或其他登录流对用户进行身份验证。 此时,应用程序有一个访问令牌, 其中包含用户的声明,并同意访问中间层 web API (API a)。 现在,API A 需要向下游 Web API (API B) 发出经过身份验证的请求。点击这里访问更详细的说明。
接下来的步骤构成了 OBO 流,并在下图中进行说明。
- 客户端应用程序使用令牌 A(其中包含 API A 的
aud
声明)向 API A 发出请求。 - API A 向 Microsoft 标识平台令牌颁发终结点进行身份验证并请求访问 API B 的令牌。
- Microsoft 标识平台令牌颁发终结点使用令牌 A 验证 API A 的凭据,并颁发供 API B(令牌 B)访问 API A 的访问令牌。
- 令牌 B 由 API A 在向 API B 发出的请求的 authorization 标头中设置。
- 受保护资源中的数据通过 API B 返回到 API A,并从那返回到客户端。
在此方案中,中间层服务无需用户干预,就要获取用户对访问下游 API 的许可。 因此,在身份验证过程的同意步骤中会提前显示授权访问下游 API 的选项。 若要了解如何为应用设置此选项,请参阅为中间层应用程序获得同意。
我们以上面的MyFrontendApp、MyBackendApp和Microsoft Graph API为例,这里我们就不另创建第三个应用了,我们以Microsoft Graph API作为第三方API。基本流程如下:
- 用户登录MyFrontendApp获取到Token_1
- 使用此Token_1发起请求调用后台MyBackendApp的API,
- MyBackendApp接着通过此Token_1在Azure AD中换取Token_2
- 使用Token_2向第三方API(Microsoft Graph 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.
- https://login.chinacloudapi.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/authorize
- https://login.chinacloudapi.cn/a09fa999-f876-4f4f-88dd-8a1983c066da/oauth2/v2.0/token
但如果用户使用V1的话,那传参的内容中的所有的scope都得换成resource,这里只举其中一个客户授权模式,如下两张图对比一下差异,至于其他模式也是需要scope都得换成resource,这里就不再演示了。
解析Token发现他们的aud都指向graph(国内版)的app id.
想查看更多差异,请点击这里
九、App id 与 App id URL的问题
通常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配置(groups and optional claims)
在上述章节中token中payload里面的信息是固定的,有时候我们希望token里面包含更多的信息,比如:groups,程序通过此groups判断是否拥有相应的权限,如果返回的token中的groups中包含类似于"cong.secrity.group.demo",那此用户将有管理员的权限。 当然它的功能不仅限于此,主要包含如下:
- optional claims
- claims mapping
- Claims transformation
本文这里只讲第一个,接下面我将新增两个optional claims,并在token中显示。
(一)增加可选的Claim和groups
第一步: 在应用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。
(二)显示group name
我们创建的组名可能类似于"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,如下图所示
(三)参考链接
- 配置option claims: https://docs.microsoft.com/zh-cn/azure/active-directory/develop/active-directory-optional-claims
- customize claims: https://docs.microsoft.com/zh-cn/azure/active-directory/develop/active-directory-claims-mapping
- 配置group claims: https://docs.microsoft.com/zh-cn/azure/active-directory/hybrid/how-to-connect-fed-group-claims
- 刚出炉新版graph-explorer(2020年5月发布): https://developer.microsoft.com/zh-cn/graph/graph-explorer
- graph及其文档:https://docs.microsoft.com/zh-cn/graph/, https://docs.microsoft.com/en-us/graph/api/resources/groups-overview?view=graph-rest-1.0
- 其他:https://securecloud.blog/category/oauth2/
- 如何获取group name的源码: https://stackoverflow.com/questions/59096299/azure-ad-get-group-ad-name-via-graph-service-or-claims?noredirect=1&lq=1和 https://stackoverflow.com/questions/52555722/azure-ad-issues-claims-security-groups-names
十一、源代码
前端:只有Angular框架的单页应用,包含adal(旧的)与msdl。
后端API:dotnetcore、nodejs、python三种语言的源代码
点击这里参考
十二、讨论与总结
后续总结
十三、参考链接
- Azure Active Directory 文档:https://docs.microsoft.com/zh-cn/azure/active-directory/
- 理解OAuth 2.0:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
- MSAL JS 与 ADAL JS 的差异:https://docs.microsoft.com/zh-cn/azure/active-directory/develop/msal-compare-msal-js-and-adal-js
- Azure AD 中8 授权类型:https://docs.microsoft.com/zh-cn/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
- Using ADAL with Angular2:https://devblogs.microsoft.com/premier-developer/using-adal-with-angular2/
- 应用程序代理的工作原理:https://docs.microsoft.com/zh-cn/azure/active-directory/manage-apps/application-proxy
- SAML 2.0 和 WSFed:https://docs.microsoft.com/zh-cn/azure/active-directory/manage-apps/isv-choose-multi-tenant-federation
- OAuth 2.0 和OPEN ID 连接:https://docs.microsoft.com/zh-cn/azure/active-directory/manage-apps/isv-choose-multi-tenant-federation
- 后台.netcore应用程序加token认证的逻辑:https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2/tree/master/1.%20Desktop%20app%20calls%20Web%20API
- 前端angular(各版本)使用MSDL:https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples
- 前端使用auglar+后端.netcore3.1:https://github.com/Azure-Samples/ms-identity-javascript-angular-spa-aspnetcore-webapi
- 微软Azure AD的所有例子:https://docs.microsoft.com/zh-cn/azure/active-directory/develop/sample-v2-code
- tutorial-v2-angular: https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-angular
- Acquire token for an API:https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-acquire-token?tabs=javascript