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

Identity Server 4:Postman可获取AccessToken,代码调用报invalid_grant

Identity Server 4 代码调用返回 invalid_grant 但Postman正常的问题

问题现象

基于Identity Server 4实现OAuth认证后,Postman中发起认证请求可正常获取AccessToken、Token Type、id_token、expires_in等信息,且能使用该AccessToken访问受保护API。但通过代码实现相同流程时,却返回invalid_grant错误。

认证流程

  1. 调用API设置患者上下文,将患者ID和GUID保存到数据库,该GUID作为launch参数值。
  2. 调用自定义/auth端点,传入Identity Server 4(ID4)/connect/authorize所需的所有参数及额外的launch参数以维持患者ID上下文,请求参数包含:client_id=client、response_type=code、scope=openid profile myAPI、client_secret=secret、state=1234567890p、aud=https://api.location.com、launch=K123K456Y7777、redirect_uri=https://test.azurewebsites.net/auth。该端点会在数据库中关联state与launch值以维持上下文。
  3. 上述端点随后调用ID4的/connect/authorize端点并传入对应参数,在认证流程中再次将sessionId与state关联以维持上下文。
  4. ID4的/connect/authorize端点按预期返回授权码(authorization code)、scope、state及session_state。
  5. 在上述authorize请求指定的redirectURI的Get方法中,获取授权码并向ID4的/connect/token端点发起标准POST请求。
  6. 请求返回invalid_grant错误。

以上流程在Postman中完全正常。

已配置的代码与参数

客户端配置

new Client
{
    ClientId = "client",
    ClientSecrets = { new Secret("secret".Sha256()) },

    //RequireClientSecret = false, //false is default

    RequirePkce = false, //to prevent 'code challenge required' message from appearing when using 'Code'

    AllowedGrantTypes = GrantTypes.Code ,//{ "code", "authorization_code" },// 
    
    // where to redirect to after login
    RedirectUris = { "https://IDS4.azurewebsites.net/signin-oidc", "https://test.azurewebsites.net/auth",
    "https://test.azurewebsites.net/token", "https://IDS4.azurewebsites.net/Account/Login"  },

    // where to redirect to after logout
    PostLogoutRedirectUris = { "https://IDS4.azurewebsites.net/signout-callback-oidc" },

    AllowedScopes = new List<string>
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,   
        "myAPI"
    }
}

Startup代码

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();

    services.AddDbContextPool<AppDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DBConnection")));

    var builder = services.AddIdentityServer()
        .AddInMemoryIdentityResources(Config.IdentityResources)
        .AddInMemoryApiScopes(Config.ApiScopes)
        .AddInMemoryClients(Config.Clients)
        .AddTestUsers(TestUsers.Users)
        .AddCustomTokenRequestValidator<CustomTokenRequestValidator>()
        .AddCustomAuthorizeRequestValidator<CustomAuthorizeRequestValidator>();
    

    builder.AddDeveloperSigningCredential();

    services.AddAuthentication()
        .AddGoogle("Google", options =>
        {
            options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;

            options.ClientId = "<insert here>";
            options.ClientSecret = "<insert here>";
        });

    
    services.AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_3_0).AddXmlSerializerFormatters()
        .AddMvcOptions(options => options.EnableEndpointRouting = false);

    services.AddScoped<IDebugRepository, SQLDebugRepository>();
    services.AddScoped<IPatientContextRepository, SQLPatientContextRepository>();
    services.AddScoped<IClinicAccessRepository, SQLClinicAccessRepository>();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseCookiePolicy(new CookiePolicyOptions
    {
        HttpOnly = HttpOnlyPolicy.None,
        MinimumSameSitePolicy = SameSiteMode.None,
        Secure = CookieSecurePolicy.Always
    });

    app.UseStaticFiles();
    app.UseRouting();

    app.UseIdentityServer();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapDefaultControllerRoute();
    });

}

获取授权码后请求Token的代码

[HttpGet]
public async Task<string> Get(string code, string scope, string state, string session_state)
{
    try
    {
        string grant_type = "authorization_code";
        string redirect_uri = "https://test.azurewebsites.net/token"; //sends the token back to requestor
        string client_id = "client"; //current stable testing client
        string baseAddress = $"https://IDS4.azurewebsites.net/connect/token";
        string client_secret = "secret";

        var client = new HttpClient();
        client.BaseAddress = new Uri($"https://IDS4.azurewebsites.net/");
        var content = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("client_id", client_id),
            new KeyValuePair<string, string>("client_secret", client_secret),
            new KeyValuePair<string, string>("grant_type", grant_type),
            new KeyValuePair<string, string>("code", code),
            new KeyValuePair<string, string>("redirect_uri", redirect_uri)
        });

        client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));

        var res = await client.PostAsync(baseAddress, content);
        var resp = await res.Content.ReadAsStringAsync();
        return resp;

    }
    catch (Exception ex)
    {
        return  ex.Message + Environment.NewLine + ex.StackTrace ;
    }
}

Token重定向测试代码

[Controller]
[Route("Token")]
[AllowAnonymous]
public class TokenController : Controller
{

    [HttpPost]
    public string Post([FromForm] string access_token,
                        [FromForm] string token_type,
                        [FromForm] string expires_in,
                        [FromForm] string scope,
                        [FromForm] string patient,
                        [FromForm] string id_token,
                        [FromForm] string oceanSharedEncryptionKey)
    {
        TokenResponseModel token = new TokenResponseModel
        {
            access_token = access_token,
            expires_in = expires_in,
            id_token = id_token,
            patient = patient,
            scope = scope,
            token_type = token_type
        };
        string rslt = JsonConvert.SerializeObject(token);
        return rslt;
    }

    public string Get( string test1)
    {
        return test1;
    }
}

当前排查方向

尚未尝试设置签名证书,猜测Postman使用内部证书且已接受,但记不太清。认为缺少证书早该引发问题,但这是正在排查的方向之一。


问题分析与解决方案

核心原因:Redirect URI不匹配

Identity Server 4要求,使用授权码交换token时,传入的redirect_uri必须和获取授权码时/connect/authorize请求中使用的redirect_uri完全一致,否则会直接返回invalid_grant。

你在获取授权码时指定的redirect_uri是https://test.azurewebsites.net/auth,但代码中请求/connect/token时传入的是https://test.azurewebsites.net/token,这就是代码失败、Postman正常的关键原因——Postman中你使用了和授权请求一致的redirect_uri。

修复步骤

修改请求/connect/token时的redirect_uri参数,使其与授权请求中的地址保持一致:

string redirect_uri = "https://test.azurewebsites.net/auth";

其他排查点

  1. 检查授权码是否被重复使用:授权码是一次性的,重复使用会返回invalid_grant。
  2. 验证会话关联逻辑:自定义的sessionId与state关联逻辑是否存在错误,导致ID4无法验证授权码有效性。
  3. 开启详细日志:在Startup中添加日志配置,查看/connect/token请求的具体错误详情:
services.AddLogging(logging =>
{
    logging.AddConsole();
    logging.SetMinimumLevel(LogLevel.Debug);
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 00:24:20