ASP.NET Core 5中ClaimsTransformation调用Microsoft Graph抛出MsalUiRequiredException异常
问题原因
报错的根本原因是IClaimsTransformation执行时,你注入的GraphServiceClient默认从当前HttpContext获取用户身份,此时身份上下文还未完全挂载到请求上下文,导致MSAL无法找到对应的用户账户来获取访问令牌。
可行解决方案
方案1:显式给Graph请求传入当前用户身份
修改你的UserPhotoClaims实现,在调用Graph接口时通过WithUser方法显式传入TransformAsync接收到的principal参数即可,代码如下:
public class UserPhotoClaims : IClaimsTransformation { private readonly GraphServiceClient _graph; public UserPhotoClaims(GraphServiceClient graphServiceClient) { _graph = graphServiceClient; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { // 跳过未认证用户的处理 if (!principal.Identity?.IsAuthenticated ?? true) { return principal; } if(!principal.HasClaim(x => x.Type == "photo")) { try { // 显式传入当前principal指定调用Graph的用户身份 Stream photo = await _graph.Me.Photo.Content .Request() .WithUser(principal) .GetAsync(); using var ms = new MemoryStream(); await photo.CopyToAsync(ms); string photoBase64 = Convert.ToBase64String(ms.ToArray()); // 直接给原有身份添加Claim,无需新建身份 (principal.Identity as ClaimsIdentity)?.AddClaim(new Claim("photo", photoBase64)); } catch { // 异常处理:比如用户无头像、Graph调用失败时不阻塞登录流程,可自行添加日志、默认头像逻辑 } } return principal; } }
方案2:直接通过令牌调用Graph(兼容性更强)
如果方案1仍不生效,可以改用ITokenAcquisition手动拿令牌调用Graph接口,需要先在Startup.cs注册HttpClient:services.AddHttpClient();,改造后的代码如下:
public class UserPhotoClaims : IClaimsTransformation { private readonly ITokenAcquisition _tokenAcquisition; private readonly IHttpClientFactory _httpClientFactory; public UserPhotoClaims(ITokenAcquisition tokenAcquisition, IHttpClientFactory httpClientFactory) { _tokenAcquisition = tokenAcquisition; _httpClientFactory = httpClientFactory; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { if (!principal.Identity?.IsAuthenticated ?? true) { return principal; } if(!principal.HasClaim(x => x.Type == "photo")) { try { // 用当前principal获取Graph访问令牌 var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync( new[] { "User.Read" }, user: principal); var client = _httpClientFactory.CreateClient(); client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken); var photoBytes = await client.GetByteArrayAsync("https://graph.microsoft.com/v1.0/me/photo/$value"); string photoBase64 = Convert.ToBase64String(photoBytes); (principal.Identity as ClaimsIdentity)?.AddClaim(new Claim("photo", photoBase64)); } catch { // 异常处理逻辑 } } return principal; } }
注意事项
- 确保你的Azure AD应用注册中已经添加了
User.Read的委托权限,并且已经完成组织管理员同意 IClaimsTransformation会在每次认证流程中被调用,建议将头像缓存到分布式缓存中,避免每次都调用Graph接口影响性能- 如果头像base64字符串过大,会超出Cookie大小限制导致认证失败,超出限制的情况下建议Claim仅存缓存key,前端单独通过接口拉取头像
内容的提问来源于stack exchange,提问作者Kaare Mai
相关产品推荐
相关产品推荐

