授权检查
词汇解释
- Authenticate:身份认证,识别用户身份的过程。
- Authorize:授权检查,确定当前用户是否有权访问资源的过程。
仅允许已登录用户访问
Nebula默认支持在Controller/Action上采用标记的方式做授权申明,例如:
[HttpPost]
[Authorize] // 注意这里
[Route("aaa.aspx")]
public string SomeMethod()
[Authorize] 标记打在Action方法上,表示当前方法仅支持【已登录用户】访问。
也可以将 [Authorize] 标记在Controller类型上,例如:
[Authorize] // 注意这里
[Route("/v20/api/WebSiteApp/Customer/")]
public class CustomerController : BaseTestDatabaseController
此时,【等同于】将 [Authorize] 标记在所有Action方法上。
如果此时需要排除某个方法,可以在Action上标记 [AllowAnonymous]
[HttpPost]
[AllowAnonymous] // 注意这里
[Route("bbb.aspx")]
public string SomeMethod2()
小结:
- [Authorize] 用于显式描述 当前功能仅限【已登录用户】访问
- [AllowAnonymous] 用于显式描述 当前功能允许供【未 登录用户】访问
- 以上2个标记可以出现在Controller/Action上,Action的标记将优先查找
注意:
- [Authorize], [AllowAnonymous] 这2个修饰属性定义在 Nebula.Common 命名空间下面。
基于角色的授权
以下标记表示当前资源仅允许 Admin, RoleX 这2类角色访问
[Authorize(Roles = "Admin,RoleX")]
当前用户是什么角色在创建 IUserInfo 时指定,例如:
IUserInfo userInfo = new WebUserInfo {
TenantId = tenantId,
UserId = userId,
UserName = "Fish Li",
UserRole = "Admin"
};
基于用户身份类型的授权
[Authorize(UserInfoType = typeof(VenusUserInfo))]
// 或者验证第三方客户端
[Authorize(UserInfoType = typeof(AppClientInfo))]
[Authorize(UserInfoType = typeof(AppClientInfo), Users = "AppId_xxxx")]
对应的登录代码片段:
VenusUserInfo userInfo = new VenusUserInfo {
UserId = user.Name,
UserName = user.Value,
};
int seconds = 7 * 24 * 60 * 60; // 7 day
AuthenticationManager.Login(userInfo, seconds);
简单理解:
- 在调用 AuthenticationManager.Login(...) 时使用了某个特定的【用户身份类型】
- 在授权检查时,就要求必须与登录时指定的【用户身份类型】一致
基于特定用户的授权
如果是比较简单的小项目,可以采用直接用户名的方式授权,例如:
[Authorize(Users = "Name1,Name2")]
- "Name1,Name2" 就是允许的登录名,
- 也可以是一个,
- 多个名称用分号分开
基于权限编号的授权
比较复杂的应用程序,可以按功能模块来实现授权,例如:
[Authorize(Rights = "A002")]
"A00002" 表示一个功能权限编号。
使用这个授权需要实现一个 BaseUserAuthProvider 抽象类,例如:
public class MyUserAuthProvider : BaseUserAuthProvider
{
private IUserInfo _userInfo;
protected override void SetDbContext(IUserInfo userInfo)
{
_userInfo = userInfo;
// 注意:FindRole,FindUser 的默认实现方式是需要查询数据库的,
// 因此需要在这里切换数据库连接,即设置基类的 _dbContext 字段
}
public override RightsRole FindRole(string roleId)
{
// 此方法用于获取角色对应的权限描述,这里不演示
return null;
}
public override RightsUser FindUser(string userId)
{
// 这里是演示代码,就直接返回了,真实情况需要查询数据库或者缓存服务
return new RightsUser {
UserId = userId,
UserName = _userInfo.UserName,
RoleIds = "Admin",
AllowRights = "22,23"
};
}
}
重点解释:
- FindUser方法,返回某个用户直接允许的权限,
- FindRole方法,返回某个用户所属的角色,
- 角色也可以赋予权限,
- 还可以给用户赋予否决权限,用于排除继承权限,
- 所以最终用户的权限会合并:单独授权 + 角色继承 - 否决权限
注册 UserAuthProvider
public class Program
{
public static void Main(string[] args)
{
AppStartOption startOption = new AppStartOption {
// 自定义的授权提供者
UserAuthProviderType = typeof(MyUserAuthProvider)
};
AppStartup.RunAsWebsite("XDemo.WebSiteApp", args, startOption);
}
}
在代码中判断用户和权限
可以调用 Controller.CheckRights("...") ,例如:
public class ActionBLL : BaseBLL
{
private void CheckOwner(ApiAction existObject)
{
IUserInfo user = this.GetUserInfo();
bool currentIsAdmin = this.Controller.CheckRights("d001");
string currentUser = user.UserId;
// 只有管理员可以修改其他用户创建的接口
// 普通用户不允许修改其他用户创建的接口
if( currentIsAdmin == false && existObject.Createor != currentUser )
throw new ForbiddenException("不能修改其他用户创建的接口。");
}
扩展[Authorize]标记
如果觉得[Authorize]的功能不能满足业务需求,还可以从AuthorizeAttribute继承实现自己的派生类,只要重写AuthenticateRequest方法就可以了,以下代码是AuthorizeAttribute的默认实现:
/// <summary>
/// 执行授权检查验证的标记
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class AuthorizeAttribute : Attribute
{
/// <summary>
/// 执行授权检查
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public virtual bool AuthenticateRequest(NHttpContext context)
{
IUserInfo userInfo = context.GetUserInfo();
// 当前用户没有登录
if( userInfo == null )
return false;
// 检查 IUserInfo 的实例类型是不是预期类型
if( _userInfoType != null && userInfo.GetType() != _userInfoType )
return false;
// 没有任何明确的授权要求,此时只要是“已登录”用户就算通过检查
if( _users == null && _roles == null && _rightsArray == null )
return true;
// 三种授权条件,只要一个符合就认为是检查通过
if( CheckUser(context, userInfo) || CheckRole(context, userInfo) || CheckRights(context, userInfo) )
return true;
// 所有条件都不匹配,授权检查失败!
return false;
}