ASP.NET MVC中访问令牌过期后无需交互登录刷新以调用Power BI API
ASP.NET MVC 无交互式登录刷新Power BI访问令牌解决方案
核心问题原因
你当前的实现存在两个核心缺失:
- 未请求
offline_access权限,无法获取刷新令牌(refresh token) - 仅存储了短期有效的访问令牌,未实现令牌刷新逻辑,也未利用MSAL的内置令牌缓存能力
修改步骤
1. 调整OIDC配置,请求刷新令牌所需权限
修改ConfigureAuth方法中的OpenIdConnectAuthenticationOptions配置,新增Scope配置项,添加offline_access权限和Power BI接口权限:
public void ConfigureAuth(IAppBuilder app) { try { app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); app.UseCookieAuthentication(new CookieAuthenticationOptions()); app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions { ClientId = clientId, Authority = authority, PostLogoutRedirectUri = postLogoutRedirectUri, RedirectUri = redirectUri, UseTokenLifetime = false, // 新增Scope配置 Scope = "openid profile offline_access https://analysis.windows.net/powerbi/api/Workspace.Read.All https://analysis.windows.net/powerbi/api/Report.Read.All https://analysis.windows.net/powerbi/api/Dashboard.Read.All https://analysis.windows.net/powerbi/api/Dataset.Read.All", Notifications = new OpenIdConnectAuthenticationNotifications { AuthenticationFailed = context => { context.HandleResponse(); context.Response.Redirect("/Error?message=" + context.Exception.Message); return Task.FromResult(0); }, AuthorizationCodeReceived = OnAuthorizationCodeCallback } }); app.UseStageMarker(PipelineStage.Authenticate); } catch (Exception ex) { throw new Exception(ex.Message); } }
2. 调整授权回调逻辑,存储刷新令牌与令牌缓存关联标识
修改OnAuthorizationCodeCallback方法,除访问令牌外,额外将刷新令牌、访问令牌过期时间、MSAL用户账户标识存入用户Claims:
private static async Task OnAuthorizationCodeCallback(AuthorizationCodeReceivedNotification context) { var appConfidential = ConfidentialClientApplicationBuilder.Create(clientId) .WithRedirectUri(redirectUri) .WithClientSecret(clientSecret) .WithAuthority(authority) // 配置MSAL令牌缓存,可根据需要持久化到数据库/缓存中 .Build(); string powerBiPermissionApi = "https://analysis.windows.net/powerbi/api/"; string[] ReadUserWorkspaces = new string[] { powerBiPermissionApi + "Workspace.Read.All", powerBiPermissionApi + "Report.Read.All", powerBiPermissionApi + "Dashboard.Read.All", powerBiPermissionApi + "Dataset.Read.All" }; var authResult = await appConfidential.AcquireTokenByAuthorizationCode(ReadUserWorkspaces, context.Code).ExecuteAsync(); ClaimsIdentity userClaims = context.AuthenticationTicket.Identity; userClaims.AddClaim(new Claim("Access_Token", authResult.AccessToken)); // 新增存储项 userClaims.AddClaim(new Claim("Refresh_Token", authResult.RefreshToken)); userClaims.AddClaim(new Claim("AccessToken_ExpiresOn", authResult.ExpiresOn.ToUnixTimeSeconds().ToString())); userClaims.AddClaim(new Claim("User_Account_Id", authResult.Account.HomeAccountId.Identifier)); }
3. 实现统一的有效访问令牌获取方法
新增公共方法,每次调用Power BI API前先校验令牌有效性,过期则自动用刷新令牌换取新的访问令牌:
public async Task<string> GetValidPowerBiAccessToken() { var identity = (ClaimsIdentity)HttpContext.Current.User.Identity; // 从Claims中取出已存的令牌信息 var existingAccessToken = identity.FindFirst("Access_Token")?.Value; var expiresOnUnix = long.Parse(identity.FindFirst("AccessToken_ExpiresOn")?.Value ?? "0"); var refreshToken = identity.FindFirst("Refresh_Token")?.Value; var userAccountId = identity.FindFirst("User_Account_Id")?.Value; // 令牌未过期直接返回(提前5分钟刷新避免边界过期问题) if (DateTimeOffset.FromUnixTimeSeconds(expiresOnUnix) > DateTimeOffset.UtcNow.AddMinutes(5)) { return existingAccessToken; } // 令牌过期,用刷新令牌换取新令牌 var appConfidential = ConfidentialClientApplicationBuilder.Create(clientId) .WithClientSecret(clientSecret) .WithAuthority(authority) .Build(); string[] scopes = new string[] { "https://analysis.windows.net/powerbi/api/Workspace.Read.All", "https://analysis.windows.net/powerbi/api/Report.Read.All", "https://analysis.windows.net/powerbi/api/Dashboard.Read.All", "https://analysis.windows.net/powerbi/api/Dataset.Read.All" }; AuthenticationResult authResult; try { // 优先用MSAL缓存静默获取 var account = await appConfidential.GetAccountAsync(userAccountId); authResult = await appConfidential.AcquireTokenSilent(scopes, account).ExecuteAsync(); } catch (MsalUiRequiredException) { // 缓存失效时用刷新令牌获取 authResult = await appConfidential.AcquireTokenByRefreshToken(scopes, refreshToken).ExecuteAsync(); } // 更新Claims中的令牌信息 identity.RemoveClaim(identity.FindFirst("Access_Token")); identity.RemoveClaim(identity.FindFirst("AccessToken_ExpiresOn")); identity.RemoveClaim(identity.FindFirst("Refresh_Token")); identity.AddClaim(new Claim("Access_Token", authResult.AccessToken)); identity.AddClaim(new Claim("AccessToken_ExpiresOn", authResult.ExpiresOn.ToUnixTimeSeconds().ToString())); identity.AddClaim(new Claim("Refresh_Token", authResult.RefreshToken)); // 更新用户认证Cookie var authenticationManager = HttpContext.Current.GetOwinContext().Authentication; authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant( new ClaimsPrincipal(identity), new AuthenticationProperties { IsPersistent = true } ); return authResult.AccessToken; }
调用说明
后续所有调用Power BI Rest API的位置,都不再直接从Claims取访问令牌,而是调用上述GetValidPowerBiAccessToken方法获取有效令牌即可,只要刷新令牌未过期,全程无需用户交互式登录。
内容的提问来源于stack exchange,提问作者PNDev
相关产品推荐
相关产品推荐

