简体   繁体   中英

OpenID Connect AllowedRedirectUris empty resulting in “invalid redirect_uri” on IdentityServer4

I have a running IdentityServer4 on a .NET Core project, and I'm trying to make an existing .NET Framework web application a client of that server.

The project is using Microsoft.Owin.Security , and in the Startup.cs I setup the authentication settings.

However, when the request is sent to the IdentityServer, the AllowedRedirectUris parameters is just an empty array, so the RedirectUri parameter doesn't match one in that empty array, resulting in invalid redirect_uri error.

Here's the Startup.cs of the existing project:

using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;

namespace MyProject.Services
{
    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions {
                AuthenticationType = "Cookies"
            });

            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    RequireHttpsMetadata = false,
                    AuthenticationType = "oidc",
                    SignInAsAuthenticationType = "Cookies",
                    Authority = "http://localhost:5000",
                    RedirectUri = "http://localhost:8010/myproject.services/api/oidc",
                    PostLogoutRedirectUri = "http://localhost:8010/myproject.application",
                    ClientId = "CLIENT1",
                    ClientSecret = "a-local-testing-password",
                    Scope = "CLIENT1 offline_access",
                    ResponseType = "code id_token" 
                });
        }
    }
}

Here's the generated request from that configuration that was found in the logging:

IdentityServer4.Endpoints.AuthorizeEndpoint[0]
{
  "ClientId": "CLIENT1",
  "ClientName": "CLIENT1_name",
  "RedirectUri": "http://localhost:8010/myproject.services/api/oidc",
  "AllowedRedirectUris": [], <-- is empty, but should contain the RedirectUri
  "SubjectId": "anonymous",
  "RequestedScopes": "",
  "Raw": {
    "client_id": "CLIENT1",
    "redirect_uri": "http://localhost:8010/myproject.services/api/oidc",
    "response_mode": "form_post",
    "response_type": "code id_token",
    "scope": "openid profile",
    "state": "OpenIdConnect.AuthenticationProperties=[...]",
    "nonce": "[...]",
    "x-client-SKU": "ID_NET451",
    "x-client-ver": "5.2.1.0"
  }
}

So does anybody know how I can make the AllowedRedirectUris contain the given RedirectUri so that my IdentityServer4 would accept the request?

UPDATE : Here's the client object in the IdentityServer4 project:

                new IdentityServer4.Models.Client
                {
                    ClientId = "CLIENT1",
                    ClientName = "CLIENT1-name",
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                    ClientSecrets =
                    {
                        new IdentityServer4.Models.Secret("a-local-testing-password".Sha256())
                    },
                    RedirectUris = { "http://localhost:8010/myproject.services/api/oidc" },
                    PostLogoutRedirectUris = { "http://localhost:8010/myproject.application" },
                    AlwaysSendClientClaims = true,
                    AlwaysIncludeUserClaimsInIdToken = true,
                    AllowedScopes = {
                        "CLIENT1"
                    },
                    Properties = new Dictionary<string, string>
                    {
                        { "TenantName", "CLIENT1" },
                        { "ImageUrl", "/Images/CLIENT1.png" },
                    },
                    IdentityProviderRestrictions = {
                        "LocalOnly"
                    },
                    AllowOfflineAccess = true
                },

Update2 : Here's the Startup.cs of the IdentityServer4:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MyProject.STS4.ManagementSite.Data;
using MyProject.STS4.ManagementSite.Models;
using MyProject.STS4.ManagementSite.Services;
using MyProject.STS4.ManagementSite.Tenant;
using IdentityServer4.Services;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Reflection;
using IdentityServer4.EntityFramework.DbContexts;
using IdentityServer4.EntityFramework.Mappers;
using Microsoft.IdentityModel.Tokens;
using IdentityServer4;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Globalization;
using System.Security.Claims;
using MyProject.STS4.ManagementSite.Models.Mail;

namespace MyProject.STS4.ManagementSite
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Read this part of the appsettings.json and convert it into the SmtpConfig
            services.Configure<SmtpConfig>(Configuration.GetSection("Smtp"));

            string connectionString = Configuration.GetConnectionString("DefaultConnection");
            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

            services.AddDbContext<ApplicationDbContext>(options =>
                 options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                 sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly)
                 ));

            // Explicitly added
            //services.TryAddScoped<IUserClaimsPrincipalFactory<TenantUser>, UserClaimsPrincipalFactory<TenantUser, IdentityRole>>();

            services.AddIdentity<TenantUser, IdentityRole>(options =>
            {
                // This should send a mail when registering
                options.SignIn.RequireConfirmedEmail = false;

                options.User.RequireUniqueEmail = false;
                // Allows emails toe be used in the username
                options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
            })
              .AddEntityFrameworkStores<ApplicationDbContext>()
              .AddDefaultTokenProviders()
              .AddUserValidator<TenantUserValidator>(); // register our own validator

            // Add application services.
            services.AddTransient<IEmailSender, EmailSender>();

            services.AddMvc();

            services.AddIdentityServer()
                .AddDeveloperSigningCredential()
                .AddAspNetIdentity<TenantUser>()
                // this adds the config data from DB (clients, resources)
                .AddConfigurationStore(options =>
                {
                    options.ConfigureDbContext = builder =>
                        builder.UseSqlServer(connectionString,
                            sql => sql.MigrationsAssembly(migrationsAssembly));
                })
                // this adds the operational data from DB (codes, tokens, consents)
                .AddOperationalStore(options =>
                {
                    options.ConfigureDbContext = builder =>
                        builder.UseSqlServer(connectionString,
                            sql => sql.MigrationsAssembly(migrationsAssembly));

                    // this enables automatic token cleanup. this is optional.
                    options.EnableTokenCleanup = true;
                    options.TokenCleanupInterval = 30;
                });

            // Add the possible authentication methods
            services.AddAuthentication()
                .AddGoogle("Google", options =>
                {
                    options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;

                    options.ClientId = "[...]";
                    options.ClientSecret = "[...]";
                })
                .AddOpenIdConnect("oidc", "OpenID Connect", options =>
                {
                    options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                    options.SignOutScheme = IdentityServerConstants.SignoutScheme;

                    options.Authority = "http://localhost:5000/";
                    options.RequireHttpsMetadata = false;
                    options.ClientId = "implicit";

                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        NameClaimType = "name",
                        RoleClaimType = "role"
                    };
                });

            // This is still to test Azure Auth
            //.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
            //{
            //  ClientId = Configuration["AzureAD:ClientId"],
            //  Authority = string.Format(CultureInfo.InvariantCulture, Configuration["AzureAd:AadInstance"], "common", "/v2.0"),
            //  ResponseType = OpenIdConnectResponseType.IdToken,

            //  Events = new OpenIdConnectEvents
            //  {                       
            //      OnTokenValidated = TokenValidated
            //  },
            //  TokenValidationParameters = new TokenValidationParameters
            //  {
            //      // Instead of using the default validation (validating against
            //      // a single issuer value, as we do in line of business apps), 
            //      // we inject our own multitenant validation logic
            //      ValidateIssuer = false,

            //      NameClaimType = "name"
            //  }
            //}
            //);

            // Registering this service allows for checking if user is active and enhancing the user with extra data (such as the claims)
            services.AddTransient<IProfileService, TenantUserProfileService>();
        }

        private Task TokenValidated(TokenValidatedContext context)
        {
            /* ---------------------
            // Replace this with your logic to validate the issuer/tenant
                ---------------------       
            // Retriever caller data from the incoming principal
            string issuer = context.SecurityToken.Issuer;
            string subject = context.SecurityToken.Subject;
            string tenantID = context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;

            // Build a dictionary of approved tenants
            IEnumerable<string> approvedTenantIds = new List<string>
            {
                "<Your tenantID>",
                "9188040d-6c67-4c5b-b112-36a304b66dad" // MSA Tenant
            };
            o
            if (!approvedTenantIds.Contains(tenantID))
                throw new SecurityTokenValidationException();
                --------------------- */

            var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
            //add your custom claims here
            claimsIdentity.AddClaim(new Claim("test", "helloworld!!!"));

            return Task.FromResult(0);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            // this will do the initial DB population
            SeedData.EnsureSeedData(app.ApplicationServices);

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            // app.UseAuthentication(); // not needed, since UseIdentityServer adds the authentication middleware
            app.UseIdentityServer();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

The IdentityServer4 project created the database, but the configuration made the server use the in memory data instead of the database data. So the in memory data did not have the updated values...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM