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

IdentityServer4中React客户端令牌无法通过C# API授权问题

问题描述

React应用通过IdentityServer4获取Access Token后,将其作为Bearer Token发送至C# API接口时始终返回未授权状态,需解决两个IdentityServer4客户端验证同一令牌的问题。

相关配置信息

IdentityServer配置

using IdentityModel;
using IdentityServer4;
using IdentityServer4.Models;
using System.Collections.Generic;

namespace IdentityServer
{
    public class Config
    {
        public static IEnumerable<Client> Clients =>
            new Client[]
            {
                new Client
                {
                    ClientId = "react_client",
                    ClientName = "React Client",
                    AllowedGrantTypes = GrantTypes.Code,
                    RequireClientSecret = false,
                    RedirectUris = { "http://localhost:3000/registration/callback" },
                    PostLogoutRedirectUris = { "http://localhost:3000/registration/" },
                    AllowedCorsOrigins = { "https://localhost:3001" },
                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "api"
                    },
                    AllowAccessTokensViaBrowser = true,
                    RequirePkce = true
                },
                new Client
                {
                    ClientId = "api",
                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    ClientSecrets = { new Secret("api_secret".Sha256()) },
                    AllowedScopes = { "api" }
                }
            };

        public static IEnumerable<ApiScope> ApiScopes =>
            new ApiScope[]
            {
                new ApiScope("api", "API")
            };

        public static IEnumerable<ApiResource> ApiResources =>
            new ApiResource[]
            {
                new ApiResource("api", "My API")
                {
                    Scopes = { "api" }
                }
            };

        public static IEnumerable<IdentityResource> IdentityResources =>
            new IdentityResource[]
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email()
            };
    }
}

React应用的C#后端Program.cs认证配置

builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "https://localhost:7014";
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false,
            ValidateIssuerSigningKey = false, // 禁用签名密钥验证
            ValidateIssuer = false, // 测试时可禁用颁发者验证
            ValidateLifetime = false // 仍验证令牌有效期
        };
    });

受保护的C#控制器

[Authorize]
[HttpGet]
public async Task<ActionResult<UserDTO>> GetCurrentUser()
{
    // 通过Email声明查找用户
    var email = User.FindFirstValue(ClaimTypes.Email);
    var user = await _userManager.FindByEmailAsync(email);

    if (user == null)
    {
        return NotFound("User not found");
    }

    UserDTO userDTO = new UserDTO
    {
        Mail = user.Email,
        UserName = user.UserName,
        DisplayName = User.FindFirstValue(ClaimTypes.Name)
    };

    return Ok(userDTO);
}

获取到的Access Token

eyJhbGciOiJSUzI1NiIsImtpZCI6IjMzQjZGRUEyRjQ4QUMxRURERTY2OTdGRkVDREQ1NUMzIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE3MDM4MDQzMjQsImV4cCI6MTcwMzgwNzkyNCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NzAxNCIsImNsaWVudF9pZCI6InJlYWN0X2NsaWVudCIsInN1YiI6IkE1NUQyMUFBQjY2ODYwNERGNjc1Njk2M0M4RkQ3N0Y3NkI5Njg1NTA4QUM3RTRFQTEwNTJEQkFFNUIwMjlENTMiLCJhdXRoX3RpbWUiOjE3MDM4MDQzMjMsImlkcCI6IkF6dXJlQUQiLCJlbWFpbCI6IkJEZWxsaW5nZXJAaG9zc3JvYi5vbm1pY3Jvc29mdC5jb20iLCJuYW1lIjoiQnJ5YW4gRGVsbGluZ2VyIiwianRpIjoiQUExREJCODk0N0ZBQ0FCQjdERUVGQkUyRDE1QjI5NDIiLCJzaWQiOiIwODdCRDE2MjgyODNDN0Q1NzJEMkM5QkQ4NTI4MzlBNyIsImlhdCI6MTcwMzgwNDMyNCwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSJdLCJhbXIiOlsiZXh0ZXJuYWwiXX0.mpV-UYXFUrOVg91MwgBtbnF8I3p0GSFP9L1MSeeEkFWMUcXgpq88a9xrcH4DtDpiOUh9nv78bjb9ocaFvUD_DMS2ZStMcB2vVNBos72gDqMMChaobnlkNOLHDU1nG7B7Nh4FoJzDo3Isl0IxlUTeXmUC0OksbpbSyj66fZUx5y61RR8Vr6dicsXaq7E6VeN2e8zdT-_ssvozxe9R-jRkarjLarxo2KB3tRb-ysAP3jA5wDvG9rKqvv1acRIeKs9dprvUN2-3JvzRXpj2uobpBPkFqdokHf4joWiTlN-dP9S_NYzsBzgSM4w2orr83REb6KWNTkZKWx3o6nMoSiozzQ

问题分析

  1. 令牌缺少API访问权限:解析提供的Token可见,scope字段仅包含openid和profile,未包含api scope,导致API验证时判定令牌无访问权限。
  2. API认证配置逻辑异常:当前配置过度禁用签名密钥、颁发者等关键验证项,虽为测试场景,但可能引发验证逻辑失效;同时未正确配置受众(Audience)验证,无法匹配IdentityServer中定义的ApiResource。
  3. 客户端Scope请求缺失:React客户端已在IdentityServer配置中允许api scope,但实际请求令牌时未包含该scope,导致令牌未携带对应权限声明。

解决方案

1. 确保React客户端请求正确Scope

在React应用的OIDC配置中,明确指定请求的scope包含api,示例:

// 以oidc-client-js为例
const config = {
  authority: 'https://localhost:7014',
  client_id: 'react_client',
  redirect_uri: 'http://localhost:3000/registration/callback',
  response_type: 'code',
  scope: 'openid profile api' // 必须包含api
};

2. 修正API的JWT验证配置

恢复必要的验证项,正确配置受众,示例:

using Microsoft.AspNetCore.Authentication.JwtBearer;

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://localhost:7014";
        options.Audience = "api"; // 与IdentityServer中ApiResource的名称一致
        // 生产环境需启用所有验证项,测试环境可根据需求调整
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            ValidateIssuer = true,
            ValidateLifetime = true
        };
    });

3. 启用API中间件

在Program.cs中按顺序添加认证和授权中间件:

app.UseAuthentication();
app.UseAuthorization();

4. 验证令牌内容

使用JWT解析工具检查令牌的scope字段是否包含api,aud字段是否包含api(若启用受众验证)。

5. 可选:优化IdentityServer配置

若需令牌携带用户声明(如Email),可在ApiResource中添加用户声明:

new ApiResource("api", "My API")
{
    Scopes = { "api" },
    UserClaims = { JwtClaimTypes.Email, JwtClaimTypes.Name }
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 10:54:52