asp.net core 自定義 Policy 替換 AllowAnonymous 的行為
阿新 • • 發佈:2019-11-21
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)
;
實現效果
訪問原來的匿名介面
userId 為0訪問原來的匿名介面
userId 大於0訪問原來的匿名介面
userId 為0訪問需要登入的介面
userId 大於0訪問需要登入的介面
More
注:按照上面的做法已經可以做到自定義 policy 代替 AllowAnonymous 的行為,但是原來返回的401,現在可能返回到就是 403 了
Reference
- https://github.com/WeihanLi/AspNetCorePlayground/blob/master/TestWebApplication