zoukankan      html  css  js  c++  java
  • ABP+AdminLTE+Bootstrap Table权限管理系统第八节--ABP错误机制及AbpSession相关

      返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期

         上一节我们讲到登录逻辑,我做的登录逻辑很简单的,我们来看一下abp module-zero里面的登录代码.

     #region Login / Logout
    
            public ActionResult Login(string returnUrl = "")
            {
                if (string.IsNullOrWhiteSpace(returnUrl))
                {
                    returnUrl = Request.ApplicationPath;
                }
    
                return View(
                    new LoginFormViewModel
                    {
                        ReturnUrl = returnUrl,
                        IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled
                    });
            }
    
            [HttpPost]
            public async Task<JsonResult> Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "")
            {
                CheckModelState();
    
                var loginResult = await GetLoginResultAsync(
                    loginModel.UsernameOrEmailAddress,
                    loginModel.Password,
                    loginModel.TenancyName
                    );
    
                await SignInAsync(loginResult.User, loginResult.Identity, loginModel.RememberMe);
    
                if (string.IsNullOrWhiteSpace(returnUrl))
                {
                    returnUrl = Request.ApplicationPath;
                }
    
                if (!string.IsNullOrWhiteSpace(returnUrlHash))
                {
                    returnUrl = returnUrl + returnUrlHash;
                }
    
                return Json(new AjaxResponse { TargetUrl = returnUrl });
            }
    
            private async Task<AbpLoginResult<Tenant, User>> GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName)
            {
                try
                {
    
                    var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);
    
                    switch (loginResult.Result)
                    {
                        case AbpLoginResultType.Success:
                            return loginResult;
                        default:
                            throw CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName);
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
    
            }
    
            private async Task SignInAsync(User user, ClaimsIdentity identity = null, bool rememberMe = false)
            {
                if (identity == null)
                {
                    identity = await _userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
                }
    
                AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
                AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = rememberMe }, identity);
            }
    
            private Exception CreateExceptionForFailedLoginAttempt(AbpLoginResultType result, string usernameOrEmailAddress, string tenancyName)
            {
                switch (result)
                {
                    case AbpLoginResultType.Success:
                        return new ApplicationException("Don't call this method with a success result!");
                    case AbpLoginResultType.InvalidUserNameOrEmailAddress:
                    case AbpLoginResultType.InvalidPassword:
                        return new UserFriendlyException(L("LoginFailed"), L("InvalidUserNameOrPassword"));
                    case AbpLoginResultType.InvalidTenancyName:
                        return new UserFriendlyException(L("LoginFailed"), L("ThereIsNoTenantDefinedWithName{0}", tenancyName));
                    case AbpLoginResultType.TenantIsNotActive:
                        return new UserFriendlyException(L("LoginFailed"), L("TenantIsNotActive", tenancyName));
                    case AbpLoginResultType.UserIsNotActive:
                        return new UserFriendlyException(L("LoginFailed"), L("UserIsNotActiveAndCanNotLogin", usernameOrEmailAddress));
                    case AbpLoginResultType.UserEmailIsNotConfirmed:
                        return new UserFriendlyException(L("LoginFailed"), "UserEmailIsNotConfirmedAndCanNotLogin");
                    case AbpLoginResultType.LockedOut:
                        return new UserFriendlyException(L("LoginFailed"), L("UserLockedOutMessage"));
                    default: //Can not fall to default actually. But other result types can be added in the future and we may forget to handle it
                        Logger.Warn("Unhandled login fail reason: " + result);
                        return new UserFriendlyException(L("LoginFailed"));
                }
            }
    
            public ActionResult Logout()
            {
                AuthenticationManager.SignOut();
                return RedirectToAction("Login");
            }
    
            #endregion

    由于abp涉及到租户和身份验证的问题,所以登录有点繁琐.分析发现主要包括以下几个步骤:

    1、 GetLoginResultAsync --> loginManager.LoginAsync --> userManager.CreateIdentityAsync:不要以为调用了LoginAsync就以为是登录,其实这是伪登录。主要根据用户名密码去核对用户信息,构造User对象返回,然后再根据User对象的身份信息去构造身份证(CliamsIdentity)。
    2、SignInAsync --> AuthenticationManager.SignOut
    -->AuthenticationManager.SignIn

    AuthenticationManager(认证管理员),负责真正的登入登出。SignIn的时候将第一步构造的身份证(CliamsIdentity)交给证件所有者(ClaimsPrincipal)。   
           登录完成之后,我们通常会有一个记住用户名密码的功能,有人就会想到abp中的AbpSession.单其实AbpSession不是单纯意义上的Session,比如AbpSession里面的Userid就是通过以下方式获得的.
    ((ClaimsPrincipal)Thread.CurrentPrincipal).Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);

           需要获取会话信息则必须实现IAbpSession接口。虽然你可以用自己的方式去实现它(IAbpSession),但是它在module-zero项目中已经有了完整的实现。IAbpSession包含还有其他信息.

     //
        // 摘要:
        //     Defines some session information that can be useful for applications.
        public interface IAbpSession
        {
            //
            // 摘要:
            //     TenantId of the impersonator. This is filled if a user with Abp.Runtime.Session.IAbpSession.ImpersonatorUserId
            //     performing actions behalf of the Abp.Runtime.Session.IAbpSession.UserId.
            int? ImpersonatorTenantId { get; }
            //
            // 摘要:
            //     UserId of the impersonator. This is filled if a user is performing actions behalf
            //     of the Abp.Runtime.Session.IAbpSession.UserId.
            long? ImpersonatorUserId { get; }
            //
            // 摘要:
            //     Gets current multi-tenancy side.
            MultiTenancySides MultiTenancySide { get; }
            //
            // 摘要:
            //     Gets current TenantId or null. This TenantId should be the TenantId of the Abp.Runtime.Session.IAbpSession.UserId.
            //     It can be null if given Abp.Runtime.Session.IAbpSession.UserId is a host user
            //     or no user logged in.
            int? TenantId { get; }
            //
            // 摘要:
            //     Gets current UserId or null. It can be null if no user logged in.
            long? UserId { get; }
    
            //
            // 摘要:
            //     Used to change Abp.Runtime.Session.IAbpSession.TenantId and Abp.Runtime.Session.IAbpSession.UserId
            //     for a limited scope.
            //
            // 参数:
            //   tenantId:
            //
            //   userId:
            IDisposable Use(int? tenantId, long? userId);

                 

    AbpSession定义的一些关键属性:

    1.UserId: 当前用户的标识ID,如果没有当前用户则为null.如果需要授权访问则它不可能为空。

    2.TenantId: 当前租户的标识ID,如果没有当前租户则为null。

    3.MultiTenancySide: 可能是Host或Tenant。

             UserId和TenantId是可以为null的。当然也提供了不为空时获取数据的 GetUserId()和GetTenantId() 方法 。当你确定有当前用户时,你可以使用GetUserId()方法。如果当前用户为空,使用该方法则会抛出一个异常。GetTenantId()的使用方式和GetUserId()类似。

                IAbpSession通常是以属性注入的方式存在于需要它的类中,不需要获取会话信息的类中则不需要它。如果我们使用属性注入方式,我们可以用 
    NullAbpSession.Instance作为默认值来初始化它(IAbpSession)

        public IAbpSession AbpSession { get; set; }
            private readonly IUserService _iUsersService;
            public AccountController(IUserService iUsersService)
            {
                _iUsersService = iUsersService;
                AbpSession = NullAbpSession.Instance;
            }
    
            // GET: Account
            public ActionResult Index()
            {
                var currentUserId = AbpSession.UserId;
                return View(); 
            }

              由于授权是应用层的任务,因此我们应该在应用层和应用层的上一层使用IAbpSession(我们不在领域层使用IAbpSession是很正常的)。

    ApplicationServiceAbpController 和 AbpApiController 这3个基类已经注入了AbpSession属性,因此在Application Service的实例方法中,能直接使用AbpSession属性。

               ABP框架中的AbpSession, 并没有使用到System.Web.HttpSessionStateBase, 而是自己定义了一个Abp.Runtime.Session.IAbpSession接口, 并在Zero模块中通过AspNet.Identity组件实现了AbpSession对象的存值、取值。 所以即使Web服务重启,也不会丢失Session状态。在我们自己的项目中, Session对象只有UserId、TenantId、MultiTenancySide这几个属性是不够用的,可以自己扩充了几个属性和方法,使用起来非常方便。

          首先我们定义IAbpSession扩展类获取扩展属性,通过扩展类,我们不需要做其他额外的更改,即可通过ApplicationService, AbpController 和 AbpApiController 这3个基类已经注入的AbpSession属性调用GetUserName()来获取扩展的Name属性。

     接口代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace JCmsErp.AbpSessionExtension
    {
       public interface IAbpSessionExtension
        {
            string UserName { get; }
        }
    }

    实现代码:

    using Abp.Configuration.Startup;
    using Abp.MultiTenancy;
    using Abp.Runtime;
    using Abp.Runtime.Session;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace JCmsErp.AbpSessionExtension
    {
        public class AbpSessionExtension : ClaimsAbpSession, IAbpSessionExtension
        {
            public AbpSessionExtension(IPrincipalAccessor principalAccessor, IMultiTenancyConfig multiTenancy, ITenantResolver tenantResolver, IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider)
                : base(principalAccessor, multiTenancy, tenantResolver, sessionOverrideScopeProvider)
            {
            }
    
            public string UserName => GetUserName(ClaimTypes.Name);
    
            private string GetUserName(string claimType)
            {
                var claimsPrincipal = PrincipalAccessor.Principal;
    
                var claim = claimsPrincipal?.Claims.FirstOrDefault(c => c.Type == claimType);
                if (string.IsNullOrEmpty(claim?.Value))
                    return null;
    
                return claim.Value;
            }
    
    
        }
    }

           然后在登录逻辑中加入以下代码:

      //添加身份信息,以便在AbpSession中使用
    
                identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));

    就这样,我们在ApplicationService, AbpController 和 AbpApiController任何地方注入IAbpSession,然后AbpSession.Name就能获取到我们登录时候添加的信息.

           二,abp的错误机制

              如果登录过程中出错怎么办,报错了ABP怎么反应,我们来看一下abp的错误机制.在web应用中,异常通常在MVC Controller actions和Web API Controller actions中处理。当异常发生时,应用程序的用户以某种方式被告知错误的相关信息及原因。果错误在正常的HTTP请求时发生,将会显示一个异常页。如果在AJAX请求中发生错误,服务器发送错误信息到客户端,然后客户端处理错误并显示给用户。在所有的Web请求中处理异常是件乏味且重复的工作。ABP自动化完成异常处理,几乎从不需要显示的处理任何异常。ABP处理所有的异常、记录异常并返回合适、格式化的响应到客户端。在客户端处理这些响应并将错误信息显示给用户。

         异常显示,首先我们在ActionResult 随便添加一个异常信息,调试一下看一下结果

       

       public ActionResult Index()
            {
    
               // return View();
                 throw new Exception("登录密码错误或用户不存在或用户被禁用。");
            }

             当然,这个异常可能由另一个方法抛出,而这个方法的调用在这个action里。ABP处理这个异常、记录它并显示'Error.cshtml'视图。你可以自定义这个视图来显示错误。一个示例错误视图(在ABP模板中的默认错误视图):

    BP对用户隐藏了异常的细节并显示了一个标准(本地化的)的错误信息,除非你显示的抛出一个UserFriendlyException,UserFriendlyException UserFriendlyException是一个特殊类型的异常,它直接显示给用户。参见下面的示例:

        // GET: Account
            public ActionResult Index()
            {
    
               // return View();
               throw new Abp.UI.UserFriendlyException("登录密码错误或用户不存在或用户被禁用。");
            }

    浏览器结果:

    所以,如果你想显示一个特定的错误信息给用户,那就抛出一个UserFriedlyException(或者一个继承自这个类的异常)。

          当然如果是ajax请求里面出错,message API处理JSON对象并显示错误信息给用户。前端应该有相应的错误处理.

      返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期

    您的资助是我最大的动力!
    金额随意,欢迎来赏!

    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的推荐按钮。
    如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的关注我

    如果,想给予我更多的鼓励,求打

    因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【安与生】!

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    Hibernate3.3 中常见错误
    Hibernate Tools for Eclipse插件的安装和使用
    写个换房(先卖后买)退个人所得税的攻略 (转)
    Unable to instantiate default tuplizer [org.hibernate.tuple.entity.PojoEntityTuplizer]
    用SQL删除重复记录的N种方法
    Spring中ref local与ref bean区别
    Nginx反向代理
    文件上传
    linux 进程
    pdo
  • 原文地址:https://www.cnblogs.com/anyushengcms/p/7261815.html
Copyright © 2011-2022 走看看