【问题标题】:Oauth2 token, handle from JS spa from MVCOauth2 令牌,来自 MVC 的 JS spa 的句柄
【发布时间】:2021-12-18 08:03:05
【问题描述】:

关于 OAuth

  • 前端 SPA 反应
  • MVC OAuth 后端,将用户登录到 3rd 方提供商,运行良好,返回令牌。

从我的 SPA 中,我可以执行 window.open 并将用户重定向到登录页面,注意:必须是一个新窗口,因为 xframeoptions 设置为拒绝。

我如何返回令牌并与 SPA 关联,因为它们位于不同的窗口/会话中?

我正在查看的选项

  • 内容安全政策 - 设置调用者的域
  • 设置相同的网站 cookie

使用aspnet-contrib/AspNet.Security.OAuth.Providers

Samples

Startup.cs

   public class Startup
{
    private const string policyName = "Cors";

    public Startup(IConfiguration configuration, IHostEnvironment hostingEnvironment)
    {
        Configuration = configuration;
        HostingEnvironment = hostingEnvironment;
    }

    public IConfiguration Configuration { get; }

    private IHostEnvironment HostingEnvironment { get; }

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


        services.AddCors(opt =>
        {
            opt.AddPolicy(name: policyName, builder =>
            {
                builder.AllowAnyOrigin()
                    .AllowAnyHeader()
                    .AllowAnyOrigin()
                    .AllowAnyMethod();
            });
        });

        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        })


        .AddCookie(options =>
        {
            options.LoginPath = "/signin";
            options.LogoutPath = "/signout";
        })


        .AddGitHub(options =>
        {
            options.ClientId = Configuration["GitHub:ClientId"];
            options.ClientSecret = Configuration["GitHub:ClientSecret"];
            options.Scope.Add("user:email");
            options.Scope.Add("read:org");
            options.Scope.Add("workflow");
            options.SaveTokens=true;
            
        });



        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app)
    {
        if (HostingEnvironment.IsDevelopment())
        {
           // IdentityModelEventSource.ShowPII = true;
        }

        // Required to serve files with no extension in the .well-known folder
        //var options = new StaticFileOptions()
        //{
        //    ServeUnknownFileTypes = true,
        //};
        app.UseForwardedHeaders(new ForwardedHeadersOptions
        {
            ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
        });
        app.UseCors(policyName);

        app.UseStaticFiles();

        app.UseRouting();

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

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

身份验证控制器

public class AuthenticationController : Controller
    {
        [HttpGet("~/signin")]
        public async Task<IActionResult> SignIn() => View("SignIn", await HttpContext.GetExternalProvidersAsync());

        [HttpPost("~/signin")]
        public async Task<IActionResult> SignIn([FromForm] string provider)
        {
            // Note: the "provider" parameter corresponds to the external
            // authentication provider choosen by the user agent.
            if (string.IsNullOrWhiteSpace(provider))
            {
                return BadRequest();
            }

            if (!await HttpContext.IsProviderSupportedAsync(provider))
            {
                return BadRequest();
            }
            
            // Instruct the middleware corresponding to the requested external identity
            // provider to redirect the user agent to its own authorization endpoint.
            // Note: the authenticationScheme parameter must match the value configured in Startup.cs
            return Challenge(new AuthenticationProperties { RedirectUri = "/" }, provider);
        }

        [HttpGet("~/signout")]
        [HttpPost("~/signout")]
        public IActionResult SignOutCurrentUser()
        {
            // Instruct the cookies middleware to delete the local cookie created
            // when the user agent is redirected from the external identity provider
            // after a successful authentication flow (e.g Google or Facebook).
            return SignOut(new AuthenticationProperties { RedirectUri = "/" },
                CookieAuthenticationDefaults.AuthenticationScheme);
        }
    }   

家庭控制器

 public class HomeController : Controller
    {
        public async Task<IActionResult> IndexAsync()
        {
            var accessToken = await HttpContext.GetTokenAsync("GitHub", "access_token");
            var refreshToken = await HttpContext.GetTokenAsync("GitHub", "refresh_token");
            return View();
        }
    }

主页(Index.cshtml)

<div class="jumbotron">
    @if (User?.Identity?.IsAuthenticated ?? false)
    {
        <h1>Welcome, @User.Identity.Name</h1>

        <p>
            @foreach (var claim in Context.User.Claims)
            {
                <div><code>@claim.Type</code>: <strong>@claim.Value</strong></div>
            }
        </p>

        <a class="btn btn-lg btn-danger" href="/signout?returnUrl=%2F">Sign out</a>
    }

    else
    {
        <h1>Welcome, anonymous</h1>
        <a class="btn btn-lg btn-success" href="/signin?returnUrl=%2F">Sign in</a>
    }
</div>

感谢观看

【问题讨论】:

  • #1 您的 oauth 服务器功能是否嵌入在您的微服务中? #2 什么是NB? #3 您是否在严格使用一些 oauth2 流程,例如授权授予? #4 为什么需要打开一个新窗口? Gmail、Microsoft、LinkedIn 等不这样做。
  • @JRichardsz,感谢您的回复,我已经发布了一些代码,一直在使用上面示例链接中的示例,不确定它是什么类型的授权,没有说我能看到的任何地方,我尝试使用 iframe 并获得 X-Frame-Options:DENY ?
  • 您附加的示例是针对客户的,而不是针对 oauth 提供者的。用户成功登录后,会发生什么?登录窗口是否关闭?你有重定向路线吗?
  • @JRichardsz,然后它运行一个挑战,登录窗口重定向到主页,我可以获得一个令牌,我添加了身份验证控制器和主页,其中显示了来自令牌的一些声明,谢谢寻找。 )顺便说一句,忽略刷新令牌它只是测试 atm 为空
  • 从客户端而不是服务器启动身份验证。当用户点击登录时,表单 React 开始认证,也许第三部分客户端有一个框架,就像微软有 MSAL。身份验证完成后,应用重定向到您的应用,然后您获取 Bearer 令牌。

标签: c# asp.net asp.net-mvc oauth-2.0 single-page-application


【解决方案1】:

似乎(如果我弄错了,请纠正我)主要问题是启动 github auth 的窗口并将该令牌发送回您的站点,这是一个不同的窗口(即弹出窗口的父窗口)。

一种选择是设置您的身份验证请求的 redirect_uri 参数,以便第 3 方身份提供者重定向回您网站上的 URL(在弹出窗口中),即您的 ~/signin 端点。这允许您的服务器端获取令牌并执行诸如创建会话、存储 cookie 等操作。在弹出窗口中设置的 cookie 将在原始窗口中对您的站点可用(假设它是同一个域)一次父窗口已刷新。

接下来,一旦弹出窗口重定向回您的 ~/signin 端点并且您创建了会话或存储了 cookie 等,您可能希望关闭该弹出窗口并刷新父窗口,以便它识别cookie / 新会话。您可以通过从 ~/signin 请求(仍在弹出窗口中)返回一个包含以下 JavaScript 的页面来执行此操作:

window.opener.document.location.reload();

// alternatively send the user to an authenticated homepage:
window.opener.document.location.href = '/signed-in-user-homepage';

// and then close the popup
window.close();

我认为这不是您的目标,但为了完整性,如果您希望从 SPA 本身执行 OAuth 身份验证/授权,因此服务器端无法获取令牌,您可能希望让 Javascript 库像 @ 987654321@ 做繁重的工作。这会启动自己的窗口来执行身份验证并将令牌交还给调用 SPA 本身。这些令牌对服务器端不可见。

真正帮助我弄清楚这一点的一个资源是 JavaScript 客户端的 IdentityServer4 快速入门,如果这是您的用例,值得一读:

OpenID Connect auth from JavaScript clients quickstart Sample code for the above quickstart

一旦您在客户端(即在 JavaScript 中)获得了一个令牌,您就可以将它传递给服务器端以用于代表用户发出请求,但这不是一个好习惯,因为它是模拟而不是正确委派的授权,并且您需要弄清楚访问令牌过期时该怎么办(将刷新令牌传递给服务器端有效地允许您的服务器无限期地模拟用户)。

我的偏好是允许服务器端使用 AspNet.Security.OAuth.Providers 执行授权码授权流程,将授权码接收到 ~/signin 页面,执行反向通道请求以获取令牌,使用身份信息为用户创建我自己的个人资料,并使用委托给我的服务的授权代表用户执行请求。

如果需要更多解释,请告诉我,但希望它是有用的。

【讨论】:

    猜你喜欢
    • 2020-04-13
    • 2016-06-26
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 2020-09-28
    • 2019-04-22
    • 1970-01-01
    相关资源
    最近更新 更多