ASP.NET Core之身份认证Cookie&Session

一、前言

  在ASP.NET中介绍了验证和授权的内容,在ASP.NET Core中是如何实现验证和授权?如何通过Session和Cookie实现身份验证?基于这个问题,本章来详细介绍一些ASP.NET Core的使用Session和Cookies,在使用前先对两者做定义、对比、作用、关联关系做一个说明,从而理解Session和Cookies。

  1、从存放位置上:Cookie是保存在客户端浏览器上,通过F12查看网站的Cookie信息;Session是保存在服务器上。2、存放的形式上:Cookie是以字符串的形式保存,可以包含会话的ID等信息;Session以对象的形式保存在服务器上,包含用户的会话数据,存储在文本文件、站点内存、Redis、关系型数据库等。3、安全性:Cookies相对不安全,因为存储在客户端容易被获取和篡改;Session相对比较安全,因为存储在服务器,不容易被获取或篡改。4、用途:Cookie适合保存用户个人设置,偏好等不敏感信息,以及会话ID等信息;Session适合做用户的身份验证,会话管理等安全高的场景;5、对服务器的影响:Cookie对服务器影响小,因为存储在客户端;Session当访问量增大时,会占用更多的服务器资源,因为每个会话都需要在服务器上维护;5、两者联系:Session需要借助Cookie才能正常工作。当用户首次访问网站时,服务器会创建一个新的Session,并生成一个唯一的Session ID。这个Session ID随后会被发送到客户端,并保存在Cookie中。在后续的请求中,客户端会自动将这个包含Session ID的Cookie发送给服务器,从而使服务器能够识别并处理特定的用户会话。

  通过上述的描述对Session和Cookie基本清晰了,接下来则是在ASP.NET Core使用Session完成基本用户账号密码登录,然后验证身份进行页面的访问。

二、实践

  使用ASP.NET Core MVC创建一个Web应用程序,使用.NET 8.0版本。然后使用ASP.NET Core的自带的登录验证功能实现系统的用户名密码登录功能,该方法会将用户的身份信息保存在认证Cookie中,以便后续客户端的请求可以验证用户的身份,而在服务端则是保存在Session中提供当前会话的用户信息。主要的方法包括HttpContext.SignInAsync()、HttpContext.SignOutAsync(),SignInAsync方法是用户登录并且选择验证架构、用户Claims信息;SignOutAsync是用户退出登录。

  1、在Program中要完成的操作包括注册服务、服务的Options信息;启用中间件比如Session、授权、认证;通过ASP.NET Core自带的依赖注入方式注入业务服务等。而且Session的存储方式可以使用应用程序内存、文本文件、Redis缓存、关系数据库,所以分别使用不同的方式来试验存储用户未登录或者登录后的会话信息、用户信息。代码如下:

复制代码
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.BearerToken;
using Microsoft.AspNetCore.Authentication.Cookies;
using tqf.LoginSessionCookie.demo.Filters;
using tqf.LoginSessionCookie.demo.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Caching.SqlServer;
using Microsoft.AspNetCore.Routing.Patterns;
using tqf.LoginSessionCookie.demo.options;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
/*
builder.Services.AddControllers(configure => {
    configure.Filters.Add(typeof(UserAuthorizeAttribute));
});
*/
builder.Services.AddControllersWithViews(configure => {
    configure.Filters.Add(typeof(UserAuthorizeAttribute));
});
builder.Services.AddMvc(configure => {
    configure.Filters.Add(typeof(UserAuthorizeAttribute));
});

// 从 appsettings.json 中获取 Redis 连接配置
var redisConnectionString = builder.Configuration.GetSection("RedisCacheSettings:ConnectionString").Value;

// 内存缓存服务
// builder.Services.AddDistributedMemoryCache();

// 添加Redis缓存服务
/*
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = redisConnectionString;
    options.InstanceName = builder.Configuration.GetSection("RedisCacheSettings:InstanceName").Value;
});
*/
builder.Services.AddScoped<IUserService,UserService>();

// 使用sqlServer的缓存服务
builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = "Server=localhost;Database=MyCache;uid=sa;Password=123456;Persist Security Info=True;Encrypt=True;TrustServerCertificate=True;";
    options.SchemaName = "dbo";
    options.TableName = "global_cache";
    options.SystemClock = new LocalSystemClock();
    options.DefaultSlidingExpiration = TimeSpan.FromMinutes(1);
    options.ExpiredItemsDeletionInterval = TimeSpan.FromMinutes(10);
});

// Session服务
builder.Services.AddSession(s => {
    s.Cookie.Name = ".Market.Session";
    s.Cookie.HttpOnly = true;//防止xss攻击
    s.IdleTimeout = TimeSpan.FromMinutes(1);
    s.IOTimeout = TimeSpan.FromMinutes(1);
});

// 定义认证方式(Scheme 架构)
builder.Services.AddAuthentication(x =>
{
    x.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(option =>
{
    //关于Cookie的配置信息请参考:https://www.cnblogs.com/sheldon-lou/p/9545726.html
    //option.Cookie.Domain = ".contoso.com";//设置Cookie的作用域:他的作用域就包括contoso.com,www.contoso.com
    option.LoginPath = "/Account/LoginIn"; //在身份验证的时候判断为“未登录”则跳转到这个页面
    option.LogoutPath = "/Account/LoginIn";//如果要退出登录则跳转到这个页面
    //option.AccessDeniedPath = "/Account/AccessDenied"; //如果已经通过身份验证,但是没有权限访问则跳转到这个页面
    option.Cookie.HttpOnly = true;//设置 cookie 是否是只能被服务器访问,默认 true,为true时通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性
    option.SlidingExpiration = true;//设置Cookie过期时间为相对时间;也就是说在Cookie设定过期的这个时间内用户没有访问服务器,那么cookie就会过期,若有访问服务器,那么cookie期限将从新设为这个时间
    option.ExpireTimeSpan = TimeSpan.FromDays(1); //设置Cookie过期时间为1天
    option.ClaimsIssuer = "Cookie";//获取或设置应用于创建的任何声明的颁发者
    //option.Cookie.Path = "/app1"; //用来隔离同一个服务器下面的不同站点。比如站点是运行在/app1下面,设置这个属性为/app1,那么这个 cookie 就只在 app1下有效。
});

var app = builder.Build();
app.UseSession();
app.UseRouting();

// Configure the HTTP request pipeline.
app.UseAuthentication();//认证
app.UseAuthorization();// 授权

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}");

app.Run();
复制代码

  上述使用了内存缓存、Redis缓存、SqlServer缓存都是.Net Core 中的分布式缓存统一接口是 IDistributedCache 该接口定义了一些对缓存常用的操作。然后Session信息则是存储在上述的缓存中,默认使用内存缓存保存Session数据,并且在中间件中要启用app.UseSession()、app.UseAuthentication()。

  2、在用户登录则是AccountController中定义用户的登录界面、登录账号密码校验的逻辑,登录保存Session和Cookie信息,代码如下:

复制代码
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using tqf.LoginSessionCookie.demo.Models;
using tqf.LoginSessionCookie.demo.Extensions;
using tqf.LoginSessionCookie.demo.Services;

namespace tqf.LoginSessionCookie.demo.Controllers
{
    public class AccountController : Controller
    {
        private IUserService _userService;
        public AccountController(IUserService userService) {
            _userService = userService;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public IActionResult LoginIn()
        {
            return View("LoginIn");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<ActionResult> Login(string userName, string password)
        {
            LoginUserInfo loginUserInfo = new LoginUserInfo();
            loginUserInfo.UserName = userName;
            loginUserInfo.Password = password;
            loginUserInfo.Id = 1;
            bool status =_userService.CheckUserInfo(loginUserInfo);
            if (!status) {
                throw new Exception("账号密码错误!");
            }
            LoginUserInfo userInfo = _userService.GetUserInfo(userName);
            HttpContext.Session.Set("Market_User", userInfo);
            //设置cookie
            var identity = new ClaimsIdentity("Forms");     // 指定身份认证类型
            identity.AddClaim(new Claim(ClaimTypes.Sid, userInfo.Id.ToString()));  // 用户Id
            identity.AddClaim(new Claim(ClaimTypes.Name, userInfo.UserName));      // 用户名称
            identity.AddClaim(new Claim(ClaimTypes.Email, userInfo.Email));
            //创建身份证这个证件的携带者:我们叫这个证件携带者为“证件当事人”
            var principal = new ClaimsPrincipal(identity);
            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { IsPersistent = true });
            return Ok(loginUserInfo);
        }

        [HttpGet]
        public ActionResult Out()
        {
            HttpContext.SignOutAsync();
            return Ok();
        }
    }
}
复制代码

  其中IUserService定义用户相关服务如验证用户名密码是否正确、获取用户的详情数据。在Login方法中制定身份认证方式Forms,定义用户的Claims信息,然后创建用户身份信息并且传入Cookie中,保存Session完成上述操作,登录成功返回用户信息。

复制代码
using tqf.LoginSessionCookie.demo.Models;

namespace tqf.LoginSessionCookie.demo.Services
{
    /// <summary>
    /// 
    /// </summary>
    public interface IUserService
    {
        public Boolean CheckUserInfo(LoginUserInfo loginUserInfo);
        public LoginUserInfo GetUserInfo(string userName);
    }
}


using Microsoft.AspNetCore.Identity;
using tqf.LoginSessionCookie.demo.Models;

namespace tqf.LoginSessionCookie.demo.Services
{
    public class UserService : IUserService
    {
        private List<LoginUserInfo> _users = new List<LoginUserInfo>();
        public UserService() {
            _users.Add(new LoginUserInfo() { UserName ="tuqunfu",PhoneNumber="18720077062",Password="123456",Id=1,Email="502599047@qq.com"});
        }

        public bool CheckUserInfo(LoginUserInfo loginUserInfo)
        {
            return _users.FirstOrDefault(x => x.UserName.Equals(loginUserInfo.UserName) && x.Password.Equals(loginUserInfo.Password))!=null?true:false;
        }
        
        public LoginUserInfo GetUserInfo(string userName)
        {
            return _users.FirstOrDefault(x=>x.UserName.Equals(userName));
        }
    }
}
复制代码

  3、在业务控制器如HomeController则通过获取Session返回用户信息,如果返回null则表示Session过期要求用户重新登录系统,保证对系统验证用户身份则使用特性方式进行验证身份,代码如下:

复制代码
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using tqf.LoginSessionCookie.demo.Extensions;
using tqf.LoginSessionCookie.demo.Filters;
using tqf.LoginSessionCookie.demo.Models;

namespace tqf.LoginSessionCookie.demo.Controllers
{
    [UserAuthorize]
    public class HomeController : Controller
    {
        [HttpGet]
        public IActionResult Index() {
            LoginUserInfo loginUserInfo = HttpContext.Session.Get<LoginUserInfo>("Market_User");
            if (loginUserInfo == null)
            {
                var userName = User?.Identity?.Name;
                //根据用户名获取对象
                loginUserInfo = new LoginUserInfo();
                //重新设置session
                HttpContext.Session.Set("Market_User", loginUserInfo);
            }
            return Ok(loginUserInfo);
        }
    }
}
复制代码
复制代码
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication;

namespace tqf.LoginSessionCookie.demo.Filters
{
    /// <summary>
    /// 
    /// </summary>
    public class UserAuthorizeAttribute : AuthorizeAttribute, IFilterMetadata
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="filterContext"></param>
        public virtual void OnAuthorization(AuthorizationFilterContext filterContext)
        {
            //获取对应Scheme方案的登录用户呢?使用HttpContext.AuthenticateAsync
            var authenticate = filterContext.HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            if (authenticate.Result.Succeeded)
            {
                return;
            }
            //
            string path = filterContext.HttpContext.Request.Path;
            //创建一个路由
            var route = new RouteValueDictionary{{ "Controller", "Account" },
                        {"Action", "LoginIn"}};
            //跳转到指定路由
            filterContext.Result = new RedirectToRouteResult(route);
            return;
        }
    }
}
复制代码

4、在使用过程中,各个缓存服务配置完成了,可能连接或者参数信息导致服务失败,其日志信息会在控制台输出相关错误信息,在使用sqlserver数据库缓存注意时区问题,默认使用美国时区,通过定义本地时区保持时区的一致性。

namespace tqf.LoginSessionCookie.demo.options
{
    public class LocalSystemClock : Microsoft.Extensions.Internal.ISystemClock
    {
        public DateTimeOffset UtcNow => DateTime.Now;
    }
}

三、总结

  通过Redis学习,衍生对ASP.NET Core的身份认证的内容,Session的多种方式存储,但是Redis和SqlServer是可以分布式存储使用的。上述只是一个简单登录验证的Demo,实际生产中必须考虑其它因素。

参考:https://blog.csdn.net/x1234w4321/article/details/142481836

posted @   tuqunfu  阅读(392)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
点击右上角即可分享
微信分享提示