简体   繁体   English

授权回调端点不断重定向到用户交互登录页面 IdentityServer

[英]Authorize callback endpoint keeps redirecting to the user interaction login page IdentityServer

I'm working on an SPA web app, and im using IdentityServer4 code flow to handle the authorization.我正在开发一个 SPA 网络应用程序,我正在使用 IdentityServer4 代码流来处理授权。 So I have the following components:所以我有以下组件:

  • The Angular Client Side App, https://localhost:5001 Angular 客户端应用程序, https://localhost:5001
  • Asp.net 3 Web Api project, running on https://localhost:5001 Asp.net 3 Web Api 项目,运行在https://localhost:5001
  • A native mobile application consuming the API, with JWT authentication, running on http://localhost:8100使用 API 的本机移动应用程序,具有 JWT 身份验证,在http://localhost:8100

Rightnow im trying to authenticate the mobile app users, however the user interaction login screen keeps redirecting, and im getting login_required .现在我正在尝试对移动应用程序用户进行身份验证,但是用户交互登录屏幕不断重定向,并且我正在获取login_required

Tracing back the calls, this is what im getting:追溯电话,这就是我得到的:

  • the mobile app calls the /connect/authorize endpoint in a webview - Check移动应用程序在 web 视图中调用/connect/authorize端点- 检查
  • I'm redirected to my SPA app login path https://localhost:5001/auth/login?returnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fredirect_uri%3Dhttp%253A%252F%252Flocalhost%253A8100%252Fauth%252Fcallback%26client_id%3Dcharla-mobile%26response_type%3Dcode%26state%3Dpq1nokeuVj%26scope%3Dcharla-api%2520openid%2520profile%2520offline_access%26code_challenge%3DJxDVsm2YnMAbvOuemWWXjYLLt-Mi1TpHoO7zhDkCWSI%26code_challenge_method%3DS256 - Check我被重定向到我的 SPA 应用程序登录路径https://localhost:5001/auth/login?returnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fredirect_uri%3Dhttp%253A%252F%252Flocalhost%253A8100%252Fauth%252Fcallback%26client_id%3Dcharla-mobile%26response_type%3Dcode%26state%3Dpq1nokeuVj%26scope%3Dcharla-api%2520openid%2520profile%2520offline_access%26code_challenge%3DJxDVsm2YnMAbvOuemWWXjYLLt-Mi1TpHoO7zhDkCWSI%26code_challenge_method%3DS256 -检查
  • I enter the username/password and call the login endpoint in the AccountController.cs - Check我输入用户名/密码并调用AccountController.cslogin端点- 检查
  • im redirected to the IdentityServer callback handler https://localhost:5001/auth/login?returnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fredirect_uri%3Dhttp%253A%252F%252Flocalhost%253A8100%252Fauth%252Fcallback%26client_id%3Dcharla-mobile%26response_type%3Dcode%26state%3Dpq1nokeuVj%26scope%3Dcharla-api%2520openid%2520profile%2520offline_access%26code_challenge%3DJxDVsm2YnMAbvOuemWWXjYLLt-Mi1TpHoO7zhDkCWSI%26code_challenge_method%3DS256 - Check我重定向到 IdentityServer 回调处理程序https://localhost:5001/auth/login?returnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fredirect_uri%3Dhttp%253A%252F%252Flocalhost%253A8100%252Fauth%252Fcallback%26client_id%3Dcharla-mobile%26response_type%3Dcode%26state%3Dpq1nokeuVj%26scope%3Dcharla-api%2520openid%2520profile%2520offline_access%26code_challenge%3DJxDVsm2YnMAbvOuemWWXjYLLt-Mi1TpHoO7zhDkCWSI%26code_challenge_method%3DS256 -检查
  • Instead of being redirected to the redirect_uri with the authorization code, im redirected again to https://localhost:5001/auth/login?returnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fredirect_uri%3Dhttp%253A%252F%252Flocalhost%253A8100%252Fauth%252Fcallback%26client_id%3Dcharla-mobile%26response_type%3Dcode%26state%3Dpbx8alT61z%26scope%3Dcharla-api%2520openid%2520profile%2520offline_access%26code_challenge%3DrGappKbnVpUNzlNHst4t5RlHephWFfJTVXuwtpQ8tZI%26code_challenge_method%3DS256 - Problem here IdentityServer can't sense that the user is now signed in. I keep getting the login_required error in the debug terminal.我没有使用授权码重定向到 redirect_uri,而是再次重定向到https://localhost:5001/auth/login?returnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fredirect_uri%3Dhttp%253A%252F%252Flocalhost%253A8100%252Fauth%252Fcallback%26client_id%3Dcharla-mobile%26response_type%3Dcode%26state%3Dpbx8alT61z%26scope%3Dcharla-api%2520openid%2520profile%2520offline_access%26code_challenge%3DrGappKbnVpUNzlNHst4t5RlHephWFfJTVXuwtpQ8tZI%26code_challenge_method%3DS256 -问题就在这里IdentityServer不能意义上说,用户现在签约在调试终端中,我不断收到login_required错误。

So here is my setup:所以这是我的设置:

Startup.cs启动文件


namespace Charla
{
    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)
        {


        services.AddIdentityCore<ApplicationUser>(options => { });
        new IdentityBuilder(typeof(ApplicationUser), typeof(IdentityRole), services)
            .AddRoleManager<RoleManager<IdentityRole>>()
            .AddSignInManager<SignInManager<ApplicationUser>>()
            .AddEntityFrameworkStores<ConverseContext>();

            /*services.AddIdentity<ApplicationUser, IdentityRole>()
                 .AddRoleManager<RoleManager<IdentityRole>>()
                .AddSignInManager<SignInManager<ApplicationUser>>()
                .AddEntityFrameworkStores<ConverseContext>()
                .AddDefaultTokenProviders();*/

            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
            var builder = services
                .AddIdentityServer(SetupIdentityServer)
                .AddDeveloperSigningCredential()
                .AddConfigurationStore(options =>
                {
                    options.ConfigureDbContext = b => b.UseMySql(Configuration.GetConnectionString("DefaultConnection"),
                        sqloptions => {
                            sqloptions.ServerVersion(new Version(10, 1, 37), ServerType.MariaDb); // replace with your Server Version and Type
                            sqloptions.MigrationsAssembly(migrationsAssembly);
                        });
                })
                .AddOperationalStore(options =>
                {
                    options.ConfigureDbContext = b => b.UseMySql(Configuration.GetConnectionString("DefaultConnection"),
                        sqloptions => {
                            sqloptions.ServerVersion(new Version(10, 1, 37), ServerType.MariaDb); // replace with your Server Version and Type
                            sqloptions.MigrationsAssembly(migrationsAssembly);
                        });
                });
                

            /*services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
                    .AddIdentityServerAuthentication(options => {
                        options.Authority = Configuration.GetValue<string>("IdentityServer:Jwt:Authority");
                        options.RequireHttpsMetadata = false;

                        options.ApiName = "charla-api";
            });*/

           services.AddAuthentication(opt => {
                opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(jwt =>{
                jwt.Authority = Configuration.GetValue<string>("IdentityServer:Jwt:Authority");
                jwt.RequireHttpsMetadata = false;
                jwt.TokenValidationParameters.ValidateAudience = false;
                jwt.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
            });



              
        }


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
        {
       
            
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseSpaStaticFiles();

            app.UseRouting();

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

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

        private static void SetupIdentityServer(IdentityServerOptions options)
        {
            options.UserInteraction.LoginUrl = "/auth/login";
            options.UserInteraction.LoginReturnUrlParameter = "returnUrl";
            options.UserInteraction.LogoutUrl = "/logout";
            options.UserInteraction.ErrorUrl= "/error/identity";

            options.Events.RaiseErrorEvents = true;
            options.Events.RaiseInformationEvents = true;
            options.Events.RaiseFailureEvents = true;
            options.Events.RaiseSuccessEvents = true;
            
            // see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
            //options.EmitStaticAudienceClaim = true;
            //identityServerOptions.Authentication.CookieLifetime = TimeSpan.FromDays(1);
        }

 
        }

    }
}

AccountController.cs账户控制器.cs


    [Route("api/[controller]")]
    [ApiController]
    [AllowAnonymous]
    public class AccountController : ControllerBase
    {

        [HttpPost("login")] 
        public async Task<IActionResult> Login(UserResource model) { 
                   
            var result = await signInManager.PasswordSignInAsync(model.email, model.password, isPersistent: true, lockoutOnFailure: false);

            var context = await interaction.GetAuthorizationContextAsync(model.return_url);

            if (result.Succeeded) { 

                var uo = db.Users.Include(q => q.UserOrganization).Single( q => q.Email == model.email ).UserOrganization.First();
                uo.LastLogin = DateTime.UtcNow;
                await db.SaveChangesAsync();

                // let identity server know that we loggedin
                await identityEvents.RaiseAsync(new UserLoginSuccessEvent(
                     model.email, uo.UserId, model.email, clientId: context?.Client.ClientId
                ));


                //return Redirect(model.return_url);
                return Ok( new{
                    email = model.email,
                    return_url = context.RedirectUri
                } );
            }

            await identityEvents.RaiseAsync(new UserLoginFailureEvent(model.email, "invalid credentials", clientId:context?.Client.ClientId));
            return NotFound(new {});

        }

IdentityConfig.cs - I'm using EF tables, but it was seeded from the below: IdentityConfig.cs - 我正在使用 EF 表,但它是从下面播种的:

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

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

        public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>
            {
                new ApiResource("charla-api", "Charla API Resource")
            };
        }

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

        public static IEnumerable<Client> Clients =>
            new Client[]
            {
                // charla web app
                new Client
                {
                    ClientId = "charla-spa",
                    ClientName = "Charla Web App",
                    RequireClientSecret = false,
                    AllowOfflineAccess = true,
                    AllowAccessTokensViaBrowser = true,

                    AllowedGrantTypes = GrantTypes.Code,
                    RedirectUris = { 
                        "https://localhost:5001/authentication/login-callback",
                        "https://app-dev.getcharla.com/authentication/login-callback",
                        "https://app.getcharla.com/authentication/login-callback"
                        },
                    //FrontChannelLogoutUri = "https://localhost:5001/authentication/logout-callback",
                    PostLogoutRedirectUris = { 
                        "https://localhost:5001/authentication/logout-callback",
                        "https://app-dev.getcharla.com/authentication/logout-callback",
                        "https://app.getcharla.com/authentication/logout-callback"
                    },

                    
                    AllowedScopes = { 
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.OfflineAccess,
                        "charla-api"
                     }
                },

                // mobile client
                new Client
                {
                    ClientId = "charla-mobile",
                    ClientName = "Charla Mobile Apps",
                    RequireClientSecret = false,
                    AllowedGrantTypes = GrantTypes.Code,
                    AllowAccessTokensViaBrowser = true,
                    AllowOfflineAccess = true,

                    RedirectUris = { 
                        "https://getcharla.com/ios_redirect",
                        "http://localhost:8100/auth/callback"
                    },
                    PostLogoutRedirectUris = { 
                        "https://getcharla.com/ios_redirect_endsession",
                        "http://localhost:8100/auth/endsession"
                    },

                    AllowedScopes = { 
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.OfflineAccess,
                        "charla-api"
                     },

                    AllowedCorsOrigins = { "http://localhost:8100", "https://localhost:5001", "http://localhost:5000" }
                },


            };
    }
}

login.component.ts - The function in the web component that fires the call to the login endpoint and that is doing the redirection: login.component.ts - 触发对login端点的调用并执行重定向的 Web 组件中的函数:

在此处输入图片说明


    ngOnInit(): void {

        this.route.queryParams.subscribe(params => {
            this.returnUrl = params['returnUrl'] || '/';
        });
    }

    /**
     * Form Submit
     */
    submit() {
        const controls = this.loginForm.controls;

        const authData = {
            email: controls['email'].value,
            password: controls['password'].value
        };
        this.auth
            .login(authData.email, authData.password, this.returnUrl)
            .pipe(
                finalize(() => {
                    this.loading = false;
                    this.cdr.markForCheck();
                })
            )
            .subscribe(
                result => {
                    window.location = this.returnUrl;
                    this.router.navigateByUrl(this.returnUrl); // Main page
                },
                err => {
                    console.log(err);

                    if (( 'status' in err) && ( err.status === 404)){
                        this.authNoticeService.setNotice(this.translate.instant('AUTH.VALIDATION.INVALID_LOGIN'), 'danger');
                    }
                }
            );
    }

Have no clue why its not working.不知道为什么它不起作用。 I read I should be using HttpContent.SignInAsync in the login web api endpoint, but im already using var result = await signInManager.PasswordSignInAsync(model.email, model.password, isPersistent: true, lockoutOnFailure: false);我读到我应该在login web api 端点中使用HttpContent.SignInAsync ,但我已经在使用var result = await signInManager.PasswordSignInAsync(model.email, model.password, isPersistent: true, lockoutOnFailure: false); so I assume thats enough.所以我认为这就足够了。

Other choices I made I'm not sure are right, like using AddIdentityCore instead of AddIdentity .我不确定我所做的其他选择是否正确,例如使用AddIdentityCore而不是AddIdentity Should I be adding AddAspNetIdentity<ApplicationUser>() ?我应该添加AddAspNetIdentity<ApplicationUser>()吗?

By following the trace you provided that could be happening because of the new rules around Cookies on the Browsers.通过跟踪您提供的跟踪,这可能是由于浏览器上有关 Cookie 的新规则而发生的。

On new ASP.NET Core applications the samesite=none attribute is automatically added, but the Browsers require that you specify the Secure attribute too otherwise the Set-Cookie will be blocked.在新的 ASP.NET Core 应用程序上,会自动添加samesite=none属性,但浏览器要求您也指定Secure属性,否则 Set-Cookie 将被阻止。

To configure your IdentityServer add the following block of code inside the Configure method in the Startup class:要配置您的 IdentityServer,请在Startup类的Configure方法中添加以下代码块:

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

When using the secure attribute HTTPS must be used otherwise it will fail使用安全属性时必须使用HTTPS,否则会失败

Another SO question regarding the topic: Session cookie set `SameSite=None;关于该主题的另一个 SO 问题: Session cookie set `SameSite=None; Secure;` does not work 安全;`不起作用

And some information by Microsoft on ASP.NET Core: https://docs.microsoft.com/pt-br/aspnet/core/security/samesite?view=aspnetcore-5.0以及微软关于 ASP.NET Core 的一些信息: https : //docs.microsoft.com/pt-br/aspnet/core/security/samesite?view=aspnetcore-5.0

I experienced a similar issue with my SPA, when logging out and logging back in again.在注销并重新登录时,我的 SPA 遇到了类似的问题。 It seems the issue was caused by a Visual Studio update.该问题似乎是由 Visual Studio 更新引起的。 If you navigate to the project folder using the command prompt and use dotnet run ' or even better dotnet watch run (so you can make changes to the .net core code while it's running) you can circumnavigate the issue;如果您使用命令提示符导航到项目文件夹并使用dotnet run ' 甚至更好的dotnet watch run (以便您可以在dotnet watch run更改 .net 核心代码),您可以绕过该问题; buy not running the code through IIS in visual studio.购买不通过 IIS 在 Visual Studio 中运行代码。

If this works for you update the Properties Launch to be Project (which also uses dotnet run) and you can then debug as well.如果这对您有效,请将 Properties Launch 更新为 Project(也使用 dotnet run),然后您也可以进行调试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如果未经过身份验证,则将用户重定向到登录页面 - Redirecting user to login page if not authenticated IdentityServer v3不从授权端点返回 - IdentityServer v3 not returning from Authorize endpoint MVC 3表单身份验证在Internet Explorer中不起作用会保持重定向到登录页面 - MVC 3 Forms Authentication not working in Internet Explorer keeps redirecting to login page 用户单击按钮时重定向到登录页面 - Redirecting to login page when user clicks a button AuthorizeAttribute保持重定向到/ Account / Login - AuthorizeAttribute keeps redirecting to /Account/Login IdentityServer 4:在 Nginx 反向代理后面找不到连接/授权/回调地址 - IdentityServer 4 : connect/authorize/callback address not found behind Nginx reverse proxy 点击 [Authorize] 总是重定向到 IdentityServer4 中的 /account/login - Hitting [Authorize] always redirects to /account/login in IdentityServer4 身份服务器在 /connect/authorize/callback 中一直显示“显示登录:用户未通过身份验证” - Identity server is keep showing "Showing login: User is not authenticated" in /connect/authorize/callback FormsAuthentication重定向到登录页面 - FormsAuthentication redirecting to login page 登录后重定向到登录页面 - Redirecting to Login page after Login
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM