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

.NET Core IdentityServer4资源所有者密码模式无法调用API

IdentityServer资源所有者密码模式认证问题解决(.NET Core WebAPI + React Native)

基于VS .NET Core WebAPI + Angular模板开发的Web应用,Angular前端的IdentityServer认证已配置正常,现需为React Native应用配置Resource Owner Password模式认证。目前能从IdentityServer获取access_token,但调用带[Authorize]属性的策略授权API时,服务器返回404 Not Found,实际为授权问题。

初始IdentityServer配置(Program.cs)

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
    {
        options.Clients[0].AllowOfflineAccess = true;
        options.Clients[0].AllowedScopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess);

        options.Clients.Add(new Client
        {
            ClientId = "Willness.Mobile",
            ClientName = "Willness.Mobile",
            ClientSecrets = { new Duende.IdentityServer.Models.Secret("pwd".Sha256()) },
            AllowedGrantTypes = { GrantType.ResourceOwnerPassword },
            AllowedScopes = {
                "WillnessAPI",
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                IdentityServerConstants.StandardScopes.OfflineAccess
            },
            AllowAccessTokensViaBrowser = true,
            AllowOfflineAccess = true
        });
    })
    .AddProfileService<ProfileService>();

排查发现的问题

  • 通过Postman可成功获取token,但调用授权API时返回404(实际为授权失败)
  • Angular应用的token经验证正常,Postman获取的token虽包含正确claims但存在异常
  • 在AuthorizationHandler中,Angular请求的身份信息正常,Postman请求的身份信息为null

解决方案

1. 实现自定义ResourceOwnerPasswordValidator

public class ResourceOwnerPasswordValidator<T> : IResourceOwnerPasswordValidator where T : ApplicationIdentityUserBase
{
    readonly UserManager<T> _userManager;
    readonly SignInManager<T> _signInManager;
    readonly IUserClaimsPrincipalFactory<T> _claimsFactory;

    public ResourceOwnerPasswordValidator(UserManager<T> userManager, SignInManager<T> signInManager, IUserClaimsPrincipalFactory<T> claimsFactory)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _claimsFactory = claimsFactory;
    }

    public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        var username = context.UserName;
        var password = context.Password;

        var user = await _userManager.FindByNameAsync(username) ?? await _userManager.FindByEmailAsync(username);
        if (user != null && !user.Deleted)
        {
            var result = await _signInManager.PasswordSignInAsync(user.UserName, password, false, false);
            if (result.Succeeded)
            {
                var principal = await _claimsFactory.CreateAsync(user);
                var claims = principal.Claims.Distinct(new ClaimComparer()).ToList();

                var claimsInDb = await _userManager.GetClaimsAsync(user);
                foreach (var claim in claimsInDb)
                {
                    if (!claims.Any(c => c.Type == claim.Type && c.Value == claim.Value))
                        claims.Add(claim);
                }

                // 标记验证成功
                context.Result = new GrantValidationResult(
                subject: user.Id,
                authenticationMethod: GrantType.ResourceOwnerPassword,
                claims: claims);

                return;
            }
        }

        // 标记验证失败
        context.Result = new GrantValidationResult(
            TokenRequestErrors.UnauthorizedClient, "Invalid credentials");
    }
}

2. 在Program.cs中注册自定义验证器

在IdentityServer配置中添加自定义验证器的注册:

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
    {
        options.Clients[0].AllowOfflineAccess = true;
        options.Clients[0].AllowedScopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess);

        options.Clients.Add(new Client
        {
            ClientId = "Willness.Mobile",
            ClientName = "Willness.Mobile",
            ClientSecrets = { new Duende.IdentityServer.Models.Secret("pwd".Sha256()) },
            AllowedGrantTypes = { GrantType.ResourceOwnerPassword },
            AllowedScopes = {
                "WillnessAPI",
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                IdentityServerConstants.StandardScopes.OfflineAccess
            },
            AllowAccessTokensViaBrowser = true,
            AllowOfflineAccess = true
        });
    })
    .AddResourceOwnerValidator<ResourceOwnerPasswordValidator<ApplicationUser>>() // 新增行
    .AddProfileService<ProfileService>();

3. 确保ProfileService的IsActiveAsync方法返回正确值

修改ProfileService中的IsActiveAsync方法,确保正确标记用户状态为活跃:

public async Task IsActiveAsync(IsActiveContext context)
{
    var sub = context.Subject.GetSubjectId();
    var user = await _userManager.FindByIdAsync(sub) ?? await _userManager.FindByNameAsync(sub);
    context.IsActive = user != null;
}

配置完成后,可获取有效access_token并成功调用授权API,实现IdentityServer的ResourceOwnerPassword模式认证。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 06:41:32