You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于C# Microsoft Identity的Blazor Server内置授权UI实现代码咨询

Blazor Server 接入Azure AD后应用内授权管理UI实现方案

你已经完成Azure AD身份校验的前提下,仅需要扩展授权逻辑即可在自有应用内实现全部角色、用户的管理操作,无需跳转至Azure门户。以下是可直接参考的实现代码:


第一步:扩展身份声明,同步角色到登录上下文

首先自定义声明转换器,将用户的角色信息(无论存储在本地数据库还是Azure AD中)附加到登录用户的Claims中,方便后续授权校验:

public class CustomClaimsTransformer : IClaimsTransformation
{
    private readonly IUserRoleService _userRoleService;

    public CustomClaimsTransformer(IUserRoleService userRoleService)
    {
        _userRoleService = userRoleService;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        if (principal.Identity?.IsAuthenticated == true)
        {
            // 从Azure AD返回的声明中获取用户唯一标识
            var userOid = principal.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier");
            if (!string.IsNullOrEmpty(userOid))
            {
                // 读取用户所属角色,支持从本地DB或者Azure Graph API读取
                var userRoles = await _userRoleService.GetUserRolesAsync(userOid);
                foreach (var role in userRoles)
                {
                    ((ClaimsIdentity)principal.Identity).AddClaim(new Claim(ClaimTypes.Role, role));
                }
            }
        }
        return principal;
    }
}

在Program.cs中注册服务:

builder.Services.AddScoped<IClaimsTransformation, CustomClaimsTransformer>();
// 注册自定义的用户角色服务,可根据存储位置替换实现类
builder.Services.AddScoped<IUserRoleService, UserRoleService>();

第二步:角色管理页面实现

创建Pages/Admin/RoleManagement.razor组件,实现角色的增删改查操作:

@page "/admin/roles"
@attribute [Authorize(Roles = "Admin")]
@inject IUserRoleService UserRoleService

<h3>角色管理</h3>

<!-- 新增角色区域 -->
<div class="mb-3">
    <input @bind="newRoleName" placeholder="输入角色名称" class="form-control w-25 d-inline me-2" />
    <button class="btn btn-primary" @onclick="AddNewRole">新增角色</button>
</div>

<!-- 角色列表 -->
<table class="table table-striped">
    <thead>
        <tr>
            <th>角色名称</th>
            <th>关联用户数</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var role in roleList)
        {
            <tr>
                <td>@role.RoleName</td>
                <td>@role.UserCount</td>
                <td>
                    <button class="btn btn-sm btn-outline-secondary me-2" @onclick="() => EditRole(role)">编辑</button>
                    <button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteRole(role.RoleId)">删除</button>
                </td>
            </tr>
        }
    </tbody>
</table>

@code {
    private string newRoleName;
    private List<RoleDto> roleList = new();

    protected override async Task OnInitializedAsync()
    {
        roleList = await UserRoleService.GetAllRolesAsync();
    }

    private async Task AddNewRole()
    {
        if (!string.IsNullOrWhiteSpace(newRoleName))
        {
            await UserRoleService.CreateRoleAsync(newRoleName);
            newRoleName = string.Empty;
            roleList = await UserRoleService.GetAllRolesAsync();
        }
    }

    private async Task EditRole(RoleDto role)
    {
        // 可扩展弹窗实现角色名称、权限配置修改
        await UserRoleService.UpdateRoleAsync(role);
        roleList = await UserRoleService.GetAllRolesAsync();
    }

    private async Task DeleteRole(Guid roleId)
    {
        await UserRoleService.DeleteRoleAsync(roleId);
        roleList = await UserRoleService.GetAllRolesAsync();
    }
}

第三步:用户角色分配页面实现

创建Pages/Admin/UserRoleAssignment.razor组件,实现用户搜索、角色分配操作:

@page "/admin/user-roles"
@attribute [Authorize(Roles = "Admin")]
@inject IUserRoleService UserRoleService
@inject IGraphClientService GraphClientService

<h3>用户角色分配</h3>

<!-- 用户搜索 -->
<div class="mb-3">
    <input @bind="searchKeyword" placeholder="输入用户姓名/邮箱搜索" class="form-control w-25 d-inline me-2" />
    <button class="btn btn-primary" @onclick="SearchUsers">搜索</button>
</div>

<!-- 用户列表 -->
<table class="table table-striped">
    <thead>
        <tr>
            <th>用户姓名</th>
            <th>用户邮箱</th>
            <th>当前角色</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var user in userList)
        {
            <tr>
                <td>@user.DisplayName</td>
                <td>@user.UserPrincipalName</td>
                <td>@string.Join("、", user.AssignedRoles)</td>
                <td>
                    <button class="btn btn-sm btn-outline-primary" @onclick="() => OpenAssignRoleModal(user)">分配角色</button>
                </td>
            </tr>
        }
    </tbody>
</table>

<!-- 角色分配弹窗 -->
@if (selectedUser != null)
{
    <div class="modal show d-block" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">为 @selectedUser.DisplayName 分配角色</h5>
                    <button type="button" class="btn-close" @onclick="CloseModal"></button>
                </div>
                <div class="modal-body">
                    @foreach (var role in allRoles)
                    {
                        <div class="form-check">
                            <input class="form-check-input" type="checkbox" 
                                   checked="@selectedUser.AssignedRoles.Contains(role.RoleName)"
                                   @onchange="(e) => ToggleRoleSelection(role.RoleName, (bool)e.Value)" />
                            <label class="form-check-label">@role.RoleName</label>
                        </div>
                    }
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" @onclick="CloseModal">取消</button>
                    <button type="button" class="btn btn-primary" @onclick="SaveRoleAssignment">保存</button>
                </div>
            </div>
        </div>
    </div>
}

@code {
    private string searchKeyword;
    private List<AdUserDto> userList = new();
    private List<RoleDto> allRoles = new();
    private AdUserDto selectedUser;
    private List<string> selectedRoles = new();

    protected override async Task OnInitializedAsync()
    {
        allRoles = await UserRoleService.GetAllRolesAsync();
    }

    private async Task SearchUsers()
    {
        // 调用Graph API搜索Azure AD用户,无需跳转Azure门户
        userList = await GraphClientService.SearchUsersAsync(searchKeyword);
        // 同步获取用户已分配的角色
        foreach (var user in userList)
        {
            user.AssignedRoles = await UserRoleService.GetUserRolesAsync(user.Oid);
        }
    }

    private void OpenAssignRoleModal(AdUserDto user)
    {
        selectedUser = user;
        selectedRoles = user.AssignedRoles.ToList();
    }

    private void ToggleRoleSelection(string roleName, bool isSelected)
    {
        if (isSelected)
        {
            if (!selectedRoles.Contains(roleName)) selectedRoles.Add(roleName);
        }
        else
        {
            selectedRoles.Remove(roleName);
        }
    }

    private async Task SaveRoleAssignment()
    {
        await UserRoleService.AssignRolesToUserAsync(selectedUser.Oid, selectedRoles);
        selectedUser.AssignedRoles = selectedRoles.ToList();
        CloseModal();
    }

    private void CloseModal()
    {
        selectedUser = null;
        selectedRoles.Clear();
    }
}

补充说明

  • 上述示例中的IUserRoleService、IGraphClientService为抽象服务,你可以根据自己的存储需求实现对应逻辑:如果需要把角色存在Azure AD中,就把服务实现替换为调用Microsoft Graph API的逻辑即可,前端UI无需修改
  • 页面访问权限可通过[Authorize]特性或者Blazor原生的<AuthorizeView>组件控制,仅允许管理员角色访问管理页面

内容的提问来源于stack exchange,提问作者NicolasN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 17:36:02