Oauth2令牌,MVC中JS spa的句柄



关于OAuth

  • 前端SPA反应
  • MVC OAuth后端,将用户签名到第三方提供商,工作良好,返回令牌

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

如何返回令牌&与SPA相关,因为它们在单独的窗口/会话中?

选项我在看

  • 内容安全策略-设置调用方的域
  • 设置同一站点cookie

使用aspnet contrib/aspnet.Security.Auth.Providers

样品

启动.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>

谢谢你看

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

一种选择是设置您的身份验证请求的redirect_uri参数,以便第三方身份提供程序重定向回您网站上的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身份验证/授权,因此服务器端无法获得令牌,您可能希望让像oidc客户端这样的Javascript库来完成繁重的工作。这将启动自己的窗口来执行身份验证,并将令牌交回调用SPA本身。这些令牌对服务器端不可见。

真正帮助我理解这一点的一个资源是用于JavaScript客户端的IdentityServer4快速启动,如果这是您的用例,那么值得一试:

从JavaScript客户端快速启动OpenID连接身份验证上述快速启动的示例代码

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

我的偏好是允许服务器端使用AspNet.Security.auth.Providers执行身份验证代码授权流,将身份验证代码接收到~/signin页面,执行后台通道请求以获取令牌,使用身份信息为用户创建自己的配置文件,并使用委托给我的服务的授权代表用户执行请求。

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

最新更新