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

基于IdentityServer4的多服务授权配置问题求助

Alright, let's break this down—you're running into a common pitfall with IdentityServer4 flow selection. Here's what's wrong and how to fix it:

The core issue is that you've configured both Service A and B as Implicit flow clients, but Implicit flow is designed for browser-based/frontend apps, not server-to-server API calls. For Service A to call Service B's authorized endpoints without forcing the user to re-authenticate, we need to reconfigure B as an API Resource in IdentityServer, and set up A to request and pass a valid access token when calling B.


Step 1: Update IdentityServer4 Configuration

First, we'll register Service B as an API resource and update Service A's client settings to allow it to access B's API.

Configure Service B as an API Resource

In your IdentityServer's Config.cs (or equivalent config class), add Service B as an API resource:

public static IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        new ApiResource("service_b_api", "Service B Protected API")
        {
            // Add granular scopes if you need fine-grained access control
            Scopes = { new Scope("service_b_api.full_access") }
        }
    };
}

Update Service A's Client Configuration

Modify Service A's client setup to use Authorization Code flow (more secure for server-side apps than Implicit) and include the scope for Service B's API:

public static IEnumerable<Client> GetClients()
{
    return new List<Client>
    {
        new Client
        {
            ClientId = "service_a",
            ClientName = "Service A (Server-Side App)",
            AllowedGrantTypes = GrantTypes.Code,
            ClientSecrets = { new Secret("service_a_secure_secret".Sha256()) },
            RedirectUris = { "https://localhost:xxxx/signin-oidc" }, // Your A's OIDC callback URL
            PostLogoutRedirectUris = { "https://localhost:xxxx/signout-callback-oidc" },
            AllowedScopes = {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                "service_b_api.full_access" // Grant access to Service B's API
            },
            AllowOfflineAccess = true, // Enable refresh tokens for longer-lived access
            AccessTokenLifetime = 3600 // Adjust token expiry as needed
        }
    };
}

Step 2: Configure Service A to Pass Access Tokens to Service B

Service A needs to retrieve a valid access token for Service B's API and attach it to outgoing requests. We'll use an HTTP client handler to automate this.

Update Service A's Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddHttpContextAccessor(); // Required to access current user's tokens

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.Authority = "https://localhost:yyyy"; // Your IdentityServer URL
        options.ClientId = "service_a";
        options.ClientSecret = "service_a_secure_secret";
        options.ResponseType = "code"; // Use Authorization Code flow
        options.Scope.Add("service_b_api.full_access"); // Request access to B's API
        options.SaveTokens = true; // Store tokens in the authentication cookie
        options.GetClaimsFromUserInfoEndpoint = true;
    });

    // Register a named HTTP client for Service B, with token attachment logic
    services.AddHttpClient("ServiceBClient", client =>
    {
        client.BaseAddress = new Uri("https://localhost:zzzz/"); // Service B's base URL
    })
    .AddHttpMessageHandler<BearerTokenAttachmentHandler>();

    // Register the custom handler to attach access tokens
    services.AddTransient<BearerTokenAttachmentHandler>();
}

// Custom handler to automatically add the Bearer token to requests
public class BearerTokenAttachmentHandler : DelegatingHandler
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public BearerTokenAttachmentHandler(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
        if (!string.IsNullOrEmpty(accessToken))
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        }
        return await base.SendAsync(request, cancellationToken);
    }
}

Use the Client in Service A's Controller

public class HomeController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

    public HomeController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<IActionResult> CallProtectedServiceB()
    {
        var client = _httpClientFactory.CreateClient("ServiceBClient");
        var response = await client.GetAsync("api/protected-data");
        response.EnsureSuccessStatusCode();
        var content = await response.Content.ReadAsStringAsync();
        return View("ServiceBResult", content);
    }
}

Step 3: Configure Service B to Validate IdentityServer Tokens

Service B needs to accept and validate the Bearer token from Service A using JWT Bearer authentication.

Update Service B's Startup.cs

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

    services.AddAuthentication("Bearer")
        .AddJwtBearer("Bearer", options =>
        {
            options.Authority = "https://localhost:yyyy"; // Your IdentityServer URL
            options.Audience = "service_b_api"; // Must match the API resource name in IdentityServer
            options.RequireHttpsMetadata = true; // Disable only in development
        });

    // Set up default authorization policy
    services.AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ... other middleware (exception handling, static files, etc.)

    app.UseRouting();

    app.UseAuthentication(); // Must come before UseAuthorization
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

Step 4: Use [Authorize] in Service B

Now you can safely add the [Authorize] attribute to Service B's controllers/actions—it will validate the Bearer token from Service A:

[ApiController]
[Route("api")]
public class ProtectedDataController : ControllerBase
{
    [Authorize]
    [HttpGet("protected-data")]
    public IActionResult GetProtectedData()
    {
        var userName = User.Identity.Name;
        var userRoles = User.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value);
        return Ok($"Hello {userName}! Your roles: {string.Join(", ", userRoles)} (from Service B's protected endpoint)");
    }
}

Key Takeaways

  • Why Implicit Flow failed: Implicit flow returns tokens directly to browsers, which aren't designed for server-side services to store and use for API calls. Authorization Code flow is the secure choice for server-to-server scenarios.
  • [Authorize] works perfectly here: As long as Service B can validate the incoming Bearer token, the [Authorize] attribute will restrict access to authenticated users exactly as intended.
  • Granular access control: If you need to limit Service A's access to specific parts of Service B, define more scopes in IdentityServer and use [Authorize(Policy = "RequireAdminScope")] with a policy that checks the scope claim.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 06:27:29