在ASP.NET Core中,重新设计了一种更加灵活的授权方式:基于策略的授权, 它是授权的核心.
在使用基于策略的授权时,首先要定义授权策略,而授权策略本质上就是对Claims的一系列断言。
基于角色的授权和基于Scheme的授权,只是一种语法上的便捷,最终都会生成授权策略。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization(options =>
{
//options.AddPolicy("Administrator", policy => policy.RequireRole("administrator"));
options.AddPolicy("Administrator", policy => policy.RequireClaim(ClaimTypes.Role, "administrator"));
//options.AddPolicy("Founders", policy => policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});
}
[Authorize(Policy = "Administrator")]
public ActionResult<IEnumerable<string>> GetValueByAdminPolicy()
{
return new string[] { "GetValueByAdminPolicy" };
}
自定义策略授权
基于策略的授权中有一个很重要的概念是Requirements,每一个Requirement都代表一个授权条件。
Requirement需要继承接口IAuthorizationRequirement。
在 ASP.NET Core 中已经内置了一些常用的实现:
AssertionRequirement :使用最原始的断言形式来声明授权策略。
DenyAnonymousAuthorizationRequirement :用于表示禁止匿名用户访问的授权策略,并在AuthorizationOptions中将其设置为默认策略。
ClaimsAuthorizationRequirement :用于表示判断Cliams中是否包含预期的Claims的授权策略。
RolesAuthorizationRequirement :用于表示使用ClaimsPrincipal.IsInRole来判断是否包含预期的Role的授权策略。
NameAuthorizationRequirement:用于表示使用ClaimsPrincipal.Identities.Name来判断是否包含预期的Name的授权策略。
OperationAuthorizationRequirement:用于表示基于操作的授权策略。
除了OperationAuthorizationRequirement外,都有对应的快捷添加方法,比如RequireClaim,RequireRole,RequireUserName等。
当内置的Requirement不能满足需求时,可以定义自己的Requirement.
下面演示自定义策略授权
Startup类配置
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("Permission", policy => policy.Requirements.Add(new PermissionRequirement()));
}).AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.LoginPath = new PathString("/api/values/login");
});
services.AddTransient<IUserService, UserService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
services.AddControllers();
}
Configure方法添加
app.UseAuthentication();
app.UseAuthorization();
新建类PermissionRequirement
public class PermissionRequirement : IAuthorizationRequirement
{
}
新建类PermissionHandler
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IUserService _userService;
private readonly IHttpContextAccessor _accessor;
public PermissionHandler(IUserService userService, IHttpContextAccessor accessor)
{
_userService = userService;
_accessor = accessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var httpContext = _accessor.HttpContext;
var isAuthenticated = httpContext.User.Identity.IsAuthenticated;
if (isAuthenticated)
{
Guid userId;
if (!Guid.TryParse(httpContext.User.Claims.SingleOrDefault(s => s.Type == "id").Value, out userId))
{
return Task.CompletedTask;
}
var functions = _userService.GetFunctionsByUserId(userId);
var requestUrl = httpContext.Request.Path.Value.ToLower();
if (functions != null && functions.Count > 0 && functions.Contains(requestUrl))
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
添加测试用户数据,项目中需从数据库读取
public static class TestUsers
{
public static List<User> Users = new List<User>
{
new User{ Id = Guid.NewGuid(), UserName = "Paul", Password = "Paul123", Roles = new List<string>{ "administrator", "api_access" }, Urls = new List<string>{ "/api/values/getadminvalue", "/api/values/getguestvalue" }},
new User{ Id = Guid.NewGuid(), UserName = "Young", Password = "Young123", Roles = new List<string>{ "api_access" }, Urls = new List<string>{ "/api/values/getguestvalue" }},
new User{ Id = Guid.NewGuid(), UserName = "Roy", Password = "Roy123", Roles = new List<string>{ "administrator" }, Urls = new List<string>{ "/api/values/getadminvalue" }},
};
}
public class User
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public List<string> Roles { get; set; }
public List<string> Urls { get; set; }
}
添加UserService
public interface IUserService
{
List<string> GetFunctionsByUserId(Guid id);
}
public class UserService : IUserService
{
public List<string> GetFunctionsByUserId(Guid id)
{
var user = TestUsers.Users.SingleOrDefault(r => r.Id.Equals(id));
return user?.Urls;
}
}
添加api类
[Authorize(Policy = "Permission")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet("[action]")]
public ActionResult<IEnumerable<string>> GetAdminValue()
{
return new string[] { "use Policy = Permission" };
}
[HttpGet("[action]")]
public ActionResult<IEnumerable<string>> GetGuestValue()
{
return new string[] { "use Policy = Permission" };
}
[AllowAnonymous]
[HttpPost("[action]")]
public async Task<ActionResult<bool>> Login([FromBody]UserDto dto)
{
var user = TestUsers.Users.FirstOrDefault(s => s.UserName == dto.UserName && s.Password == dto.Password);
if (user != null)
{
//用户标识
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Sid, user.UserName));
identity.AddClaim(new Claim("id", user.Id.ToString()));
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
if (dto.ReturnUrl != null)
{
return Redirect(dto.ReturnUrl);
}
else
{
return RedirectToAction(nameof(ValuesController.GetAdminValue), "Values");
}
}
else
{
const string badUserNameOrPasswordMessage = "用户名或密码错误!";
return BadRequest(badUserNameOrPasswordMessage);
}
}
public class UserDto
{
public string UserName { get; set; }
public string Password { get; set; }
public string ReturnUrl { get; set; }
}
}
然后我们用Paul用户可以访问所有的接口,Young只能访问getguestvalue接口,Roy只能访问getadminvalue接口。