zoukankan      html  css  js  c++  java
  • asp.net core 自定义 Policy 替换 AllowAnonymous 的行为

    asp.net core 自定义 Policy 替换 AllowAnonymous 的行为

    Intro

    最近对我们的服务进行了改造,原本内部服务在内部可以匿名调用,现在增加了限制,通过 identity server 来管理 api 和 client,网关和需要访问api的客户端或api服务相互调用通过 client_credencial 的方式来调用,这样一来我们可以清晰知道哪些 api 服务会被哪些 api/client 所调用,而且安全性来说更好。
    为了保持后端服务的代码更好的兼容性,希望能够实现相同的代码通过在 Startup 里不同的配置实现不同的 Authorization 逻辑,原来我们的服务的 Authorize 都是以 Authorize("policyName") 的形式来写的,这样一来我们只需要修改这个 Policy 的授权配置就可以了。对于 AllowAnonymous 就希望可以通过一种类似的方式来实现,通过自定义一个 Policy 来实现自己的逻辑

    实现方式

    将 action 上的 AllowAnonymous 替换为 Authorize("policyName"),在没有设置 Authorize 的 controller 上增加 Authorize("policyName")

    public class AllowAnonymousPolicyTransformer : IApplicationModelConvention
    {
        private readonly string _policyName;
    
        public AllowAnonymousPolicyTransformer() : this("anonymous")
        {
        }
    
        public AllowAnonymousPolicyTransformer(string policyName) => _policyName = policyName;
    
        public void Apply(ApplicationModel application)
        {
            foreach (var controllerModel in application.Controllers)
            {
                if (controllerModel.Filters.Any(_ => _.GetType() == typeof(AuthorizeFilter)))
                {
                    foreach (var actionModel in controllerModel.Actions)
                    {
                        if (actionModel.Filters.Any(_ => _.GetType() == typeof(AllowAnonymousFilter)))
                        {
                            var allowAnonymousFilter = actionModel.Filters.First(_ => _.GetType() == typeof(AllowAnonymousFilter));
                            actionModel.Filters.Remove(allowAnonymousFilter);
                            actionModel.Filters.Add(new AuthorizeFilter(_policyName));
                        }
                    }
                }
                else
                {
                    if (controllerModel.Filters.Any(_ => _.GetType() == typeof(AllowAnonymousFilter)))
                    {
                        var allowAnonymousFilter = controllerModel.Filters.First(_ => _.GetType() == typeof(AllowAnonymousFilter));
                        controllerModel.Filters.Remove(allowAnonymousFilter);
                    }
                    controllerModel.Filters.Add(new AuthorizeFilter(_policyName));
                }
            }
        }
    }
    
    public static class MvcBuilderExtensions
    {
        public static IMvcBuilder AddAnonymousPolicyTransformer(this IMvcBuilder builder)
        {
            builder.Services.Configure<MvcOptions>(options =>
            {
                options.Conventions.Insert(0, new AllowAnonymousPolicyTransformer());
            });
            return builder;
        }
    
        public static IMvcBuilder AddAnonymousPolicyTransformer(this IMvcBuilder builder, string policyName)
        {
            builder.Services.Configure<MvcOptions>(options =>
            {
                options.Conventions.Insert(0, new AllowAnonymousPolicyTransformer(policyName));
            });
            return builder;
        }
    }
    

    controller 中的代码:

    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        private readonly ILogger _logger;
    
        public ValuesController(ILogger<ValuesController> logger)
        {
            _logger = logger;
        }
    
        // GET api/values
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            var msg = $"IsAuthenticated: {User.Identity.IsAuthenticated} ,UserName: {User.Identity.Name}";
            _logger.LogInformation(msg);
            return new string[] { msg };
        }
    
        // GET api/values/5
        [Authorize]
        [HttpGet("{id:int}")]
        public ActionResult<string> Get(int id)
        {
            return "value";
        }
        // ...
    }
    

    Startup 中 ConfigureServices 配置:

    var anonymousPolicyName = "anonymous";
    
    services.AddAuthorization(options =>
    {
        options.AddPolicy(anonymousPolicyName, builder => builder.RequireAssertion(context => context.User.Identity.IsAuthenticated));
    
        options.DefaultPolicy = new AuthorizationPolicyBuilder(HeaderAuthenticationDefaults.AuthenticationSchema)
            .RequireAuthenticatedUser()
            .RequireAssertion(context => context.User.GetUserId<int>() > 0)
            .Build();
    });
    
    services.AddMvc(options =>
        {
            options.Conventions.Add(new ApiControllerVersionConvention());
        })
        .AddAnonymousPolicyTransformer(anonymousPolicyName)
        ;
    

    实现效果

    访问原来的匿名接口

    with custom anonymous policy

    userId 为0访问原来的匿名接口

    with header authentication && userId <= 0

    userId 大于0访问原来的匿名接口

    with header authentication && userId > 0

    userId 为0访问需要登录的接口
    with header authentication && userId <= 0 && userId >0 required

    userId 大于0访问需要登录的接口
    with header authentication && userId > 0 && userId >0 required

    More

    注:按照上面的做法已经可以做到自定义 policy 代替 AllowAnonymous 的行为,但是原来返回的401,现在可能返回到就是 403 了

    Reference

  • 相关阅读:
    Python之数据结构:元组
    Python之数据结构:列表
    python学习之路-第一天-接触python
    微信公众号的搭建-第五天-自定义菜单
    微信公众号的搭建-第四天(2)-获取并缓存access_token
    微信公众号的搭建-第四天-完成消息接受和返回响应的消息类型
    微信公众号的搭建-第三天-对请求、事件、响应类的封装
    鸟哥的Linux私房菜-第一部分-第3章主机规划与磁盘分区
    微信公众号的搭建-第二天-申请公众号并与本地测试服务器绑定
    微信公众号的搭建-第一天-搭建一个测试服务器
  • 原文地址:https://www.cnblogs.com/weihanli/p/11884392.html
Copyright © 2011-2022 走看看