Azure B2C:如何在 JWT 令牌中获得 "group" 声明
Azure B2C: How do I get "group" claim in JWT token
在 Azure B2C 中,我曾经能够通过以下 Retrieving Azure AD Group information with JWT:
在我的 JWT 令牌中获得 "groups" 声明
- 打开旧式 Azure 管理器 (https://manage.windowsazure.com)
- 使用 B2C 注册我的应用程序
- 下载应用程序的 B2C 清单
- 在清单中,将 "groupMembershipClaims" 条目更改为
"groupMembershipClaims": "SecurityGroup",
- 再次上传更改后的 B2C 清单
问题
这在过去(大约一个月前,我相信......)运作良好,但现在不再奏效了。详情见下文...
到目前为止我尝试了什么
方案 A:使用 Azure 管理器
遵循上面已知的好食谱。
不幸的是,这不再有效 - 当此客户端尝试使用 B2C 对我进行身份验证时,我收到以下错误:
AADB2C90068: The provided application with ID '032fe196-e17d-4287-9cfd-25386d49c0d5' is not valid against this service. Please use an application created via the B2C portal and try again"
好的,很公平 - 他们正在将我们转移到新的 Portal。
B 计划:使用 Azure 门户
使用新的 Portal,遵循古老的秘诀。
但这也不起作用 - 当我到达 "download manifest" 部分时,我找不到任何方法来访问清单(谷歌搜索告诉我它可能永远消失了......)。
计划 C:混合使用 Azure 门户和管理器
有点绝望,我尝试混合使用计划 A 和 B:使用新门户注册应用程序,然后使用旧 Azure 管理器更改清单。
但运气不好 - 当我尝试上传清单时,它失败并显示消息
ParameterValidationException=Invalid parameters provided; BadRequestException=Updates to converged applications are not allowed in this version.
计划 Z:使用图表 API 检索组成员数据
只需放弃 "group" 声明 - 相反,每当我需要群组信息时,只需使用图表 API.
查询 B2C 服务器
我真的、真的不想这样做 - 它会破坏访问令牌的自包含性,并使系统非常 "chatty"。
但我在这里将其作为计划 Z 包括在内,只是想说:是的,我知道存在该选项,不,我还没有尝试过 - 我不想这样做。
问题:
最近如何在我的 JWT 令牌中获取 "group" 声明?
恐怕是Z计划。我不知道他们为什么不 return,但目前 marked as planned on their Feedback Portal (it's the highest rated item)。
我就是这样做的。在用户通过身份验证时查询组,您也可以按照自己的方式进行 - 只需在需要时查询即可。取决于您的用例。
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
LoginPath = new PathString("/account/unauthorised"),
CookieSecure = CookieSecureOption.Always,
ExpireTimeSpan = TimeSpan.FromMinutes(20),
SlidingExpiration = true,
CookieHttpOnly = true
});
// Configure OpenID Connect middleware for each policy
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(Globals.SignInPolicyId));
}
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = string.Format(Globals.AadInstance, Globals.TenantName, policy),
AuthenticationType = policy,
AuthenticationMode = AuthenticationMode.Active,
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = Globals.ClientIdForLogin,
RedirectUri = Globals.RedirectUri,
PostLogoutRedirectUri = Globals.RedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = AuthenticationFailed,
SecurityTokenValidated = SecurityTokenValidated
},
Scope = "openid",
ResponseType = "id_token",
// This piece is optional - it is used for displaying the user's name in the navigation bar.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
}
};
}
private async Task SecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> token)
{
var groups = await _metaDataService.GetGroups(token.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value);
if (groups?.Value != null && groups.Value.Any())
{
foreach (IGroup group in groups.Value.ToList())
{
token.AuthenticationTicket.Identity.AddClaim(
new Claim(ClaimTypes.Role, group.DisplayName, ClaimValueTypes.String, "GRAPH"));
}
}
}
// Used for avoiding yellow-screen-of-death
private Task AuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
if (notification.Exception.Message == "access_denied")
{
notification.Response.Redirect("/");
}
else
{
notification.Response.Redirect("/error?message=" + notification.Exception.Message);
}
return Task.FromResult(0);
}
}
我的 GetGroups
方法只查询 getMemberGroups
method on the Users API
然后我有一个简单的帮助方法来确定用户是否在角色中:
public static bool UserIsInRole(IPrincipal user, string roleName)
{
var claims = user.Identity as ClaimsIdentity;
if (claims == null) return false;
return claims.FindAll(x => x.Type == ClaimTypes.Role).Any(x => x.Value == roleName);
}
在 Azure B2C 中,我曾经能够通过以下 Retrieving Azure AD Group information with JWT:
在我的 JWT 令牌中获得 "groups" 声明- 打开旧式 Azure 管理器 (https://manage.windowsazure.com)
- 使用 B2C 注册我的应用程序
- 下载应用程序的 B2C 清单
- 在清单中,将 "groupMembershipClaims" 条目更改为
"groupMembershipClaims": "SecurityGroup",
- 再次上传更改后的 B2C 清单
问题
这在过去(大约一个月前,我相信......)运作良好,但现在不再奏效了。详情见下文...
到目前为止我尝试了什么
方案 A:使用 Azure 管理器
遵循上面已知的好食谱。
不幸的是,这不再有效 - 当此客户端尝试使用 B2C 对我进行身份验证时,我收到以下错误:
AADB2C90068: The provided application with ID '032fe196-e17d-4287-9cfd-25386d49c0d5' is not valid against this service. Please use an application created via the B2C portal and try again"
好的,很公平 - 他们正在将我们转移到新的 Portal。
B 计划:使用 Azure 门户
使用新的 Portal,遵循古老的秘诀。
但这也不起作用 - 当我到达 "download manifest" 部分时,我找不到任何方法来访问清单(谷歌搜索告诉我它可能永远消失了......)。
计划 C:混合使用 Azure 门户和管理器
有点绝望,我尝试混合使用计划 A 和 B:使用新门户注册应用程序,然后使用旧 Azure 管理器更改清单。
但运气不好 - 当我尝试上传清单时,它失败并显示消息
ParameterValidationException=Invalid parameters provided; BadRequestException=Updates to converged applications are not allowed in this version.
计划 Z:使用图表 API 检索组成员数据
只需放弃 "group" 声明 - 相反,每当我需要群组信息时,只需使用图表 API.
查询 B2C 服务器我真的、真的不想这样做 - 它会破坏访问令牌的自包含性,并使系统非常 "chatty"。
但我在这里将其作为计划 Z 包括在内,只是想说:是的,我知道存在该选项,不,我还没有尝试过 - 我不想这样做。
问题:
最近如何在我的 JWT 令牌中获取 "group" 声明?
恐怕是Z计划。我不知道他们为什么不 return,但目前 marked as planned on their Feedback Portal (it's the highest rated item)。
我就是这样做的。在用户通过身份验证时查询组,您也可以按照自己的方式进行 - 只需在需要时查询即可。取决于您的用例。
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
LoginPath = new PathString("/account/unauthorised"),
CookieSecure = CookieSecureOption.Always,
ExpireTimeSpan = TimeSpan.FromMinutes(20),
SlidingExpiration = true,
CookieHttpOnly = true
});
// Configure OpenID Connect middleware for each policy
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(Globals.SignInPolicyId));
}
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = string.Format(Globals.AadInstance, Globals.TenantName, policy),
AuthenticationType = policy,
AuthenticationMode = AuthenticationMode.Active,
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = Globals.ClientIdForLogin,
RedirectUri = Globals.RedirectUri,
PostLogoutRedirectUri = Globals.RedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = AuthenticationFailed,
SecurityTokenValidated = SecurityTokenValidated
},
Scope = "openid",
ResponseType = "id_token",
// This piece is optional - it is used for displaying the user's name in the navigation bar.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
}
};
}
private async Task SecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> token)
{
var groups = await _metaDataService.GetGroups(token.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value);
if (groups?.Value != null && groups.Value.Any())
{
foreach (IGroup group in groups.Value.ToList())
{
token.AuthenticationTicket.Identity.AddClaim(
new Claim(ClaimTypes.Role, group.DisplayName, ClaimValueTypes.String, "GRAPH"));
}
}
}
// Used for avoiding yellow-screen-of-death
private Task AuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
if (notification.Exception.Message == "access_denied")
{
notification.Response.Redirect("/");
}
else
{
notification.Response.Redirect("/error?message=" + notification.Exception.Message);
}
return Task.FromResult(0);
}
}
我的 GetGroups
方法只查询 getMemberGroups
method on the Users API
然后我有一个简单的帮助方法来确定用户是否在角色中:
public static bool UserIsInRole(IPrincipal user, string roleName)
{
var claims = user.Identity as ClaimsIdentity;
if (claims == null) return false;
return claims.FindAll(x => x.Type == ClaimTypes.Role).Any(x => x.Value == roleName);
}