微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

.net 5 如何使用自定义提供程序设置授权

如何解决.net 5 如何使用自定义提供程序设置授权

在我的 .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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。