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 來實現本身的邏輯git

實現方式

將 action 上的 AllowAnonymous 替換爲 Authorize("policyName"),在沒有設置 Authorize 的 controller 上增長 Authorize("policyName")github

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 配置:api

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訪問原來的匿名接口app

with header authentication && userId <= 0

userId 大於0訪問原來的匿名接口asp.net

with header authentication && userId > 0

userId 爲0訪問須要登陸的接口
with header authentication && userId <= 0 && userId >0 requiredide

userId 大於0訪問須要登陸的接口
with header authentication && userId > 0 && userId >0 requiredui

More

注:按照上面的作法已經能夠作到自定義 policy 代替 AllowAnonymous 的行爲,可是原來返回的401,如今可能返回到就是 403 了this

Reference

相關文章
相關標籤/搜索