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