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

Angular+BFF架构下如何通过OpenIdConnectEvents处理登录权限校验并反馈Angular

问题

我搭建了基于Angular SPA应用与ASP.NET Core 9 Web API的BFF应用架构,采用OpenId Connect对接Microsoft EntraId、Microsoft及Google身份提供商实现单点登录,已参照Damien Bowden的示例完成配置,正常登录后可重定向至指定Angular页面。

但遇到一个问题:如何处理那些已完成OIDC登录,但因用户账号停用或已从应用数据库移除而无权访问应用的情况?

我尝试通过OpenIdConnectEvents处理,但不清楚如何将未授权信息返回给Angular,编写了如下代码:

builder.Services
       .Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
            {
                options.Events = new OpenIdConnectEvents
                    {   
                        OnTokenValidated = async context =>
                            {
                                if (context.Principal?.Identity is not ClaimsIdentity claimsIdentity)
                                    return;

                                var userName = claimsIdentity.FindFirst("preferred_username")?.Value;
            
                                var userServiceRepository = context.HttpContext.RequestServices.GetRequiredService<IUserServiceRepository>();

                                var usager = await userServiceRepository.GetUsager(userName);

                                if (usager == null)  // 注:原代码逻辑写反,应为用户不存在时拒绝
                                {                 
                                    context.Fail("Unauthorized");
                                }
                            }
                    };
            });

使用该方法后,Web API会重定向至https://localhost:7143/signin-oidc端点并抛出异常。

我考虑过两种思路:

  1. 不完成用户登录,而是在从signin-oidc重定向后通过API调用校验认证状态;
  2. 给用户添加new Claim("access_denied", "true")声明后完成登录,但觉得这种情况不应视为登录成功,方案不合理。

想请教这类流程的最佳实践方案。


最佳实践方案

针对BFF架构下的本地用户权限校验场景,推荐以下落地方案:

1. 在OnTokenValidated中自定义失败重定向,拦截无效用户

你当前代码的问题是调用context.Fail()后,OIDC中间件会默认触发错误流程,需手动指定重定向路径,直接跳转到Angular的无权限页面:

OnTokenValidated = async context =>
{
    var userName = context.Principal?.FindFirst("preferred_username")?.Value;
    if (string.IsNullOrEmpty(userName))
    {
        context.Fail("Missing username claim");
        return;
    }

    var userService = context.HttpContext.RequestServices.GetRequiredService<IUserServiceRepository>();
    var usager = await userService.GetUsager(userName);

    if (usager == null || !usager.IsActive) // 校验用户存在且处于激活状态
    {
        // 清除已生成的本地认证Cookie
        await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        // 重定向到Angular的无权限页面,携带错误原因参数
        context.Response.Redirect($"/unauthorized?reason=account_invalid");
        // 终止后续OIDC流程,避免默认错误跳转
        context.HandleResponse();
    }
}

这种方式在OIDC登录流程的最前端拦截无效用户,无需完成登录,直接引导到Angular的专属错误页,体验更直接。

2. 全局会话校验中间件,覆盖登录后全场景

如果需要支持会话中期禁用用户的检测(比如用户登录后被后台停用),可以在BFF层添加全局中间件,每次请求时校验本地用户状态:

app.Use(async (context, next) =>
{
    if (context.User.Identity?.IsAuthenticated == true)
    {
        var userName = context.User.FindFirst("preferred_username")?.Value;
        var userService = context.RequestServices.GetRequiredService<IUserServiceRepository>();
        var usager = await userService.GetUsager(userName);

        if (usager == null || !usager.IsActive)
        {
            // 登出OIDC会话和本地认证Cookie
            await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            await context.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
            // 重定向到无权限页
            context.Response.Redirect("/unauthorized?reason=account_disabled");
            return;
        }
    }
    await next();
});

该方案不仅能处理登录时的校验,还能在用户使用过程中实时检测账号状态,保证安全性。

3. 明确拒绝"伪登录"方案

不建议给用户添加access_denied声明后完成登录:这种方式会让用户处于已认证状态,但实际无权限,后续需要在Angular或API层额外处理校验逻辑,增加复杂度,还可能引发安全隐患(比如用户利用有效会话Cookie尝试绕过校验)。

补充注意事项

  • 拦截无效用户后,必须清除OIDC会话和本地认证Cookie,避免用户陷入登录循环;
  • Angular端需实现/unauthorized页面,解析URL参数中的错误原因,给用户友好提示;
  • 性能敏感场景可给本地用户状态添加缓存(如Redis),避免每次请求都查询数据库。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 05:52:45