问题描述
在我的 .net 5 网站中,我必须从标题中读取用户登录信息并调用外部 Web 服务以检查是否已获得授权并获取权限列表。
编辑 3:
目标
- 从企业单点登录设置的 http 标头读取当前用户
- 通过调用外部网络服务读取用户权限和信息 让他们保持警惕,以防止每个动作都额外调用
- 让用户可以自由访问任何页面
- 默认情况下使用自定义声明授权所有控制器的操作
实际问题
context.User.Identity.IsAuthenticated 在中间件中始终为 false
实际代码
启动 - 配置服务
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 每次都是假的,即使之前的操作设置了主体
原文:
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
}
}
但外部网络服务返回为用户授权的控制器/操作列表,因此我必须测试所有操作执行以简单地检查名称是否包含在列表中。
有没有办法做到这一点,而不必以这种方式在每个动作或每个控制器上编写属性:
[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
,那么您可以执行以下操作:
services.AddControllers(options =>
{
options.Filters.Add(new MyAuthenticationFilter());
});
使用这种方法,您不再需要从 ActionFilterAttribute
继承,也不需要添加 [MyAuthentication(Roles = “Admin”)]
属性。
只需确保您允许对不需要身份验证和/或授权的操作进行匿名请求。
编辑 2:
对于更新的设置,请确保执行以下操作:
- 添加 cookie 身份验证
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie();
-
中间件顺序
app.UseRouting();
app.UseAuthentication();
app.UseMiddleware<AuthenticationMiddleware>();
app.UseAuthorization();
编辑 1:
我也不确定 string[] methods= GetMethods(userSSO) 是否由 .net core filterContext.HttpContext.User 缓存,避免多次调用外部网络服务。
过滤器的生命周期取决于您如何实现它,通常它是单例的,但您可以按照以下方法使其成为瞬态:
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,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.UseSession();
之后立即配置调用 app.UseRouting()
,以便会话在授权期间可用。
上面的代码将设置当前 HTTP 上下文的用户并将其保存在会话中。后续请求将尝试使用存储在会话中的用户。这也将使 DI 容器管理过滤器的生命周期。在 Filters in ASP.NET Core 中阅读更多相关信息。
我不建议您采用这种方法。请利用 .NET Core 中的身份验证中间件进行基于 cookie 或令牌的身份验证。
一旦请求到达操作执行,context.HttpContext.User.Identity.IsAuthenticated
现在将是 true
。