首页 > 代码库 > IAbpSession的实现与OWIN

IAbpSession的实现与OWIN

IAbpSession

在AbpController中,通过属性注入的方式注入IAbpSession对象。

IAbpSession的实例ClaimsAbpSession。

技术分享
    public class ClaimsAbpSession : IAbpSession, ISingletonDependency
    {
        public virtual long? UserId
        {
            get
            {
                var userIdClaim = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
                if (string.IsNullOrEmpty(userIdClaim?.Value))
                {
                    return null;
                }

                long userId;
                if (!long.TryParse(userIdClaim.Value, out userId))
                {
                    return null;
                }

                return userId;
            }
        }

        public virtual int? TenantId
        {
            get
            {
                if (!MultiTenancy.IsEnabled)
                {
                    return MultiTenancyConsts.DefaultTenantId;
                }

                var tenantIdClaim = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.TenantId);
                if (string.IsNullOrEmpty(tenantIdClaim?.Value))
                {
                    return null;
                }

                return Convert.ToInt32(tenantIdClaim.Value);
            }
        }
View Code

 PrincipalAccessor.Principal指的是ClaimsPrincipal类型的对象。该对象获得的方式如下:

 public virtual ClaimsPrincipal Principal => Thread.CurrentPrincipal as ClaimsPrincipal;

技术分享
public static IPrincipal CurrentPrincipal
{
    [System.Security.SecuritySafeCritical]  // auto-generated
    get
    {
        lock (CurrentThread)
        {
            IPrincipal principal = (IPrincipal)
                CallContext.Principal;
            if (principal == null)
            {
                principal = GetDomain().GetThreadPrincipal();
                CallContext.Principal = principal;
            }
            return principal;
        }
    }

    [System.Security.SecuritySafeCritical]  // auto-generated
    [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.ControlPrincipal)]
    set
    {
        CallContext.Principal = value;
    }
}
View Code

 Thread.CurrentPrincipal通过CallContext.Principal或GetDomain().GetThreadPrincipal(). 获得IPrincipal对象。

无论通过哪种方式,该IPrincipal对象,都是从请求上下文获得的。

因此:IAbpSession保存的值是从请求上下文获得的,应用程序必然在某个时刻,将值保存到请求上下文中。这一步是通过OWIN实现的。

 

与IAbpSession相关的OWIN

在MVC项目App_Start文件夹添加Startup文件。

技术分享
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/"),
                CookieSecure = CookieSecureOption.Never,
            });
        }
View Code

 这一步的作用是向请求管道注册了一个事件。该事件在请求处理的Authenticate阶段执行,执行的内容是获得请求上下文的cookie(string字符串),经过解码和解密,将键值对保存成多个Claim类型的对象,并将这些Claim对象集合保存到ClaimsIdentity类型的对象Claims属性中。

ClaimsIdentity类型中的Claims属性:public virtual IEnumerable<Claim> Claims { get; }

ClaimsIdentity类型的对象又被上下文所引用。

因此可理解:Thread.CurrentPrincipal as ClaimsPrincipal 即是指这个解密出来的ClaimsIdentity对象。

 

如何将用户信息保存到上下文,并加密到cookie

AuthenticationManager类型

 internal class AuthenticationManager : IAuthenticationManager

 为控制器添加如下属性:

技术分享
        private IAuthenticationManager AuthenticationManager
        {
            get
            {
                return HttpContext.GetOwinContext().Authentication;
            }
        }
View Code

 HttpContext.GetOwinContext().Authentication返回的即是AuthenticationManager类型的对象;

该类型有两个方法,负责登录和登出。

public void SignIn(AuthenticationProperties properties, params ClaimsIdentity[] identities)
public void SignOut(AuthenticationProperties properties, string[] authenticationTypes)

 SignIn和SignOut还有分别有一个重载,只是 AuthenticationProperties properties参数是new AuthenticationProperties();

在这里,只要为SignIn方法成功传入一个对象ClaimsIdentity即可能登录成功。但如何构建ClaimsIdentity对象。

微软的Identity实现:

Microsoft.AspNet.Identity.Core程序集下UserManager<TUser, TKey>实现如下方法。

public virtual Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)

TUser需实现IUser<out Tkey>接口,该接口包含Id和UserName两个属性。

    public interface IUser<out TKey>
    {
        TKey Id { get; }
        string UserName { get; set; }
    }

因此可以理解为:用户输入用户名和密码→应用程序向数据库查询该用户,返回一个实现IUser接口的对象→调用UserManager.CreateIdentityAsync方法,将IUser对象传入方法中→该方法返回一个ClaimsIdentity对象→将该对象传给AuthenticationManager.SignIn方法。

SignIn会写入到上下文并在某个“时间地点”加密、编码成cookie. 反过来讲,SignOut会清除上下文中的ClaimsIdentity对象。

自定义构建ClaimsIdentity对象:

仿照UserManager.CreateIdentityAsync内的代码,如下写法:

在这里,创建ClaimsIdentityFactory对象仅仅只为使用其内的常量字符串,并无其它意义,不能修改替换ClaimsIdentityFactory内的常量字符串,否则会出错。(new Claim时构造函数传入的factory的几个属性均是常量字符串)

IdentityProviderClaimType和DefaultIdentityProviderClaimValue 也均为ClaimsIdentityFactory内的internal修饰的常量字符串,因在本处引用不到,所以拿出来了。
user = new Entitys.User { Id = 123, TenantId = 1, Email = "1233@qq.com", UserName = "admin" };
var factory = new Microsoft.AspNet.Identity.ClaimsIdentityFactory<User, long>();
string IdentityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";
string DefaultIdentityProviderClaimValue = http://www.mamicode.com/"ASP.NET Identity";
ClaimsIdentity id = new ClaimsIdentity(DefaultAuthenticationTypes.ApplicationCookie, factory.UserNameClaimType, factory.RoleClaimType);
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.String));
id.AddClaim(new Claim(factory.UserNameClaimType, user.UserName, ClaimValueTypes.String));
id.AddClaim(new Claim(IdentityProviderClaimType, DefaultIdentityProviderClaimValue, ClaimValueTypes.String));
id.AddClaim(new Claim(AbpClaimTypes.TenantId, user.TenantId.ToString(CultureInfo.InvariantCulture)));

AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = rememberMe }, id);

 

如上所示:应用程序就可以实现登录了。登录后就可以访问[Authorize]特性控制的Action。如果调用SignOut方法,即登出,访问[Authorize]特性控制的Action将需要登录。

简要说明的是,在Abp工作单元中也实现了IAbpSession属性,从而得到TenantId的值,以致能在查询中自动添加TenantId条件过滤。

 

OWIN学习参考博文:

http://www.cnblogs.com/jesse2013/p/aspnet-identity-claims-based-authentication-and-owin.html

 

分享一个Asp.net 源码网站,同时感谢介绍给我的那位好人。

https://referencesource.microsoft.com/#q=Identity

需要参考的源码:

katana项目,包含Owin相关几个程序集源码。

https://katanaproject.codeplex.com/SourceControl/latest#README

Identity源码:

https://github.com/aspnet/Identity

 

IAbpSession的实现与OWIN