Blazor Server Teams应用:如何结合AuthorizeRouteView实现Teams身份验证?
核心思路
要将Teams身份验证与Blazor的AuthorizeRouteView绑定,需要自定义AuthenticationStateProvider,让Blazor的授权系统识别Teams的用户身份;同时要验证应用是否在Teams环境内运行,拒绝外部访问。
1. 自定义TeamsAuthenticationStateProvider
这个类负责向Blazor提供当前用户的认证状态,整合Teams的teamsUserCredential逻辑:
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Identity.Client; using Microsoft.TeamsFx; using Microsoft.Graph; using System.Security.Claims; public class TeamsAuthenticationStateProvider : AuthenticationStateProvider { private readonly TeamsUserCredential _teamsUserCredential; private readonly string[] _scopes; private ClaimsPrincipal _cachedUser; public TeamsAuthenticationStateProvider(TeamsUserCredential teamsUserCredential, string[] scopes) { _teamsUserCredential = teamsUserCredential; _scopes = scopes; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { // 优先返回缓存的用户身份 if (_cachedUser != null) { return new AuthenticationState(_cachedUser); } try { // 尝试获取现有令牌,验证登录状态 await _teamsUserCredential.GetTokenAsync(_scopes); // 获取用户信息(复用你现有逻辑) var graph = GetGraphServiceClient(_teamsUserCredential); var profile = await graph.Me.GetAsync(); // 构建ClaimsPrincipal供Blazor授权系统使用 var claims = new List<Claim> { new Claim(ClaimTypes.Name, profile.DisplayName), new Claim(ClaimTypes.Email, profile.Mail ?? profile.UserPrincipalName), new Claim(ClaimTypes.NameIdentifier, profile.Id) }; _cachedUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "TeamsAuth")); return new AuthenticationState(_cachedUser); } catch (MsalUiRequiredException) { // 未登录,返回未认证状态 return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); } catch (Exception) { // 其他错误,返回未认证状态 return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); } } // 触发Teams登录并更新认证状态 public async Task LoginAsync() { await _teamsUserCredential.LoginAsync(_scopes); // 通知Blazor重新检测认证状态 NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } // 复用你现有Graph客户端构建方法 private GraphServiceClient GetGraphServiceClient(TokenCredential tokenCredential) { return new GraphServiceClient(tokenCredential, _scopes); } }
2. 注册服务到DI容器
在Program.cs中注册自定义认证提供者和Teams相关服务:
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.TeamsFx; var builder = WebApplication.CreateBuilder(args); // 注册Teams SDK服务 builder.Services.AddTeamsFx(builder.Configuration); // 注册自定义认证提供者 builder.Services.AddScoped<AuthenticationStateProvider, TeamsAuthenticationStateProvider>(sp => { var teamsCredential = sp.GetRequiredService<TeamsUserCredential>(); var scopes = new[] { "User.Read" }; // 替换为你的实际权限范围 return new TeamsAuthenticationStateProvider(teamsCredential, scopes); }); // 添加Blazor授权核心服务 builder.Services.AddAuthorizationCore(); // 常规Blazor服务注册 builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); var app = builder.Build(); // 中间件配置 app.UseStaticFiles(); app.UseRouting(); app.MapBlazorHub(); app.MapFallbackToPage("/_Host"); app.Run();
3. 修改App.razor使用AuthorizeRouteView
替换默认的RouteView为AuthorizeRouteView,配置未授权/未认证时的提示:
<CascadingAuthenticationState> <Router AppAssembly="@typeof(App).Assembly"> <Found Context="routeData"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> <NotAuthorized> <div class="alert alert-danger mt-4"> 你没有权限访问此页面。 </div> </NotAuthorized> <Authorizing> <div class="text-center mt-5"> 正在验证身份... </div> </Authorizing> </AuthorizeRouteView> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p class="mt-4">抱歉,未找到该页面。</p> </LayoutView> </NotFound> </Router> </CascadingAuthenticationState>
4. 限制仅Teams内访问
在MainLayout.razor中添加Teams环境检测逻辑,拒绝外部访问:
@inject IJSRuntime JSRuntime @inject NavigationManager NavigationManager @implements IAsyncDisposable @code { private IJSObjectReference _teamsSdk; protected override async Task OnInitializedAsync() { try { // 加载Teams JS SDK并初始化 _teamsSdk = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "https://res.cdn.office.net/teams-js/2.16.0/js/MicrosoftTeams.min.js"); await _teamsSdk.InvokeVoidAsync("initialize"); // 尝试获取Teams上下文,验证是否在Teams内 await _teamsSdk.InvokeAsync<object>("getContext"); } catch (Exception) { // 无法加载SDK或获取上下文,跳转至提示页 NavigationManager.NavigateTo("/not-in-teams"); } } public async ValueTask DisposeAsync() { if (_teamsSdk != null) { await _teamsSdk.DisposeAsync(); } } }
创建NotInTeams.razor提示页:
@page "/not-in-teams" <h3 class="mt-4">请在Microsoft Teams内打开此应用</h3> <p>本应用仅支持在Microsoft Teams客户端或Teams网页版中访问。</p>
5. 在页面中触发登录
如果用户未认证,可在页面或布局中添加登录触发逻辑:
@inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager NavigationManager @code { private bool _isAuthenticated; protected override async Task OnInitializedAsync() { var authState = await AuthStateProvider.GetAuthenticationStateAsync(); _isAuthenticated = authState.User.Identity.IsAuthenticated; if (!_isAuthenticated) { // 触发Teams登录弹窗 await ((TeamsAuthenticationStateProvider)AuthStateProvider).LoginAsync(); // 登录后跳转回原页面 NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); } } }
内容的提问来源于stack exchange,提问作者Invop
相关产品推荐
相关产品推荐

