授权检查

词汇解释

  • 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;
    }