来自 Azure AD 中 OAuth 代表流的无效 Base64 SAML 断言
Invalid Base64 SAML Assertion from OAuth on-behalf-of flow in Azure AD
我在使用 Azure AD 和 On-Behalf-Of Flow 将 OAuth 访问令牌交换为 SAML 断言时遇到了一个奇怪的问题。我正在尝试使用 Azure AD 的代理流将 OAuth 访问令牌交换为 SAML 断言。
设置
- 前端使用 OAuth 访问令牌与后端通信
- 我需要从中获取数据的数据源,它受 SAML 保护
需要从后端执行从数据源获取数据的请求,因为存在对数据源的访问限制。
描述
根据 Azure AD v1 (Github docs) 的文档,我能够请求最初看起来不错的响应。我使用的请求参数是:
grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer
assertion: <access token containing the correct scopes for the Back-End>
client_id: <client-id-of-back-end>
client_secret: <assigned-secret>
resource: <resource-of-the-datasource>
requested_token_use: on_behalf_of
requested_token_type: urn:ietf:params:oauth:token-type:saml2
请求作为 POST 请求发送,使用 x-www-form-urlencoded
作为内容类型(端点“https://login.microsoftonline.com/tenant-id/oauth2/token”)。
问题
我几乎可以肯定,我遇到了一个错误,但是我不知道如何在没有开发人员支持计划的情况下联系 Azure。我收到的回复一开始看起来不错:
{
"token_type": "Bearer",
"expires_in": "3579",
"ext_expires_in": "3579",
"expires_on": "1613985579",
"resource": "<datasource>",
"access_token": "PEFzc2Vyd...9uPg",
"issued_token_type": "urn:ietf:params:oauth:token-type:saml2",
"refresh_token": "0.ATEAt...hclkg-7g"
}
来自 access_token
字段的断言不是有效的 base64 字符串。尝试使用 C# Base64Convert
对其进行解码,导致此异常:
System.FormatException: The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.
然而,我能够使用 bashs base64 -D
部分解码它,这给了我一个某种程度上有效的断言:
$ base64 -D "response.txt"
Invalid character in input stream.
<Assertion ID="_26be6964-2e17-4184-8ac7-d4cdbb9d5700" IssueInstant="2021-02-22T12:35:49.919Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion"><Issuer>https://sts.windows.net/[id]/</Issuer><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><Reference URI="#_26be6964-2e17-4184-8ac7-d4cdbb9d5700"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>...<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"><AttributeValue>test@domain.com</
问题
我几乎可以肯定,断言应该是一个有效的 base64 字符串,可以使用任何能够这样做的东西进行解码。我错过了什么吗?或者这是 V1 OBO Flow 的已知问题?是否有已知的解决方法?
断言是您在初始调用 AAD 时收到的访问令牌,如前所述 here。
这是一个 JWT 令牌,采用 Based64 URL 编码,可以使用 https://JWT.io or https://JWT.ms 等工具或任何编程语言进行解码。要点是,如果发出的访问令牌是有效的访问令牌,则应该对其进行解码,并且这与在后续调用中添加以获取 SAML 令牌的访问令牌相同。
您还可以查看以下关于 OBO 流程的文章:https://blogs.aaddevsup.xyz/2019/08/understanding-azure-ads-on-behalf-of-flow-aka-obo-flow/
这里要注意的要点是我们如何从 AAD 请求初始访问令牌。如果您的前端是一个 SPA,并且您在那里使用隐式流,您可能想看看这个“截至 2018 年 5 月,一些隐式流派生 id_token 不能用于 OBO 流。单页应用程序 (SPA) 应将访问令牌传递给中间层机密客户端以执行 OBO 流。"
在解码JWT时,首先需要将其从Base64URL编码的字符串转换为Base64编码的字符串。一旦 JWT 被 base64 编码,就需要对其进行解码,然后将其解析为 json.
相同的 Powershell 示例:
$token = "<put the jwt here>"
if (!$token.Contains(".") -or !$token.StartsWith("eyJ")) {
Write-Error "Invalid token" -ErrorAction Stop
}
# Token
foreach ($i in 0..1) {
$data = $token.Split('.')[$i].Replace('-', '+').Replace('_', '/')
switch ($data.Length % 4) {
0 { break }
2 { $data += '==' }
3 { $data += '=' }
}
}
$decodedToken = [System.Text.Encoding]::UTF8.GetString([convert]::FromBase64String($data)) | ConvertFrom-Json
Write-Verbose "JWT Token:"
Write-Verbose $decodedToken
C# 示例:
static void jwtDecoder()
{
try
{
Console.WriteLine("JWT to Decode: " + jwtEncodedString + "\n");
var jwtHandler = new JwtSecurityTokenHandler();
var readableToken = jwtHandler.CanReadToken(jwtEncodedString);
if (readableToken != true)
{
Console.WriteLine("\n\nThe token doesn't seem to be in a proper JWT format.\n\n");
}
if (readableToken == true)
{
var token = jwtHandler.ReadJwtToken(jwtEncodedString);
var headers = token.Header;
var jwtHeader = "{";
foreach (var h in headers)
{
jwtHeader += '"' + h.Key + "\":\"" + h.Value + "\",";
}
jwtHeader += "}";
Console.Write("\nHeader :\r\n" + JToken.Parse(jwtHeader).ToString(Formatting.Indented));
var claims = token.Claims;
var jwtPayLoad = "{";
foreach (Claim c in claims)
{
jwtPayLoad += '"' + c.Type + "\":\"" + c.Value + "\",";
}
jwtPayLoad += "}";
Console.Write("\r\nPayload :\r\n" + JToken.Parse(jwtPayLoad).ToString(Formatting.Indented));
var jwtSignature = "[RawSignature: ";
jwtSignature += token.RawSignature;
jwtSignature += " ]";
Console.Write("\r\nSignature :\r\n" + jwtSignature);
//Console.ReadLine();
}
}
finally
{
Console.Write("\n\nPress Enter to close window ...");
Console.Read();
}
}
我在使用 Azure AD 和 On-Behalf-Of Flow 将 OAuth 访问令牌交换为 SAML 断言时遇到了一个奇怪的问题。我正在尝试使用 Azure AD 的代理流将 OAuth 访问令牌交换为 SAML 断言。
设置
- 前端使用 OAuth 访问令牌与后端通信
- 我需要从中获取数据的数据源,它受 SAML 保护
需要从后端执行从数据源获取数据的请求,因为存在对数据源的访问限制。
描述
根据 Azure AD v1 (Github docs) 的文档,我能够请求最初看起来不错的响应。我使用的请求参数是:
grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer
assertion: <access token containing the correct scopes for the Back-End>
client_id: <client-id-of-back-end>
client_secret: <assigned-secret>
resource: <resource-of-the-datasource>
requested_token_use: on_behalf_of
requested_token_type: urn:ietf:params:oauth:token-type:saml2
请求作为 POST 请求发送,使用 x-www-form-urlencoded
作为内容类型(端点“https://login.microsoftonline.com/tenant-id/oauth2/token”)。
问题
我几乎可以肯定,我遇到了一个错误,但是我不知道如何在没有开发人员支持计划的情况下联系 Azure。我收到的回复一开始看起来不错:
{
"token_type": "Bearer",
"expires_in": "3579",
"ext_expires_in": "3579",
"expires_on": "1613985579",
"resource": "<datasource>",
"access_token": "PEFzc2Vyd...9uPg",
"issued_token_type": "urn:ietf:params:oauth:token-type:saml2",
"refresh_token": "0.ATEAt...hclkg-7g"
}
来自 access_token
字段的断言不是有效的 base64 字符串。尝试使用 C# Base64Convert
对其进行解码,导致此异常:
System.FormatException: The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.
然而,我能够使用 bashs base64 -D
部分解码它,这给了我一个某种程度上有效的断言:
$ base64 -D "response.txt"
Invalid character in input stream.
<Assertion ID="_26be6964-2e17-4184-8ac7-d4cdbb9d5700" IssueInstant="2021-02-22T12:35:49.919Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion"><Issuer>https://sts.windows.net/[id]/</Issuer><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><Reference URI="#_26be6964-2e17-4184-8ac7-d4cdbb9d5700"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>...<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"><AttributeValue>test@domain.com</
问题
我几乎可以肯定,断言应该是一个有效的 base64 字符串,可以使用任何能够这样做的东西进行解码。我错过了什么吗?或者这是 V1 OBO Flow 的已知问题?是否有已知的解决方法?
断言是您在初始调用 AAD 时收到的访问令牌,如前所述 here。
这是一个 JWT 令牌,采用 Based64 URL 编码,可以使用 https://JWT.io or https://JWT.ms 等工具或任何编程语言进行解码。要点是,如果发出的访问令牌是有效的访问令牌,则应该对其进行解码,并且这与在后续调用中添加以获取 SAML 令牌的访问令牌相同。
您还可以查看以下关于 OBO 流程的文章:https://blogs.aaddevsup.xyz/2019/08/understanding-azure-ads-on-behalf-of-flow-aka-obo-flow/
这里要注意的要点是我们如何从 AAD 请求初始访问令牌。如果您的前端是一个 SPA,并且您在那里使用隐式流,您可能想看看这个“截至 2018 年 5 月,一些隐式流派生 id_token 不能用于 OBO 流。单页应用程序 (SPA) 应将访问令牌传递给中间层机密客户端以执行 OBO 流。"
在解码JWT时,首先需要将其从Base64URL编码的字符串转换为Base64编码的字符串。一旦 JWT 被 base64 编码,就需要对其进行解码,然后将其解析为 json.
相同的 Powershell 示例:
$token = "<put the jwt here>"
if (!$token.Contains(".") -or !$token.StartsWith("eyJ")) {
Write-Error "Invalid token" -ErrorAction Stop
}
# Token
foreach ($i in 0..1) {
$data = $token.Split('.')[$i].Replace('-', '+').Replace('_', '/')
switch ($data.Length % 4) {
0 { break }
2 { $data += '==' }
3 { $data += '=' }
}
}
$decodedToken = [System.Text.Encoding]::UTF8.GetString([convert]::FromBase64String($data)) | ConvertFrom-Json
Write-Verbose "JWT Token:"
Write-Verbose $decodedToken
C# 示例:
static void jwtDecoder()
{
try
{
Console.WriteLine("JWT to Decode: " + jwtEncodedString + "\n");
var jwtHandler = new JwtSecurityTokenHandler();
var readableToken = jwtHandler.CanReadToken(jwtEncodedString);
if (readableToken != true)
{
Console.WriteLine("\n\nThe token doesn't seem to be in a proper JWT format.\n\n");
}
if (readableToken == true)
{
var token = jwtHandler.ReadJwtToken(jwtEncodedString);
var headers = token.Header;
var jwtHeader = "{";
foreach (var h in headers)
{
jwtHeader += '"' + h.Key + "\":\"" + h.Value + "\",";
}
jwtHeader += "}";
Console.Write("\nHeader :\r\n" + JToken.Parse(jwtHeader).ToString(Formatting.Indented));
var claims = token.Claims;
var jwtPayLoad = "{";
foreach (Claim c in claims)
{
jwtPayLoad += '"' + c.Type + "\":\"" + c.Value + "\",";
}
jwtPayLoad += "}";
Console.Write("\r\nPayload :\r\n" + JToken.Parse(jwtPayLoad).ToString(Formatting.Indented));
var jwtSignature = "[RawSignature: ";
jwtSignature += token.RawSignature;
jwtSignature += " ]";
Console.Write("\r\nSignature :\r\n" + jwtSignature);
//Console.ReadLine();
}
}
finally
{
Console.Write("\n\nPress Enter to close window ...");
Console.Read();
}
}