asp.net core的cookie认证

以前都是重复造轮子,最近研究asp.net core的认证和授权,用起来还是很方便的。

配置类:

using Auth.Core.ComponentExtensions;
using Auth.Core.Configuration;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.DependencyInjection;
namespace Auth.Core.ServiceExtensions
{
    public static class CookieAuthenticationService
    {
        /// <summary>
        /// 注册常用的工具帮助类
        /// </summary>
        /// <param name="services"></param>
        public static void AddCookieAuthenticationService(this IServiceCollection services)
        {
            GlobalConfig globalConfig = GlobalVars.GlobalConfig;
            //身份认证
            services.AddAuthentication(options =>
            {
                //只配置了DefaultScheme,这样,DefaultSignInScheme, DefaultSignOutScheme, DefaultChallengeScheme, DefaultForbidScheme 等都会使用该 Scheme 作为默认值
                //注意新版本必须设置,否则会报错
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
            .AddCookie(options =>
            {
                options.Cookie.Name = globalConfig.AuthConfig.CookieName;
                //设置了HttpOnly避免XSS攻击。
                options.Cookie.HttpOnly = true;
                options.Cookie.Domain = globalConfig.AuthConfig.CookieDomain;
                options.ExpireTimeSpan = System.TimeSpan.FromSeconds(globalConfig.AuthConfig.ExpireTime); //authenticationPropertie中的设置时间优先
                //options.ReturnUrlParameter = "abc";
                //options.LoginPath = new PathString("/Account/Login");
                //options.AccessDeniedPath = new PathString("/Account/Denied");
                //options.LogoutPath = new PathString("/Account/Logout");
                options.Cookie.Path = "/";
                options.TicketDataFormat = new TicketDataFormat(new AesDataProtector(globalConfig.AuthConfig.SecurityKey));
                options.SlidingExpiration = true; //是否平滑过期
                options.Events = new CookieAuthenticationEvents
                {
                    ///自定义验证事件
                    //OnValidatePrincipal = CustomCookieAuthenticationEvents.OnValidateAsync,
                    //未登录跳转事件
                    OnRedirectToLogin = CustomCookieAuthenticationEvents.OnRedirectToLogin,
                    //无权限跳转事件
                    OnRedirectToAccessDenied = CustomCookieAuthenticationEvents.OnRedirectToAccessDenied,
                    ////签发前的事件
                   OnSigningIn = CustomCookieAuthenticationEvents.OnSigningIn,
                };

                // 在这里可以根据需要添加一些Cookie认证相关的配置,在本次示例中使用默认值就可以了。
            });

        }

    }

}

这里重定义了加密方式:options.TicketDataFormat = new TicketDataFormat(new AesDataProtector(globalConfig.AuthConfig.SecurityKey)); 默认的加密方式太长了。

加密类代码如下:

using HuaTuo.Utils;
using Microsoft.AspNetCore.DataProtection;

namespace Auth.Core.ServiceExtensions
{
    internal class AesDataProtector : IDataProtector
    {
        private string _key;
        public AesDataProtector(string key)
        {
            this._key = key;
        }
        public IDataProtector CreateProtector(string purpose)
        {
            return this;
        }
        /// <summary>
        /// 只对Claim中的CustomSerializationData属性行了保护,并非整个ClaimsPrincipal对象
        /// </summary>
        /// <param name="plaintext"></param>
        /// <returns></returns>
        public byte[] Protect(byte[] plaintext)
        {
            string text=System.Text.Encoding.UTF8.GetString(plaintext);
            string enText ="";
            try
            {
                enText = AESHelper.Encrypt(text, this._key);
            }
            catch
            {
            }
            return System.Text.Encoding.UTF8.GetBytes(enText);
        }

        public byte[] Unprotect(byte[] protectedData)
        {
            string text = System.Text.Encoding.UTF8.GetString(protectedData);
            string deText ="";
            try
            {
                deText = AESHelper.Decrypt(text, this._key);
            }
            catch
            {
            }
            return System.Text.Encoding.UTF8.GetBytes(deText);
        }
    }
}

这里采用了aes对称加密,AESHelper为工具类提供的帮助类,这里就不提供了。

下面是自定义时间类,默认的没有认证通过会进行页面跳转,对于前后端分离项目是很不友好的,所以重定义对自带的几个event进行了自定义。

                options.Events = new CookieAuthenticationEvents
                {
                    ///自定义验证事件
                    //OnValidatePrincipal = CustomCookieAuthenticationEvents.OnValidateAsync,
                    //未登录跳转事件
                    OnRedirectToLogin = CustomCookieAuthenticationEvents.OnRedirectToLogin,
                    //无权限跳转事件
                    OnRedirectToAccessDenied = CustomCookieAuthenticationEvents.OnRedirectToAccessDenied,
                    ////签发前的事件
                   OnSigningIn = CustomCookieAuthenticationEvents.OnSigningIn,
                };

自定义事件类代码如下:

using HuaTuo.Utils;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using System;
using System.Text;
using System.Threading.Tasks;

namespace Auth.Core.ComponentExtensions
{
    //cookie认证的aop事件
    public class CustomCookieAuthenticationEvents
    {
        /// <summary>
        /// 认证通过后才会进入此任务
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public static  Task OnValidateAsync(CookieValidatePrincipalContext context)
        {
            //TipsInfo tipsInfo = new TipsInfo()
            //{
            //    Status = 0,
            //    Code = StatusCodes.Status401Unauthorized,
            //    Msg = "对不起,你没有权限访问!"
            //};
            //context.Response.ContentType = "application/json";
            //return context.Response.WriteAsync(JsonHelper.JsonParse(tipsInfo), Encoding.UTF8);
            //var userPrincipal = context.Principal;
            //if (userPrincipal?.Identity?.IsAuthenticated??false)
            //{
            //    context.ShouldRenew = true;
            //}
            //else
            //{
            //    context.RejectPrincipal();
            //    //退出,清理cookie
            //    context.HttpContext.SignOutAsync();
            //}
            ////主动删除需要和数据库或redis配合,如用户修改了密码,redis或数据库中插入uid和userPrincipal中的claim进行对比,如果存在则context.RejectPrincipal;
             return Task.CompletedTask;
        }

        ///自定义跳转事件
        public static Task OnRedirectToLogin(RedirectContext<CookieAuthenticationOptions> context)
        {
            //context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            TipsInfo tipsInfo = new TipsInfo()
            {
                Status = 0,
                Code = StatusCodes.Status401Unauthorized,
                Msg = "对不起,未登录或登录超时!"
            };
            context.Response.ContentType = "application/json";
            return context.Response.WriteAsync(JsonHelper.JsonParse(tipsInfo), Encoding.UTF8);
        }

        /// <summary>
        /// 签发令牌前的aop,注意:签发到前端的cookie是对密文进行了base64加密
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        internal static Task OnSigningIn(CookieSigningInContext context)
        {
            AuthenticationProperties authenticationProperties = new AuthenticationProperties();
            context.Properties.AllowRefresh =false;//自动刷新cookie
            context.Properties.IssuedUtc = DateTime.UtcNow;//签发时间
            context.Properties.ExpiresUtc = DateTime.UtcNow.Add(context.Options.ExpireTimeSpan);
            context.Properties.IsPersistent = true; //必须设置,否则ExpiresUtc无效
            return Task.CompletedTask;
        }

        internal static Task OnRedirectToAccessDenied(RedirectContext<CookieAuthenticationOptions> context)
        {
            var user = context.HttpContext.User.Identity;
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            TipsInfo tipsInfo = new TipsInfo()
            {
                Status = 0,
                Code = StatusCodes.Status403Forbidden,
                Msg = "对不起,您没有权限访问此接口!"
            };
            context.Response.ContentType = "application/json";
            return context.Response.WriteAsync(JsonHelper.JsonParse(tipsInfo), Encoding.UTF8);
        }
    }
}

前端登录接口的控制器代码如下:

        [HttpPost]
        public  Task<TipsInfo> GetToken(LoginDto loginDto)
        {
            //通过services层检测用户名密码省略....

            AuthConfig authConfig = GlobalVars.GlobalConfig.AuthConfig;
            TipsInfo tipsInfo = new TipsInfo();

                //cookie令牌,签发到前端的cookie对密文又进行了base64加密
                var claimIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
                claimIdentity.AddClaim(new Claim("Uid", "1"));
                claimIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
                claimIdentity.AddClaim(new Claim(ClaimTypes.Name, "guizhoumen"));
                claimIdentity.AddClaim(new Claim(ClaimTypes.Email, "email"));
                claimIdentity.AddClaim(new Claim(ClaimTypes.MobilePhone, "13318250159"));
                var claimsPrincipal = new ClaimsPrincipal(claimIdentity);
                // 在OnSigningIn事件中配置后,下面的可以注释掉。
                //AuthenticationProperties authenticationProperties = new AuthenticationProperties();
                //authenticationProperties.AllowRefresh = true;//自动刷新cookie
                //authenticationProperties.IssuedUtc = DateTime.UtcNow;//签发时间
                //authenticationProperties.ExpiresUtc = DateTime.UtcNow.AddHours(2);
                //authenticationProperties.IsPersistent = true;
 //HttpContext.SignInAsync(claimsPrincipal, authenticationProperties);
HttpContext.SignInAsync(claimsPrincipal); return Task.FromResult(tipsInfo); }

这里需要注意,如果需要设置过期时间,必须定义AuthenticationProperties,而且IsPersistent必须设置为true,AllowRefresh表示自动刷新过期时间。

这里注释掉,是因为我把这段提到了OnSigningIn时间中去定义了。

posted @ 2021-11-02 16:04  鹅是码农  阅读(331)  评论(0编辑  收藏  举报