如何使用 PageModels 为 ASP .NET Core 3.0 设置身份验证和授权?
How do I set up authentication & authorisation for ASP .NET Core 3.0 with PageModels?
我正在尝试为我拥有的前端 Web 应用程序设置身份验证和授权部分。该应用程序设置为 ASP.NET 核心 Razor 页面应用程序。 (.NET 核心 3.0);我还使用以下命令预先安装了身份验证来设置它:dotnet new razor -au Individual
。
有了这个,我正在尝试从我拥有的外部服务(身份服务器)设置 OpenID 身份验证外部登录。问题在于在前端显示最终用户(或者如果我这样做完全错误...... - 我在这个主题上花费了可疑的时间,除了旧问题之外我找不到任何东西,或关于您以前如何使用 MVC 应用程序执行此操作的内容)-我还能够找到存储在本地数据库中的外部用户,并且可以通过代码从外部身份服务器成功登录。 (稍后解释)
这是我的第一个主要 authentication/authorization 部分的项目 - 如果有任何愚蠢的事情跳出来,我提前道歉。我很想知道出了什么问题。开始了!
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")
));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//options.DefaultAuthenticateScheme = IdentityConstants.ExternalScheme;
//options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.LoginPath = "/Account/Login/";
})
.AddOpenIdConnect(ChallengeScheme, o =>
{
o.ClientId = "client_id";
o.ClientSecret = "*******************";
o.Authority = "http://endpointhere.com";
o.ResponseType = "code" ;
o.SaveTokens = true;
o.Scope.Add("IdentityServerApi");
});
services.AddAuthorization();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
ExternalLogin.cshtml.cs ; OnGetCallbackAsync
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync("oidc", info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
// Store access token, token so it is included in cookie
var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
//await _signInManager.SignInAsync(user, props, info.LoginProvider);
// Update external authentication tokens with signInManager
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ReturnUrl = returnUrl;
LoginProvider = info.LoginProvider;
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
Input = new InputModel
{
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
};
}
return Page();
}
}
_LoginPartial.cshtml
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
<ul class="navbar-nav">
@if (User.Identity.IsAuthenticated)
{
<li class="nav-item">
<a class="nav-link text-light" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/", new { area = "" })" method="post" >
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-light" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>
来自上面的源代码:成功登录后,我能够检索从外部身份服务器检索到的访问令牌 - 但我无法在 UI 中加载任何用户信息。
例如,在 _LoginPartial
中,User.Identity.IsAuthenticated
和 SignInManager.IsSignedIn(User)
中的默认值都不为真。它们始终为假。
我查看了下面的代码,然后...:
// Code from ExternalLogin.cshtml.cs
var info = await _signInManager.GetExternalLoginInfoAsync();
info.Principal.Identity.IsAuthenticated 总是返回真;我还阅读了这些资源,试图弄清楚发生了什么——我已经尝试了很多东西,但都无济于事。 PS:我也尝试过旧的 MVC 方式 - 但我无法在我拥有的 mac 上做任何脚手架,所以我在 GitHub 上提出了一个问题:https://github.com/aspnet/Identity/issues/1452
- https://github.com/alexandre-spieser/AspNetCore.Identity.MongoDbCore/issues/8
- https://github.com/aspnet/Security/issues/1538
- https://github.com/aspnet/Identity/issues/1452
- https://forums.asp.net/t/1177741.aspx?User+Identity+IsAuthenticated+remains+false+why+
- https://github.com/IdentityServer/IdentityServer3.AspNetIdentity/issues/79
现在,我还了解到,从 this issue 我知道 User.Identity.IsAuthenticated 用于外部的任何其他内容,而 SignInManager 仅用于 asp.net 的身份框架。我只是想知道这是否与我的问题有关?
but I was unable to load any user information in the UI.
注意当某些用户通过身份验证然后被重定向到URL的
/Identity/Account/ExternalLogin?returnUrl=%2F&handler=Callback
此 Identity/Account/ExternalLogin
页面将通过以下方式发送 cookie 使用 Identity.Application
方案登录用户:
set-cookie: .AspNetCore.Identity.Application={the-cookie-here}
换句话说,这里的登录方案是IdentityConstants.ApplicationScheme
(即Identity.Application
)
但是,您已将默认身份验证方案设置为 Cookies
:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
...
})
因此,默认情况下,RazorPage 中的 User
属性 将始终使用 CookieAuthenticationDefaults.AuthenticationScheme
的方案进行身份验证,这与 Identity.Application
方案不同。这就是为什么你的 User.Identity.IsAuthenticated
总是错误的。
要解决该问题,您可以:
- 方法 1:通过以下方式为 Cookie 配置转发方案:
.AddCookie(options =>{
options.LoginPath = "/Account/Login/";
options.ForwardDefault = IdentityConstants.ApplicationScheme;
})
- 方法二:使用
IdentityConstants.ApplicationScheme
作为默认方案
- 方法 3:为页面 handler/page 模型手动添加
[Authorize(AuthenticationSchemes="Identity.Application")]
:
[Authorize(AuthenticationSchemes="Identity.Application")]
我正在尝试为我拥有的前端 Web 应用程序设置身份验证和授权部分。该应用程序设置为 ASP.NET 核心 Razor 页面应用程序。 (.NET 核心 3.0);我还使用以下命令预先安装了身份验证来设置它:dotnet new razor -au Individual
。
有了这个,我正在尝试从我拥有的外部服务(身份服务器)设置 OpenID 身份验证外部登录。问题在于在前端显示最终用户(或者如果我这样做完全错误...... - 我在这个主题上花费了可疑的时间,除了旧问题之外我找不到任何东西,或关于您以前如何使用 MVC 应用程序执行此操作的内容)-我还能够找到存储在本地数据库中的外部用户,并且可以通过代码从外部身份服务器成功登录。 (稍后解释)
这是我的第一个主要 authentication/authorization 部分的项目 - 如果有任何愚蠢的事情跳出来,我提前道歉。我很想知道出了什么问题。开始了!
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("DefaultConnection")
));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//options.DefaultAuthenticateScheme = IdentityConstants.ExternalScheme;
//options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(options =>
{
options.LoginPath = "/Account/Login/";
})
.AddOpenIdConnect(ChallengeScheme, o =>
{
o.ClientId = "client_id";
o.ClientSecret = "*******************";
o.Authority = "http://endpointhere.com";
o.ResponseType = "code" ;
o.SaveTokens = true;
o.Scope.Add("IdentityServerApi");
});
services.AddAuthorization();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
ExternalLogin.cshtml.cs ; OnGetCallbackAsync
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync("oidc", info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
// Store access token, token so it is included in cookie
var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
//await _signInManager.SignInAsync(user, props, info.LoginProvider);
// Update external authentication tokens with signInManager
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ReturnUrl = returnUrl;
LoginProvider = info.LoginProvider;
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
Input = new InputModel
{
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
};
}
return Page();
}
}
_LoginPartial.cshtml
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
<ul class="navbar-nav">
@if (User.Identity.IsAuthenticated)
{
<li class="nav-item">
<a class="nav-link text-light" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/", new { area = "" })" method="post" >
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-light" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>
来自上面的源代码:成功登录后,我能够检索从外部身份服务器检索到的访问令牌 - 但我无法在 UI 中加载任何用户信息。
例如,在 _LoginPartial
中,User.Identity.IsAuthenticated
和 SignInManager.IsSignedIn(User)
中的默认值都不为真。它们始终为假。
我查看了下面的代码,然后...:
// Code from ExternalLogin.cshtml.cs
var info = await _signInManager.GetExternalLoginInfoAsync();
info.Principal.Identity.IsAuthenticated 总是返回真;我还阅读了这些资源,试图弄清楚发生了什么——我已经尝试了很多东西,但都无济于事。 PS:我也尝试过旧的 MVC 方式 - 但我无法在我拥有的 mac 上做任何脚手架,所以我在 GitHub 上提出了一个问题:https://github.com/aspnet/Identity/issues/1452
- https://github.com/alexandre-spieser/AspNetCore.Identity.MongoDbCore/issues/8
- https://github.com/aspnet/Security/issues/1538
- https://github.com/aspnet/Identity/issues/1452
- https://forums.asp.net/t/1177741.aspx?User+Identity+IsAuthenticated+remains+false+why+
- https://github.com/IdentityServer/IdentityServer3.AspNetIdentity/issues/79
现在,我还了解到,从 this issue 我知道 User.Identity.IsAuthenticated 用于外部的任何其他内容,而 SignInManager 仅用于 asp.net 的身份框架。我只是想知道这是否与我的问题有关?
but I was unable to load any user information in the UI.
注意当某些用户通过身份验证然后被重定向到URL的
/Identity/Account/ExternalLogin?returnUrl=%2F&handler=Callback
此 Identity/Account/ExternalLogin
页面将通过以下方式发送 cookie 使用 Identity.Application
方案登录用户:
set-cookie: .AspNetCore.Identity.Application={the-cookie-here}
换句话说,这里的登录方案是IdentityConstants.ApplicationScheme
(即Identity.Application
)
但是,您已将默认身份验证方案设置为 Cookies
:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
...
})
因此,默认情况下,RazorPage 中的 User
属性 将始终使用 CookieAuthenticationDefaults.AuthenticationScheme
的方案进行身份验证,这与 Identity.Application
方案不同。这就是为什么你的 User.Identity.IsAuthenticated
总是错误的。
要解决该问题,您可以:
- 方法 1:通过以下方式为 Cookie 配置转发方案:
.AddCookie(options =>{ options.LoginPath = "/Account/Login/"; options.ForwardDefault = IdentityConstants.ApplicationScheme; })
- 方法二:使用
IdentityConstants.ApplicationScheme
作为默认方案 - 方法 3:为页面 handler/page 模型手动添加
[Authorize(AuthenticationSchemes="Identity.Application")]
:[Authorize(AuthenticationSchemes="Identity.Application")]