从 Angular 5 注销 MVC 应用程序
Logout of MVC Application From Angular 5
我们有一个构建在 MVC 身份验证之上的 angular 5 应用程序。应用程序由 Home/Index 操作提供,一旦应用程序加载,angular 路由会处理几乎所有事情。我们这样做主要是因为我们想使用 MVC 的身份验证过程(我们使用 Identity Server 4 作为我们的 Oath 系统)。
除一个例外外,此方法运行良好:注销。当我们尝试注销时,应用程序似乎立即重新授权并重新加载,而不是将我们返回到我们的 Identity Server 登录页面。
原来我们的开发环境是通过这段代码成功的:
[HttpPost]
public async Task<IActionResult> Logout()
{
foreach (string key in Request.Cookies.Keys)
{
Response.Cookies.Delete(key);
}
await HttpContext.SignOutAsync();
return Ok();
}
但这是误报,因为我们所有的应用程序都在本地主机上 运行,因此它们可以访问彼此的 cookie。因此,身份服务器 cookie 与 Angular 应用程序的 cookie 一起被清除。
我们试图用这样的东西替换那个逻辑:
public async Task Logout()
{
if (User?.Identity.IsAuthenticated == true)
{
// delete local authentication cookie
await HttpContext.SignOutAsync("Cookies");
await HttpContext.SignOutAsync("oidc");
}
}
但是,它在任何一个环境中都没有注销(但是该代码适用于我们使用相同身份服务器的 MVC 应用程序之一)。
为了提供一些背景知识,我们 Angular 应用程序的注销过程来自 material 菜单,然后我们在调用注销功能。此过程的代码片段:
注销按钮调用的方法:
public openDialog(): void {
let dialogRef = this.dialog.open(ModalComponent, {
width: '250px',
data: { text: this.logoutText, ok: this.okText, cancel: this.cancelText }
});
dialogRef.afterClosed().subscribe(result => {
if (result !== undefined) {
switch (result.data) {
case 'ok':
this.logout();
break;
case 'cancel':
break;
default:
break;
}
}
});
}
private logout(): void {
this.sessionService.logOut();
}
会话服务:
public logOut(): void {
this.auth.revokeToken();
this.http.post(this.logoutUrl, undefined).subscribe(x => window.location.reload());
}
如前所述,以这种方式调用注销以页面刷新结束,用户并没有真正注销。我们可能遗漏了一些简单的东西,但到目前为止我所做的所有修补工作都没有取得任何成功。
编辑:
我们的 angular 路由相当简单(尽管路径阻止我们调用类似 home/logout 的东西):
{
path: 'parts',
component: PartsComponent,
canActivate: [AuthGuard],
runGuardsAndResolvers: 'always',
data: { title: 'Parts' }
},
{
path: '',
redirectTo: 'parts',
pathMatch: 'full'
},
{
path: '**',
redirectTo: 'parts'
}
我们的 MVC 路由也相当简单
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
"Sitemap",
"sitemap.xml",
new { controller = "Home", action = "SitemapXml" });
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
我不确定我们如何可以轻松地按照 angular 路线路由到 home/logout。我猜我们必须为它添加一条路线。我们曾经尝试过,但它从未正确路由。我正在尝试查找有关我们尝试过的内容的笔记。
我无法回答 Angular;我还在努力。但是,当用户在客户端注销时,客户端 Web 应用程序可能会要求 IDP 撤销访问令牌和刷新令牌。例如,在 ASP.Net Core -
using System;
using System.Threading.Tasks;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace MY_APP.Controllers
{
public class AccountController : Controller
{
[HttpGet]
public async Task<IActionResult> SignOut()
{
var discoveryClient = new DiscoveryClient("IDP_URL");
var metaDataResponse = await discoveryClient.GetAsync();
var revocationClient = new TokenRevocationClient(
metaDataResponse.RevocationEndpoint,
"CLIENT_NAME",
"CLIENT_SECRET");
// revoke the access token
string accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
if (!string.IsNullOrWhiteSpace(accessToken))
{
var revokeAccessTokenResponse = await revocationClient.RevokeAccessTokenAsync(accessToken);
if (revokeAccessTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the access token.",
revokeAccessTokenResponse.Exception);
}
// revoke the refresh token
string refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
if (!string.IsNullOrWhiteSpace(refreshToken))
{
var revokeRefreshTokenResponse = await revocationClient.RevokeAccessTokenAsync(refreshToken);
if (revokeRefreshTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the refresh token.",
revokeRefreshTokenResponse.Exception);
}
return SignOut(
new AuthenticationProperties { RedirectUri = "CALL_BACK_URL" },
CookieAuthenticationDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme);
}
}
}
清除应用程序的本地 cookie 后,将用户发送到您的 Idsrv 的 end_session_endpoint
。 (您显示的用于清除会话的代码应该可以工作,否则我会在 startup.cs 中进行配置并进行调试以检查重定向是否真的删除了浏览器中的 cookie)。
例如https://demo.identityserver.io/.well-known/openid-configuration
你看到终点了。这应该删除 Idsrv 上的会话。根据您的设置,这可能会终止您在使用相同 Identity Server 实例的其他应用程序上的会话。
您可以在 docs 中阅读更多相关信息。
我没有发现 Angular 路由干扰服务器端路由的问题。只要您当然确实使用常规 window.location.replace
重定向到该页面
当然就像@Win 提到的那样,如果您使用它们,撤销 refresh_token 和 reference_tokens 将是一个很好的安全指南。
我们有一个构建在 MVC 身份验证之上的 angular 5 应用程序。应用程序由 Home/Index 操作提供,一旦应用程序加载,angular 路由会处理几乎所有事情。我们这样做主要是因为我们想使用 MVC 的身份验证过程(我们使用 Identity Server 4 作为我们的 Oath 系统)。
除一个例外外,此方法运行良好:注销。当我们尝试注销时,应用程序似乎立即重新授权并重新加载,而不是将我们返回到我们的 Identity Server 登录页面。
原来我们的开发环境是通过这段代码成功的:
[HttpPost]
public async Task<IActionResult> Logout()
{
foreach (string key in Request.Cookies.Keys)
{
Response.Cookies.Delete(key);
}
await HttpContext.SignOutAsync();
return Ok();
}
但这是误报,因为我们所有的应用程序都在本地主机上 运行,因此它们可以访问彼此的 cookie。因此,身份服务器 cookie 与 Angular 应用程序的 cookie 一起被清除。
我们试图用这样的东西替换那个逻辑:
public async Task Logout()
{
if (User?.Identity.IsAuthenticated == true)
{
// delete local authentication cookie
await HttpContext.SignOutAsync("Cookies");
await HttpContext.SignOutAsync("oidc");
}
}
但是,它在任何一个环境中都没有注销(但是该代码适用于我们使用相同身份服务器的 MVC 应用程序之一)。
为了提供一些背景知识,我们 Angular 应用程序的注销过程来自 material 菜单,然后我们在调用注销功能。此过程的代码片段:
注销按钮调用的方法:
public openDialog(): void {
let dialogRef = this.dialog.open(ModalComponent, {
width: '250px',
data: { text: this.logoutText, ok: this.okText, cancel: this.cancelText }
});
dialogRef.afterClosed().subscribe(result => {
if (result !== undefined) {
switch (result.data) {
case 'ok':
this.logout();
break;
case 'cancel':
break;
default:
break;
}
}
});
}
private logout(): void {
this.sessionService.logOut();
}
会话服务:
public logOut(): void {
this.auth.revokeToken();
this.http.post(this.logoutUrl, undefined).subscribe(x => window.location.reload());
}
如前所述,以这种方式调用注销以页面刷新结束,用户并没有真正注销。我们可能遗漏了一些简单的东西,但到目前为止我所做的所有修补工作都没有取得任何成功。
编辑:
我们的 angular 路由相当简单(尽管路径阻止我们调用类似 home/logout 的东西):
{
path: 'parts',
component: PartsComponent,
canActivate: [AuthGuard],
runGuardsAndResolvers: 'always',
data: { title: 'Parts' }
},
{
path: '',
redirectTo: 'parts',
pathMatch: 'full'
},
{
path: '**',
redirectTo: 'parts'
}
我们的 MVC 路由也相当简单
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
"Sitemap",
"sitemap.xml",
new { controller = "Home", action = "SitemapXml" });
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
我不确定我们如何可以轻松地按照 angular 路线路由到 home/logout。我猜我们必须为它添加一条路线。我们曾经尝试过,但它从未正确路由。我正在尝试查找有关我们尝试过的内容的笔记。
我无法回答 Angular;我还在努力。但是,当用户在客户端注销时,客户端 Web 应用程序可能会要求 IDP 撤销访问令牌和刷新令牌。例如,在 ASP.Net Core -
using System;
using System.Threading.Tasks;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace MY_APP.Controllers
{
public class AccountController : Controller
{
[HttpGet]
public async Task<IActionResult> SignOut()
{
var discoveryClient = new DiscoveryClient("IDP_URL");
var metaDataResponse = await discoveryClient.GetAsync();
var revocationClient = new TokenRevocationClient(
metaDataResponse.RevocationEndpoint,
"CLIENT_NAME",
"CLIENT_SECRET");
// revoke the access token
string accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
if (!string.IsNullOrWhiteSpace(accessToken))
{
var revokeAccessTokenResponse = await revocationClient.RevokeAccessTokenAsync(accessToken);
if (revokeAccessTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the access token.",
revokeAccessTokenResponse.Exception);
}
// revoke the refresh token
string refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
if (!string.IsNullOrWhiteSpace(refreshToken))
{
var revokeRefreshTokenResponse = await revocationClient.RevokeAccessTokenAsync(refreshToken);
if (revokeRefreshTokenResponse.IsError)
throw new Exception("Problem encountered while revoking the refresh token.",
revokeRefreshTokenResponse.Exception);
}
return SignOut(
new AuthenticationProperties { RedirectUri = "CALL_BACK_URL" },
CookieAuthenticationDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme);
}
}
}
清除应用程序的本地 cookie 后,将用户发送到您的 Idsrv 的 end_session_endpoint
。 (您显示的用于清除会话的代码应该可以工作,否则我会在 startup.cs 中进行配置并进行调试以检查重定向是否真的删除了浏览器中的 cookie)。
例如https://demo.identityserver.io/.well-known/openid-configuration
你看到终点了。这应该删除 Idsrv 上的会话。根据您的设置,这可能会终止您在使用相同 Identity Server 实例的其他应用程序上的会话。
您可以在 docs 中阅读更多相关信息。
我没有发现 Angular 路由干扰服务器端路由的问题。只要您当然确实使用常规 window.location.replace
当然就像@Win 提到的那样,如果您使用它们,撤销 refresh_token 和 reference_tokens 将是一个很好的安全指南。