无法使用“/connect/authorize”端点从 IdentityServer4 获取授权码

Posted

技术标签:

【中文标题】无法使用“/connect/authorize”端点从 IdentityServer4 获取授权码【英文标题】:Cannot get Authorization Code from IdentityServer4 using "/connect/authorize" endpoint 【发布时间】:2021-11-04 09:13:21 【问题描述】:

我正在开发一个 SPA 应用程序,其 Identityserver4 介于 Angular 和 .net API 之间。身份服务器的大部分配置都已完成,唯一的障碍是,当我调用 /connect/authorize 时,我没有获得授权代码,我得到了带有我提供的数据的编码重定向 URL。我错过了什么吗?我真的无法理解为什么会有这种行为。

Postman screenshot

这是客户端配置

new Client
            
                ClientId = "Angular",
                ClientName = "Angular Client",
                AlwaysIncludeUserClaimsInIdToken = true,
                AllowedGrantTypes = new List<string>  GrantType.AuthorizationCode ,
                RequirePkce =true,
                RequireClientSecret = false,
                RequireConsent = false,
                ClientSecrets =
                
                  
                    new Secret("secret".Sha512(),"my secret")
                ,
                
                AllowedScopes = "WebAPI","fullcontroll",IdentityServerConstants.StandardScopes.OpenId,
                Claims = new List<ClientClaim>
                
                    new ClientClaim("clientName", "SPA"),
                  
                ,
                RedirectUris =  "https://localhost:6001/login" ,
                FrontChannelLogoutUri = "https://localhost:4001/signout-oidc",
                PostLogoutRedirectUris =  "http://localhost:4001/signout-callback-oidc" ,
                AllowedCorsOrigins = new List<string>
                
                    "https://localhost:4001",
                    "https://localhost:5001",
                    "https://localhost:6001",
                ,
                AllowOfflineAccess =true,
                AccessTokenLifetime = 3600,
                IdentityTokenLifetime = 300,
                AlwaysSendClientClaims = true,
                Enabled = true
            ,

Startup.cs

 services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<IdentityServerContext>()
                .AddRoleManager<RoleManager<IdentityRole>>()
                .AddUserManager<UserManager<ApplicationUser>>()
                .AddSignInManager<SignInManager<ApplicationUser>>()         
                .AddDefaultTokenProviders();

        services.ConfigDbContext(Configuration);

        services.InjectClients(Configuration);

        services.AddAuthentication(options =>
        
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            options.DefaultForbidScheme = OpenIdConnectDefaults.AuthenticationScheme;
            options.RequireAuthenticatedSignIn = true;
        )
        .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
    
        options.Cookie.Name = "login-vault";
        options.Cookie.SameSite = SameSiteMode.Lax;
        //options.LoginPath = "/identityserver/login";
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
        options.Cookie.SecurePolicy = 0;
        options.SlidingExpiration = true;
        options.Events.OnSigningOut = async e =>
        
            // revoke refresh token on sign-out
            await e.HttpContext.RevokeUserRefreshTokenAsync();
        ;
    )
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
    
        options.Authority = "https://localhost:4001";
        options.ClientId = "Angular";
        options.ClientSecret = "secret";
        options.Resource = "WebAPI";
        options.ResponseType = "code";
        try
        
            var oidc = Registry.CurrentUser.OpenSubKey(@"Keys", true).OpenSubKey(@"IdentityServer", true).CreateSubKey("OIDC", true);
            options.DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(oidc.ToString()),
                options =>
                
                    options.UseCryptographicAlgorithms(
            new AuthenticatedEncryptorConfiguration()
            
                EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
                ValidationAlgorithm = ValidationAlgorithm.HMACSHA512
            )
               .SetDefaultKeyLifetime(TimeSpan.FromDays(30))
               .PersistKeysToRegistry(Registry.CurrentUser.OpenSubKey(@"Keys", true).OpenSubKey(@"IdentityServer", true).OpenSubKey(@"OIDC", true))
               .ProtectKeysWithDpapi();
                )
            .CreateProtector("WebAPI");
        
        catch (Exception e)
        
            Console.WriteLine(e.Message);
        

        options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;          
        options.UseTokenLifetime = true;
        options.GetClaimsFromUserInfoEndpoint = true;
        options.SaveTokens = true;
        options.UsePkce = true;
     
        options.Scope.Clear();
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.Scope.Add("email");
        options.Scope.Add("WebAPI");
        options.Scope.Add("offline_access");
   
        options.CallbackPath = "/signin-odic";
        options.SignedOutRedirectUri = "https://localhost:4001/connect/endsession";

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

        services.AddAccessTokenManagement(options =>
        
            options.Client.Scheme = OpenIdConnectDefaults.AuthenticationScheme;
            options.User.Scheme = OpenIdConnectDefaults.AuthenticationScheme;
        );

        services.AddClientAccessTokenClient("Angular", configureClient: client =>
            client.BaseAddress = new Uri("https://localhost:5001/api/"));

        services.AddUserAccessTokenClient("ApplicationUsers", client =>
        
            client.BaseAddress = new Uri("https://localhost:5001/api/");
        );

这是 IdentityServer 配置

services.AddIdentityServer(options =>
        
            options.Authentication.CookieLifetime = TimeSpan.FromMinutes(5);
            options.Authentication.CookieSameSiteMode = SameSiteMode.Lax;
            options.Authentication.CookieSlidingExpiration = true;
            //options.Authentication.CheckSessionCookieName = "Identity.Session";
            options.EmitStaticAudienceClaim = true;
            options.IssuerUri = "https://localhost:4001";
            options.LowerCaseIssuerUri = true;
            options.Events.RaiseErrorEvents = true;
            options.Events.RaiseInformationEvents = true;
            options.Events.RaiseFailureEvents = true;
            options.Events.RaiseSuccessEvents = true;
            options.EmitScopesAsSpaceDelimitedStringInJwt = false;
            options.Endpoints.EnableCheckSessionEndpoint = true;
            options.Endpoints.EnableEndSessionEndpoint = true;
            options.Endpoints.EnableTokenEndpoint = true;
            options.Endpoints.EnableAuthorizeEndpoint = true;
            options.Endpoints.EnableJwtRequestUri = true;
            options.UserInteraction.LoginUrl = "https://localhost:6001/login";
            options.UserInteraction.LogoutUrl = "https://localhost:6001/logout";
            //options.UserInteraction.ConsentUrl = "";
        )
                .AddAspNetIdentity<ApplicationUser>()
                .AddDeveloperSigningCredential()
                //.AddSigningCredential(new X509Certificate2(Path.Combine(".", "certs", "IdentityServer4Auth.pfx")))
                .AddConfigurationStore(options =>
                
                    options.ConfigureDbContext = b => b.UseSqlServer(configuration.GetConnectionString("IdentityServer"),
                        sql => sql.MigrationsAssembly(migrationAssembly));
                )
                .AddOperationalStore(options =>
                
                    options.ConfigureDbContext = b => b.UseSqlServer(configuration.GetConnectionString("IdentityServer"),
                        sql => sql.MigrationsAssembly(migrationAssembly));
                    options.EnableTokenCleanup = false;
                    options.TokenCleanupInterval = 3600;
                );

非常感谢您的帮助! 谢谢!

【问题讨论】:

请修剪您的代码,以便更容易找到您的问题。请按照以下指南创建minimal reproducible example。 【参考方案1】:

授权端点旨在在浏览器内调用(通常是 GET 请求,但也支持通过表单的 POST)。这样,如果需要交互式身份验证,端点可以重定向到该 UI,然后在完成后继续为请求提供服务。

您的 identityserver4 实例配置为使用 https://localhost:6001/login 进行身份验证,这就是为什么您会看到 302 重定向到该 URL - 授权端点看到用户未经身份验证(不存在 cookie)并自动重定向到options.UserInteraction.LoginUrl 的值。

【讨论】:

以上是关于无法使用“/connect/authorize”端点从 IdentityServer4 获取授权码的主要内容,如果未能解决你的问题,请参考以下文章

带有 docker-compose 的 Identity Server 4 在 /connect/authorize/callback 之后重定向到登录页面

设置为允许使用提示=无静默续订

身份服务器端点 OIDC

IdentityServer4 中的 CSRF 保护

检查用户是否已在IdentityServer 4中进行了身份验证

Identity Server 3 Access Token Validation 端点因受众验证失败而失败