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

如何限制ASP.NET Core Identity仅开放登录注册?Blazor Wasm应用问题

实现方案:限制Identity功能并迁移账户管理/登录注册到Blazor UI

一、限制Identity仅开放Login和Register

1. 移除无关页面

直接删除Server项目中Areas/Identity/Pages/Account目录下除Login.cshtml、Register.cshtml之外的所有文件(如Manage文件夹、ForgotPassword.cshtml等),从根源上禁用账户管理相关页面的访问。

2. 路由权限加固

在Server项目的Program.cs中配置Razor Pages路由规则,确保仅允许匿名访问登录、注册页面,其他Identity路由需授权(或直接拦截):

builder.Services.Configure<RazorPagesOptions>(options =>
{
    // 允许匿名访问登录、注册
    options.Conventions.AllowAnonymousToAreaPage("Identity", "/Account/Login");
    options.Conventions.AllowAnonymousToAreaPage("Identity", "/Account/Register");
    // 其他Account下的路由强制授权(无权限则无法访问)
    options.Conventions.AuthorizeAreaFolder("Identity", "/Account");
});

二、在Blazor Wasm端实现账户管理

账户管理依赖Server端API提供数据操作能力,客户端负责UI交互,步骤如下:

1. Server端编写账户管理API

创建AccountManagementController,注入UserManager<IdentityUser>实现核心逻辑:

[Authorize]
[ApiController]
[Route("api/account")]
public class AccountManagementController : ControllerBase
{
    private readonly UserManager<IdentityUser> _userManager;

    public AccountManagementController(UserManager<IdentityUser> userManager)
    {
        _userManager = userManager;
    }

    // 修改密码接口
    [HttpPost("changepassword")]
    public async Task<IActionResult> ChangePassword(ChangePasswordRequest request)
    {
        var user = await _userManager.GetUserAsync(User);
        if (user == null) return NotFound("用户不存在");

        var result = await _userManager.ChangePasswordAsync(user, request.OldPassword, request.NewPassword);
        if (!result.Succeeded)
        {
            foreach (var error in result.Errors)
                ModelState.AddModelError(string.Empty, error.Description);
            return BadRequest(ModelState);
        }

        return Ok("密码修改成功");
    }

    // 获取当前用户信息接口
    [HttpGet("profile")]
    public async Task<IActionResult> GetProfile()
    {
        var user = await _userManager.GetUserAsync(User);
        if (user == null) return NotFound();

        return Ok(new 
        { 
            user.Email, 
            user.UserName, 
            user.PhoneNumber 
        });
    }

    // 其他接口:更新个人信息、绑定手机号等
}

public class ChangePasswordRequest
{
    [Required] public string OldPassword { get; set; }
    [Required, StringLength(100, MinimumLength = 6)] public string NewPassword { get; set; }
    [Compare("NewPassword")] public string ConfirmPassword { get; set; }
}

2. Blazor端编写账户管理组件

以修改密码组件为例(ChangePassword.razor):

@page "/account/changepassword"
@inject HttpClient Http
@inject NavigationManager NavManager

<h3>修改密码</h3>

<EditForm Model="@request" OnValidSubmit="@HandleChangePassword">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <div class="mb-3">
        <label class="form-label">旧密码</label>
        <InputPassword @bind-Value="request.OldPassword" class="form-control" />
    </div>
    <div class="mb-3">
        <label class="form-label">新密码</label>
        <InputPassword @bind-Value="request.NewPassword" class="form-control" />
    </div>
    <div class="mb-3">
        <label class="form-label">确认新密码</label>
        <InputPassword @bind-Value="request.ConfirmPassword" class="form-control" />
    </div>

    <button type="submit" class="btn btn-primary">确认修改</button>
</EditForm>

@code {
    private ChangePasswordRequest request = new();

    private async Task HandleChangePassword()
    {
        var response = await Http.PostAsJsonAsync("api/account/changepassword", request);
        if (response.IsSuccessStatusCode)
        {
            NavManager.NavigateTo("/account/profile");
        }
        else
        {
            var errors = await response.Content.ReadFromJsonAsync<Dictionary<string, string[]>>();
            // 处理并显示错误信息
        }
    }

    private class ChangePasswordRequest
    {
        [Required] public string OldPassword { get; set; }
        [Required, StringLength(100, MinimumLength = 6)] public string NewPassword { get; set; }
        [Compare("NewPassword")] public string ConfirmPassword { get; set; }
    }
}

3. 配置HttpClient认证拦截

确保Blazor端的HttpClient能自动携带JWT令牌,在Wasm项目的Program.cs中配置:

builder.Services.AddHttpClient("ServerAPI", client => 
    client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => 
    sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));

三、用Blazor UI替代Identity的登录/注册

完全可以用Blazor组件实现登录注册,步骤如下:

1. 禁用Identity默认登录注册页面

删除Server项目中Areas/Identity/Pages/Account/Login.cshtml和Register.cshtml,或在路由配置中拦截访问。

2. Blazor端编写登录组件

以密码模式为例(需确保OpenIddict已启用密码授权类型):

@page "/login"
@inject HttpClient Http
@inject NavigationManager NavManager
@inject AuthenticationStateProvider AuthState

<h3>用户登录</h3>

<EditForm Model="@loginModel" OnValidSubmit="@HandleLogin">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <div class="mb-3">
        <label class="form-label">邮箱</label>
        <InputText @bind-Value="loginModel.Email" class="form-control" />
    </div>
    <div class="mb-3">
        <label class="form-label">密码</label>
        <InputPassword @bind-Value="loginModel.Password" class="form-control" />
    </div>

    <button type="submit" class="btn btn-primary">登录</button>
    <p class="mt-2">还没有账户?<a href="/register">立即注册</a></p>
</EditForm>

@code {
    private LoginModel loginModel = new();

    private async Task HandleLogin()
    {
        var tokenRequest = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["grant_type"] = "password",
            ["username"] = loginModel.Email,
            ["password"] = loginModel.Password,
            ["client_id"] = "BlazorClient", // 你的OpenIddict客户端ID
            ["scope"] = "openid profile api" // 匹配OpenIddict配置的作用域
        });

        var response = await Http.PostAsync("connect/token", tokenRequest);
        if (response.IsSuccessStatusCode)
        {
            var token = await response.Content.ReadFromJsonAsync<TokenResponse>();
            // 更新认证状态
            await ((ApiAuthorizationProvider)AuthState).NotifyUserAuthenticated(token.AccessToken);
            NavManager.NavigateTo("/");
        }
        else
        {
            var error = await response.Content.ReadAsStringAsync();
            // 显示登录失败信息
        }
    }

    private class LoginModel
    {
        [Required, EmailAddress] public string Email { get; set; }
        [Required] public string Password { get; set; }
    }

    private class TokenResponse
    {
        public string AccessToken { get; set; }
        public string TokenType { get; set; }
        public int ExpiresIn { get; set; }
    }
}

3. Blazor端编写注册组件

调用Server端注册API完成用户创建:

@page "/register"
@inject HttpClient Http
@inject NavigationManager NavManager

<h3>用户注册</h3>

<EditForm Model="@registerModel" OnValidSubmit="@HandleRegister">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <div class="mb-3">
        <label class="form-label">邮箱</label>
        <InputText @bind-Value="registerModel.Email" class="form-control" />
    </div>
    <div class="mb-3">
        <label class="form-label">密码</label>
        <InputPassword @bind-Value="registerModel.Password" class="form-control" />
    </div>
    <div class="mb-3">
        <label class="form-label">确认密码</label>
        <InputPassword @bind-Value="registerModel.ConfirmPassword" class="form-control" />
    </div>

    <button type="submit" class="btn btn-primary">注册</button>
    <p class="mt-2">已有账户?<a href="/login">立即登录</a></p>
</EditForm>

@code {
    private RegisterModel registerModel = new();

    private async Task HandleRegister()
    {
        var response = await Http.PostAsJsonAsync("api/account/register", registerModel);
        if (response.IsSuccessStatusCode)
        {
            NavManager.NavigateTo("/login");
        }
        else
        {
            var errors = await response.Content.ReadFromJsonAsync<Dictionary<string, string[]>>();
            // 显示注册失败信息
        }
    }

    private class RegisterModel
    {
        [Required, EmailAddress] public string Email { get; set; }
        [Required, StringLength(100, MinimumLength = 6)] public string Password { get; set; }
        [Compare("Password")] public string ConfirmPassword { get; set; }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 07:20:47