.net 5 如何使用自定义提供程序设置授权
.net 5 how to setup authorization using custom provider
在我的 .net 5 网站中,我必须从 header 读取用户登录并调用外部网络服务来检查是否已授权并获取权限列表。
编辑 3:
目标
- 从公司单sign-on
设置的httpheader读取当前用户
- 通过调用外部网络服务读取用户权限和信息
让他们戴上帽子,以防止 extra-calls 每个动作
- 让用户可以通过任何页面自由访问
- 默认情况下使用自定义声明授权所有控制器的操作
实际问题
context.User.Identity.IsAuthenticated 在中间件中总是 false
实际代码
启动 - ConfigureServices
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
services.AddControllers(options => { options.Filters.Add<AuditAuthorizationFilter>(); });
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
启动-配置
app.UseMiddleware<AuthenticationMiddleware>();
app.UseAuthentication();
app.UseAuthorization();
中间件
public class AuthenticationMiddleware
{
private readonly RequestDelegate _next;
// Dependency Injection
public AuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.User.Identity.IsAuthenticated)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, context.Request.Headers["Token"]),
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaultsAuthenticationScheme);
var authProperties = new AuthenticationProperties();
await context.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
}
await _next(context);
}
}
过滤
public class AuditAuthorizationFilter : IAuthorizationFilter, IOrderedFilter
{
public int Order => -1;
private readonly IHttpContextAccessor _httpContextAccessor;
public AuditAuthorizationFilter(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.User.Identity.IsAuthenticated)
{
context.Result = new ForbidResult();
}
else
{
string metodo = $"{context.RouteData.Values["controller"]}/{context.RouteData.Values["action"]}";
if (!context.HttpContext.User.HasClaim("type", metodo))
{
context.Result = new ForbidResult();
}
}
}
}
编辑 2:
我的启动
public void ConfigureServices(IServiceCollection services)
{
services.AddDevExpressControls();
services.AddTransient<ILoggingService, LoggingService>();
services.AddHttpContextAccessor();
services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_3_0);
services.ConfigureReportingServices(configurator => {
configurator.UseAsyncEngine();
configurator.ConfigureWebDocumentViewer(viewerConfigurator => {
viewerConfigurator.UseCachedReportSourceBuilder();
});
});
services.AddControllersWithViews().AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddControllers(options => { options.Filters.Add(new MyAuthenticationAttribute ()); });
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
app.UseDevExpressControls();
app.UseExceptionHandlerMiddleware(Log.Logger, errorPagePath: "/Error/HandleError" , respondWithJsonErrorDetails: true);
app.UseStatusCodePagesWithReExecute("/Error/HandleError/{0}");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSerilogRequestLogging(opts => opts.EnrichDiagnosticContext = LogHelper.EnrichFromRequest);
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
编辑 1:
为了使原始代码适应 .net 5,我做了一些更改:
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
const string MyHeaderToken = "HTTP_KEY";
string userSSO = null;
if (string.IsNullOrWhiteSpace(context.HttpContext.Request.Headers[MyHeaderToken]))
{
userSSO = context.HttpContext.Request.Headers[MyHeaderToken];
}
if (string.IsNullOrWhiteSpace(userSSO))
{
//filterContext.Result = new unh();
}
else
{
// Create GenericPrincipal
GenericIdentity webIdentity = new GenericIdentity(userSSO, "My");
//string[] methods = new string[0]; // GetMethods(userSSO);
GenericPrincipal principal = new GenericPrincipal(webIdentity, null);
IdentityUser user = new (userSSO);
Thread.CurrentPrincipal = principal;
}
}
但是 context.HttpContext.User.Identity.IsAuthenticated 每次都是假的,即使之前的操作设置了 principal
原版:
我使用自定义属性以这种方式管理此场景:
public class MyAuthenticationAttribute : ActionFilterAttribute, IAuthenticationFilter{
public string[] Roles { get; set; }
public void OnAuthentication(AuthenticationContext filterContext)
{
string MyHeaderToken = “SM_USER”;
string userSSO = null;
if (HttpContext.Current.Request.Headers[MyHeaderToken] != null)
{
userSSO = HttpContext.Current.Request.Headers[MyHeaderToken];
Trace.WriteLine(string.Format(“got MyToken: {0}”, userSSO));
}
if (string.IsNullOrWhiteSpace(userSSO))
{
Trace.WriteLine(“access denied, no token found”);
}
else
{
// Create GenericPrincipal
GenericIdentity webIdentity = new GenericIdentity(userSSO, “My”);
string[] methods= GetMethods(userSSO);
GenericPrincipal principal = new GenericPrincipal(webIdentity, methods);
filterContext.HttpContext.User = principal;
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
//check authorizations
}
}
但是外部网络服务 returns 列表 controller/action 授权给用户,所以我必须测试所有操作执行以简单地检查名称是否包含在列表中。
有没有办法做到这一点,而不必以这种方式在每个动作或每个控制器上写属性:
[MyAuthentication(Roles = “Admin”)]
pubic class AdminController: Controller
{
}
我知道我可以使用
services.AddMvc(o =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
o.Filters.Add(new AuthorizeFilter(policy));
});
但不知道如何通过我的自定义授权使用它
我也不确定 string[] methods= GetMethods(userSSO)
是否被 .net 核心缓存 filterContext.HttpContext.User
避免多次调用外部网络服务。
谢谢
如果你想在全球范围内应用你的自定义 IAuthenticationFilter
那么你可以执行 ff:
services.AddControllers(options =>
{
options.Filters.Add(new MyAuthenticationFilter());
});
使用这种方法,您不再需要从 ActionFilterAttribute
继承,也不需要添加 [MyAuthentication(Roles = “Admin”)]
属性。
只需确保您允许对不需要身份验证 and/or 授权的操作进行匿名请求。
编辑 2:
对于更新的设置,请确保执行以下操作:
- 添加cookie认证
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie();
中间件顺序
app.UseRouting();
app.UseAuthentication();
app.UseMiddleware<AuthenticationMiddleware>();
app.UseAuthorization();
编辑 1:
i'am also not sure if string[] methods= GetMethods(userSSO) is cached by .net core filterContext.HttpContext.User avoiding multiple calls to external webservice.
过滤器的生命周期取决于你如何实现它,通常它是单例的,但你可以通过以下方法使其成为瞬态的:
public class MyAuthorizationFilter : IAuthorizationFilter, IOrderedFilter
{
public int Order => -1; // Ensures that it runs first before basic Authorize filter
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
if (context.HttpContext.Session.IsAvailable
&& context.HttpContext.Session.TryGetValue("_SessionUser", out byte[] _user))
{
SessionUser su = (SessionUser)this.ByteArrayToObject(_user);
GenericPrincipal principal = this.CreateGenericPrincipal(su.IdentityName, su.Type, su.Roles);
context.HttpContext.User = principal;
}
else
{
const string MyHeaderToken = "HTTP_KEY";
string userSSO = null;
if (!string.IsNullOrWhiteSpace(context.HttpContext.Request.Headers[MyHeaderToken]))
{
userSSO = context.HttpContext.Request.Headers[MyHeaderToken];
}
userSSO = "TestUser";
if (string.IsNullOrWhiteSpace(userSSO))
{
//filterContext.Result = new unh();
}
else
{
string identityType = "My";
string[] methods = new string[0]; // GetMethods(userSSO);
// Create GenericPrincipal
GenericPrincipal principal = this.CreateGenericPrincipal(userSSO, identityType, methods);
context.HttpContext.User = principal;
if (context.HttpContext.Session.IsAvailable)
{
SessionUser su = new SessionUser()
{
IdentityName = principal.Identity.Name,
Type = principal.Identity.AuthenticationType,
Roles = methods
};
byte[] _sessionUser = this.ObjectToByteArray(su);
context.HttpContext.Session.Set("_SessionUser", _sessionUser);
}
}
}
}
}
private GenericPrincipal CreateGenericPrincipal(string name, string type, string[] roles)
{
GenericIdentity webIdentity = new GenericIdentity(name, type);
GenericPrincipal principal = new GenericPrincipal(webIdentity, roles);
return principal;
}
// Convert an object to a byte array
private byte[] ObjectToByteArray(Object obj)
{
BinaryFormatter bf = new BinaryFormatter();
using (var ms = new MemoryStream())
{
bf.Serialize(ms, obj);
return ms.ToArray();
}
}
// Convert a byte array to an Object
private Object ByteArrayToObject(byte[] arrBytes)
{
using (var memStream = new MemoryStream())
{
var binForm = new BinaryFormatter();
memStream.Write(arrBytes, 0, arrBytes.Length);
memStream.Seek(0, SeekOrigin.Begin);
var obj = binForm.Deserialize(memStream);
return obj;
}
}
[Serializable]
private class SessionUser
{
public string IdentityName { get; set; }
public string Type { get; set; }
public string[] Roles { get; set; }
}
}
public class MyAuthorizationAttribute : TypeFilterAttribute
{
public MyAuthorizationAttribute()
: base(typeof(MyAuthorizationFilter))
{
}
}
On Startup.cs > 配置在 app.UseRouting()
之后立即调用 app.UseSession();
,以便会话在授权期间可用。
上面的代码将设置当前 HTTP 上下文的用户并将其保存在会话中。后续请求将尝试使用存储在会话中的用户。这也将使 DI 容器管理过滤器的生命周期。在 Filters in ASP.NET Core.
中阅读更多相关信息
我不建议您采用这种方法。请利用 .NET Core 中的身份验证中间件进行基于 cookie 或令牌的身份验证。
一旦请求到达操作执行,context.HttpContext.User.Identity.IsAuthenticated
现在将是 true
。
在我的 .net 5 网站中,我必须从 header 读取用户登录并调用外部网络服务来检查是否已授权并获取权限列表。
编辑 3:
目标
- 从公司单sign-on 设置的httpheader读取当前用户
- 通过调用外部网络服务读取用户权限和信息 让他们戴上帽子,以防止 extra-calls 每个动作
- 让用户可以通过任何页面自由访问
- 默认情况下使用自定义声明授权所有控制器的操作
实际问题
context.User.Identity.IsAuthenticated 在中间件中总是 false
实际代码
启动 - ConfigureServices
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
services.AddControllers(options => { options.Filters.Add<AuditAuthorizationFilter>(); });
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
启动-配置
app.UseMiddleware<AuthenticationMiddleware>();
app.UseAuthentication();
app.UseAuthorization();
中间件
public class AuthenticationMiddleware
{
private readonly RequestDelegate _next;
// Dependency Injection
public AuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.User.Identity.IsAuthenticated)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, context.Request.Headers["Token"]),
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaultsAuthenticationScheme);
var authProperties = new AuthenticationProperties();
await context.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
}
await _next(context);
}
}
过滤
public class AuditAuthorizationFilter : IAuthorizationFilter, IOrderedFilter
{
public int Order => -1;
private readonly IHttpContextAccessor _httpContextAccessor;
public AuditAuthorizationFilter(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.User.Identity.IsAuthenticated)
{
context.Result = new ForbidResult();
}
else
{
string metodo = $"{context.RouteData.Values["controller"]}/{context.RouteData.Values["action"]}";
if (!context.HttpContext.User.HasClaim("type", metodo))
{
context.Result = new ForbidResult();
}
}
}
}
编辑 2:
我的启动
public void ConfigureServices(IServiceCollection services)
{
services.AddDevExpressControls();
services.AddTransient<ILoggingService, LoggingService>();
services.AddHttpContextAccessor();
services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_3_0);
services.ConfigureReportingServices(configurator => {
configurator.UseAsyncEngine();
configurator.ConfigureWebDocumentViewer(viewerConfigurator => {
viewerConfigurator.UseCachedReportSourceBuilder();
});
});
services.AddControllersWithViews().AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddControllers(options => { options.Filters.Add(new MyAuthenticationAttribute ()); });
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
app.UseDevExpressControls();
app.UseExceptionHandlerMiddleware(Log.Logger, errorPagePath: "/Error/HandleError" , respondWithJsonErrorDetails: true);
app.UseStatusCodePagesWithReExecute("/Error/HandleError/{0}");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSerilogRequestLogging(opts => opts.EnrichDiagnosticContext = LogHelper.EnrichFromRequest);
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
编辑 1: 为了使原始代码适应 .net 5,我做了一些更改:
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
const string MyHeaderToken = "HTTP_KEY";
string userSSO = null;
if (string.IsNullOrWhiteSpace(context.HttpContext.Request.Headers[MyHeaderToken]))
{
userSSO = context.HttpContext.Request.Headers[MyHeaderToken];
}
if (string.IsNullOrWhiteSpace(userSSO))
{
//filterContext.Result = new unh();
}
else
{
// Create GenericPrincipal
GenericIdentity webIdentity = new GenericIdentity(userSSO, "My");
//string[] methods = new string[0]; // GetMethods(userSSO);
GenericPrincipal principal = new GenericPrincipal(webIdentity, null);
IdentityUser user = new (userSSO);
Thread.CurrentPrincipal = principal;
}
}
但是 context.HttpContext.User.Identity.IsAuthenticated 每次都是假的,即使之前的操作设置了 principal
原版:
我使用自定义属性以这种方式管理此场景:
public class MyAuthenticationAttribute : ActionFilterAttribute, IAuthenticationFilter{
public string[] Roles { get; set; }
public void OnAuthentication(AuthenticationContext filterContext)
{
string MyHeaderToken = “SM_USER”;
string userSSO = null;
if (HttpContext.Current.Request.Headers[MyHeaderToken] != null)
{
userSSO = HttpContext.Current.Request.Headers[MyHeaderToken];
Trace.WriteLine(string.Format(“got MyToken: {0}”, userSSO));
}
if (string.IsNullOrWhiteSpace(userSSO))
{
Trace.WriteLine(“access denied, no token found”);
}
else
{
// Create GenericPrincipal
GenericIdentity webIdentity = new GenericIdentity(userSSO, “My”);
string[] methods= GetMethods(userSSO);
GenericPrincipal principal = new GenericPrincipal(webIdentity, methods);
filterContext.HttpContext.User = principal;
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
//check authorizations
}
}
但是外部网络服务 returns 列表 controller/action 授权给用户,所以我必须测试所有操作执行以简单地检查名称是否包含在列表中。
有没有办法做到这一点,而不必以这种方式在每个动作或每个控制器上写属性:
[MyAuthentication(Roles = “Admin”)]
pubic class AdminController: Controller
{
}
我知道我可以使用
services.AddMvc(o =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
o.Filters.Add(new AuthorizeFilter(policy));
});
但不知道如何通过我的自定义授权使用它
我也不确定 string[] methods= GetMethods(userSSO)
是否被 .net 核心缓存 filterContext.HttpContext.User
避免多次调用外部网络服务。
谢谢
如果你想在全球范围内应用你的自定义 IAuthenticationFilter
那么你可以执行 ff:
services.AddControllers(options =>
{
options.Filters.Add(new MyAuthenticationFilter());
});
使用这种方法,您不再需要从 ActionFilterAttribute
继承,也不需要添加 [MyAuthentication(Roles = “Admin”)]
属性。
只需确保您允许对不需要身份验证 and/or 授权的操作进行匿名请求。
编辑 2:
对于更新的设置,请确保执行以下操作:
- 添加cookie认证
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie();
中间件顺序
app.UseRouting();
app.UseAuthentication();
app.UseMiddleware<AuthenticationMiddleware>();
app.UseAuthorization();
编辑 1:
i'am also not sure if string[] methods= GetMethods(userSSO) is cached by .net core filterContext.HttpContext.User avoiding multiple calls to external webservice.
过滤器的生命周期取决于你如何实现它,通常它是单例的,但你可以通过以下方法使其成为瞬态的:
public class MyAuthorizationFilter : IAuthorizationFilter, IOrderedFilter
{
public int Order => -1; // Ensures that it runs first before basic Authorize filter
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
if (context.HttpContext.Session.IsAvailable
&& context.HttpContext.Session.TryGetValue("_SessionUser", out byte[] _user))
{
SessionUser su = (SessionUser)this.ByteArrayToObject(_user);
GenericPrincipal principal = this.CreateGenericPrincipal(su.IdentityName, su.Type, su.Roles);
context.HttpContext.User = principal;
}
else
{
const string MyHeaderToken = "HTTP_KEY";
string userSSO = null;
if (!string.IsNullOrWhiteSpace(context.HttpContext.Request.Headers[MyHeaderToken]))
{
userSSO = context.HttpContext.Request.Headers[MyHeaderToken];
}
userSSO = "TestUser";
if (string.IsNullOrWhiteSpace(userSSO))
{
//filterContext.Result = new unh();
}
else
{
string identityType = "My";
string[] methods = new string[0]; // GetMethods(userSSO);
// Create GenericPrincipal
GenericPrincipal principal = this.CreateGenericPrincipal(userSSO, identityType, methods);
context.HttpContext.User = principal;
if (context.HttpContext.Session.IsAvailable)
{
SessionUser su = new SessionUser()
{
IdentityName = principal.Identity.Name,
Type = principal.Identity.AuthenticationType,
Roles = methods
};
byte[] _sessionUser = this.ObjectToByteArray(su);
context.HttpContext.Session.Set("_SessionUser", _sessionUser);
}
}
}
}
}
private GenericPrincipal CreateGenericPrincipal(string name, string type, string[] roles)
{
GenericIdentity webIdentity = new GenericIdentity(name, type);
GenericPrincipal principal = new GenericPrincipal(webIdentity, roles);
return principal;
}
// Convert an object to a byte array
private byte[] ObjectToByteArray(Object obj)
{
BinaryFormatter bf = new BinaryFormatter();
using (var ms = new MemoryStream())
{
bf.Serialize(ms, obj);
return ms.ToArray();
}
}
// Convert a byte array to an Object
private Object ByteArrayToObject(byte[] arrBytes)
{
using (var memStream = new MemoryStream())
{
var binForm = new BinaryFormatter();
memStream.Write(arrBytes, 0, arrBytes.Length);
memStream.Seek(0, SeekOrigin.Begin);
var obj = binForm.Deserialize(memStream);
return obj;
}
}
[Serializable]
private class SessionUser
{
public string IdentityName { get; set; }
public string Type { get; set; }
public string[] Roles { get; set; }
}
}
public class MyAuthorizationAttribute : TypeFilterAttribute
{
public MyAuthorizationAttribute()
: base(typeof(MyAuthorizationFilter))
{
}
}
On Startup.cs > 配置在 app.UseRouting()
之后立即调用 app.UseSession();
,以便会话在授权期间可用。
上面的代码将设置当前 HTTP 上下文的用户并将其保存在会话中。后续请求将尝试使用存储在会话中的用户。这也将使 DI 容器管理过滤器的生命周期。在 Filters in ASP.NET Core.
中阅读更多相关信息我不建议您采用这种方法。请利用 .NET Core 中的身份验证中间件进行基于 cookie 或令牌的身份验证。
一旦请求到达操作执行,context.HttpContext.User.Identity.IsAuthenticated
现在将是 true
。