带有 Owin 身份验证的单声道
Mono with Owin Authentication
我有一个 Owin 应用程序,我在 Mono 中 运行,我正在尝试获得身份验证以在其上正常工作。不过,我使用了 this page as a start. I quickly realized that Owin Authentication uses some Windows specific libraries. This question 上的信息对此有一个解决方法,我认为这就足够了。不是。
以下代码抛出另一个问题中描述的异常(在问题描述之外的另一个地方(参见代码中的注释))。如果我试图注释掉一些东西来定位关于 Owin 管道的错误异常被抛出(因为依赖)。如果我注释掉足以删除第一个异常再次出现。
是否有人能够在 Mono 的 Owin 中成功设置身份验证(使用 AspNet Identity)?
启动
public void Configure(IAppBuilder app)
{
// Add the AspNet Identity user manager to the Owin context
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// This was the first line to fail, but adding the AesDataProtectorProvider as
// described in the referred question fixed that
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
TicketDataFormat = new SecureDataFormat<AuthenticationTicket>(
DataSerializers.Ticket,
new AesDataProtectorProvider("myAuthKey"),
TextEncodings.Base64)
});
// This causes an exception complaining that the Windows only assembly
// DpapiDataProtector can't be loaded, as described in the referred question
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Set up the custom middleware as described in the first link
// Something inside this causes other exceptions.
app.Use(typeof(AuthMiddleware), app, new AuthOptions());
}
AuthMiddleware
public class AuthMiddleware : AuthenticationMiddleware<AuthOptions>
{
public AuthMiddleware(OwinMiddleware next, IAppBuilder app, AuthOptions options)
: base(next, options)
{
if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType))
{
options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
}
if (options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(typeof(AuthMiddleware).FullName,
options.AuthenticationType);
options.StateDataFormat = new PropertiesDataFormat(dataProtector);
}
}
protected override AuthenticationHandler<AuthOptions> CreateHandler()
{
return new AuthHandler();
}
}
AuthOptions
public class AuthOptions : AuthenticationOptions
{
public AuthOptions()
: base("MyApp")
{
Description.Caption = "MyApp";
// Where to redirect requests if not authenticated
CallbackPath = new PathString("/login"); AuthenticationMode = AuthenticationMode.Passive;
}
public PathString CallbackPath { get; set; }
public string SignInAsAuthenticationType { get; set; }
public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }
}
AuthHandler
public class AuthHandler : AuthenticationHandler<AuthOptions>
{
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
var identity = new ClaimsIdentity(Options.SignInAsAuthenticationType);
var properties = Options.StateDataFormat.Unprotect(Request.Query["state"]);
return Task.FromResult(new AuthenticationTicket(identity, properties));
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode == 401)
{
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge != null)
{
var state = challenge.Properties;
if (string.IsNullOrEmpty(state.RedirectUri))
{
state.RedirectUri = Request.Uri.ToString();
}
var stateString = Options.StateDataFormat.Protect(state);
Response.Redirect(WebUtilities.AddQueryString(Options.CallbackPath.Value, "state", stateString));
}
}
return Task.FromResult<object>(null);
}
public override async Task<bool> InvokeAsync()
{
Request.Environment.Add("Context", Context);
// If user is not logged in and tries to access any page that is not in
// the list of allowed pages, redirect to login page
if (Context.Authentication.User == null &&
!Request.Path.ToString().StartsWith("/login"))
{
Response.Redirect(Options.CallbackPath.Value);
return true;
}
return false;
}
}
GitHub 展示 Mono WebAPI、OAuth2 Bearer 令牌身份验证的存储库,AspNet.Identity + MySQL UserStore:
需要弄清楚的几件事:
- AspNet.Identity是一组接口(Microsoft.AspNet.Identity.Core)及其实现( Microsoft.AspNet.Identity.EntityFramework/Owin) 大致描述了 用于在数据库中持久保存 帐户 的存储库模式。
- AspNet.Identity不做authentication/authorization。它在处理密码的同时创建、保存、更新、删除用户。
- AspNet.Identity主要类使用的是UserManager(来自Microsoft.AspNet.Identity.Core) 和 SignInManger(来自 Microsoft.AspNet.Identity.Owin). SignInManager 不需要执行任何类型的 OWIN 身份验证。
- 您发布的代码严格 Microsoft.Owin 相关,与 AspNet.Identity 无关,除了ApplicationUserManager,在 Visual Studio 模板中扩展了 Microsoft.AspNet.Identity.UserManager。
显然,上面提交的代码包含的内容超出了使 Owin 身份验证正常工作所必需的(并且不适用于 Mono)。在研究并尝试了很多排列之后,我相信我现在已经在工作状态下进行了身份验证。我不知道它是否一定是正确的条件,但只要它有效...
使用以下设置,未登录的用户只能访问允许页面列表中的页面(由 AuthHandler
中的方法 public override async Task<bool> InvokeAsync()
控制)。
在登录页面上,我可以使用 ApplicationUserManager
来登录用户。登录后,可以访问 Owin 托管的所有页面。
启动
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
TicketDataFormat = new SecureDataFormat<AuthenticationTicket>(
DataSerializers.Ticket,
new AesDataProtectorProvider("myAuthKey"),
TextEncodings.Base64)
});
app.Use(typeof(AuthMiddleware), app, new AuthOptions());
AuthMiddleware
public class AuthMiddleware : AuthenticationMiddleware<AuthOptions>
{
public AuthMiddleware(OwinMiddleware next, IAppBuilder app, AuthOptions options)
: base(next, options)
{
if (options.StateDataFormat == null)
{
options.StateDataFormat = new PropertiesDataFormat(new AesDataProtectorProvider("myAuthKey"));
}
}
protected override AuthenticationHandler<AuthOptions> CreateHandler()
{
return new AuthHandler();
}
}
AuthHandler
public class AuthHandler : AuthenticationHandler<AuthOptions>
{
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
// This method never gets called in the current setup,
// but it is required because the compilation fails otherwise.
// Therefore only return an empty object.
return Task.FromResult<AuthenticationTicket>(null);
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode == 401)
{
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge != null)
{
var state = challenge.Properties;
if (string.IsNullOrEmpty(state.RedirectUri))
{
state.RedirectUri = Request.Uri.ToString();
}
var stateString = Options.StateDataFormat.Protect(state);
Response.Redirect(WebUtilities.AddQueryString(Options.CallbackPath.Value, "state", stateString));
}
}
return Task.FromResult<object>(null);
}
public override async Task<bool> InvokeAsync()
{
// If user is not logged in and tries to access any page that is not in
// the list of allowed pages, redirect to login page.
// Add any additional pages not protected by authentication here
if (Context.Authentication.User == null &&
!Request.Path.ToString().StartsWith("/login"))
{
Response.Redirect(Options.CallbackPath.Value);
return true;
}
return false;
}
}
我为单声道发出拉取请求 https://github.com/mono/mono/pull/3048。
它使异步 web 的同步执行 api 堆栈。
现在您可以通过此修复使用 Owin 身份验证。
我将留下一个与 OP 问题相关但未解决的答案,作为其他尚未跳转到 .NET Core 的人的资源。
在 Mono、Mod-Mono 和 Apache 上,我的经验是 regenerateIdentity
函数在 Mono 上失败,但是当 运行 从 VS 本地时会成功。
修复前 (startup.auth.cs)
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(2),
regenerateIdentity: (manager, user) => AuthenticationService.RefreshIdentityAsync(manager, user))
}
});
我在 Chrome 的开发工具(应用程序选项卡)中注意到 cookie 保留了下来,因为它
将在未来 2 周后到期。
我的解决方案是调整 cookie 的 'Expires' 时间,使其与验证间隔 对齐。现在客户端负责 cookie 驱逐,Mono 不必在重新生成时处理 cookie。我意识到这不是一个通用的修复程序,但对于许多人来说,我确信这已经足够好了。
修复后
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromMinutes(2),
SlidingExpiration = false,
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(2),
regenerateIdentity: (manager, user) => AuthenticationService.RefreshIdentityAsync(manager, user))
}
});
我有一个 Owin 应用程序,我在 Mono 中 运行,我正在尝试获得身份验证以在其上正常工作。不过,我使用了 this page as a start. I quickly realized that Owin Authentication uses some Windows specific libraries. This question 上的信息对此有一个解决方法,我认为这就足够了。不是。
以下代码抛出另一个问题中描述的异常(在问题描述之外的另一个地方(参见代码中的注释))。如果我试图注释掉一些东西来定位关于 Owin 管道的错误异常被抛出(因为依赖)。如果我注释掉足以删除第一个异常再次出现。
是否有人能够在 Mono 的 Owin 中成功设置身份验证(使用 AspNet Identity)?
启动
public void Configure(IAppBuilder app)
{
// Add the AspNet Identity user manager to the Owin context
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// This was the first line to fail, but adding the AesDataProtectorProvider as
// described in the referred question fixed that
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
TicketDataFormat = new SecureDataFormat<AuthenticationTicket>(
DataSerializers.Ticket,
new AesDataProtectorProvider("myAuthKey"),
TextEncodings.Base64)
});
// This causes an exception complaining that the Windows only assembly
// DpapiDataProtector can't be loaded, as described in the referred question
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Set up the custom middleware as described in the first link
// Something inside this causes other exceptions.
app.Use(typeof(AuthMiddleware), app, new AuthOptions());
}
AuthMiddleware
public class AuthMiddleware : AuthenticationMiddleware<AuthOptions>
{
public AuthMiddleware(OwinMiddleware next, IAppBuilder app, AuthOptions options)
: base(next, options)
{
if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType))
{
options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
}
if (options.StateDataFormat == null)
{
var dataProtector = app.CreateDataProtector(typeof(AuthMiddleware).FullName,
options.AuthenticationType);
options.StateDataFormat = new PropertiesDataFormat(dataProtector);
}
}
protected override AuthenticationHandler<AuthOptions> CreateHandler()
{
return new AuthHandler();
}
}
AuthOptions
public class AuthOptions : AuthenticationOptions
{
public AuthOptions()
: base("MyApp")
{
Description.Caption = "MyApp";
// Where to redirect requests if not authenticated
CallbackPath = new PathString("/login"); AuthenticationMode = AuthenticationMode.Passive;
}
public PathString CallbackPath { get; set; }
public string SignInAsAuthenticationType { get; set; }
public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }
}
AuthHandler
public class AuthHandler : AuthenticationHandler<AuthOptions>
{
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
var identity = new ClaimsIdentity(Options.SignInAsAuthenticationType);
var properties = Options.StateDataFormat.Unprotect(Request.Query["state"]);
return Task.FromResult(new AuthenticationTicket(identity, properties));
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode == 401)
{
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge != null)
{
var state = challenge.Properties;
if (string.IsNullOrEmpty(state.RedirectUri))
{
state.RedirectUri = Request.Uri.ToString();
}
var stateString = Options.StateDataFormat.Protect(state);
Response.Redirect(WebUtilities.AddQueryString(Options.CallbackPath.Value, "state", stateString));
}
}
return Task.FromResult<object>(null);
}
public override async Task<bool> InvokeAsync()
{
Request.Environment.Add("Context", Context);
// If user is not logged in and tries to access any page that is not in
// the list of allowed pages, redirect to login page
if (Context.Authentication.User == null &&
!Request.Path.ToString().StartsWith("/login"))
{
Response.Redirect(Options.CallbackPath.Value);
return true;
}
return false;
}
}
GitHub 展示 Mono WebAPI、OAuth2 Bearer 令牌身份验证的存储库,AspNet.Identity + MySQL UserStore:
需要弄清楚的几件事:
- AspNet.Identity是一组接口(Microsoft.AspNet.Identity.Core)及其实现( Microsoft.AspNet.Identity.EntityFramework/Owin) 大致描述了 用于在数据库中持久保存 帐户 的存储库模式。
- AspNet.Identity不做authentication/authorization。它在处理密码的同时创建、保存、更新、删除用户。
- AspNet.Identity主要类使用的是UserManager(来自Microsoft.AspNet.Identity.Core) 和 SignInManger(来自 Microsoft.AspNet.Identity.Owin). SignInManager 不需要执行任何类型的 OWIN 身份验证。
- 您发布的代码严格 Microsoft.Owin 相关,与 AspNet.Identity 无关,除了ApplicationUserManager,在 Visual Studio 模板中扩展了 Microsoft.AspNet.Identity.UserManager。
显然,上面提交的代码包含的内容超出了使 Owin 身份验证正常工作所必需的(并且不适用于 Mono)。在研究并尝试了很多排列之后,我相信我现在已经在工作状态下进行了身份验证。我不知道它是否一定是正确的条件,但只要它有效...
使用以下设置,未登录的用户只能访问允许页面列表中的页面(由 AuthHandler
中的方法 public override async Task<bool> InvokeAsync()
控制)。
在登录页面上,我可以使用 ApplicationUserManager
来登录用户。登录后,可以访问 Owin 托管的所有页面。
启动
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
},
TicketDataFormat = new SecureDataFormat<AuthenticationTicket>(
DataSerializers.Ticket,
new AesDataProtectorProvider("myAuthKey"),
TextEncodings.Base64)
});
app.Use(typeof(AuthMiddleware), app, new AuthOptions());
AuthMiddleware
public class AuthMiddleware : AuthenticationMiddleware<AuthOptions>
{
public AuthMiddleware(OwinMiddleware next, IAppBuilder app, AuthOptions options)
: base(next, options)
{
if (options.StateDataFormat == null)
{
options.StateDataFormat = new PropertiesDataFormat(new AesDataProtectorProvider("myAuthKey"));
}
}
protected override AuthenticationHandler<AuthOptions> CreateHandler()
{
return new AuthHandler();
}
}
AuthHandler
public class AuthHandler : AuthenticationHandler<AuthOptions>
{
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
// This method never gets called in the current setup,
// but it is required because the compilation fails otherwise.
// Therefore only return an empty object.
return Task.FromResult<AuthenticationTicket>(null);
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode == 401)
{
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge != null)
{
var state = challenge.Properties;
if (string.IsNullOrEmpty(state.RedirectUri))
{
state.RedirectUri = Request.Uri.ToString();
}
var stateString = Options.StateDataFormat.Protect(state);
Response.Redirect(WebUtilities.AddQueryString(Options.CallbackPath.Value, "state", stateString));
}
}
return Task.FromResult<object>(null);
}
public override async Task<bool> InvokeAsync()
{
// If user is not logged in and tries to access any page that is not in
// the list of allowed pages, redirect to login page.
// Add any additional pages not protected by authentication here
if (Context.Authentication.User == null &&
!Request.Path.ToString().StartsWith("/login"))
{
Response.Redirect(Options.CallbackPath.Value);
return true;
}
return false;
}
}
我为单声道发出拉取请求 https://github.com/mono/mono/pull/3048。 它使异步 web 的同步执行 api 堆栈。
现在您可以通过此修复使用 Owin 身份验证。
我将留下一个与 OP 问题相关但未解决的答案,作为其他尚未跳转到 .NET Core 的人的资源。
在 Mono、Mod-Mono 和 Apache 上,我的经验是 regenerateIdentity
函数在 Mono 上失败,但是当 运行 从 VS 本地时会成功。
修复前 (startup.auth.cs)
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(2),
regenerateIdentity: (manager, user) => AuthenticationService.RefreshIdentityAsync(manager, user))
}
});
我在 Chrome 的开发工具(应用程序选项卡)中注意到 cookie 保留了下来,因为它 将在未来 2 周后到期。
我的解决方案是调整 cookie 的 'Expires' 时间,使其与验证间隔 对齐。现在客户端负责 cookie 驱逐,Mono 不必在重新生成时处理 cookie。我意识到这不是一个通用的修复程序,但对于许多人来说,我确信这已经足够好了。
修复后
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromMinutes(2),
SlidingExpiration = false,
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(2),
regenerateIdentity: (manager, user) => AuthenticationService.RefreshIdentityAsync(manager, user))
}
});