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权限管理系统一期

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

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

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

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

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    cinder支持nfs快照
    浏览器输入URL到返回页面的全过程
    按需制作最小的本地yum源
    创建可执行bin安装文件
    RPCVersionCapError: Requested message version, 4.17 is incompatible. It needs to be equal in major version and less than or equal in minor version as the specified version cap 4.11.
    惠普IPMI登陆不上
    Linux进程状态——top,ps中看到进程状态D,S,Z的含义
    openstack-neutron基本的网络类型以及分析
    openstack octavia的实现与分析(二)原理,架构与基本流程
    flask上下文流程图
  • 原文地址:https://www.cnblogs.com/anyushengcms/p/7261815.html
Copyright © 2011-2022 走看看