session传值方式(教你用session传递数据)

session传值方式(教你用session传递数据)

大概微软也知道在 .NET 库中集成太多验证方案太笨重,所以现在新版本的 ASP.NET Core 的默认库中只保留一些基本的验证方案——如 Cookie,这个方案是内置的,我们不需要自己写代码(在 Microsoft.AspNetCore.Authentication.Cookies 命名空间中)。在 Microsoft.AspNetCore.Authentication 命名空间下有个抽象类 AuthenticationHandler,它实现了一点基本功能,我们如果想自己写验证方案,可以从这个类派生。但,老周这次要用的方案只是对 Session 的简单检查,所以,就不需要从这个抽象类派生,而是直接实现 IAuthenticationHandler 接口。在实现验证逻辑前,咱们写个类,作为一些可设置参数的选项。publicclassTestAuthenticationOptions{///<summary>///登录入口路径///</summary>publicstringLoginPath{get;set;}=”/Home/Login”;///<summary>///存入Session的键名///</summary>publicstringSessionKeyName{get;set;}=”uid”;///<summary>///返回URL参数名///</summary>publicstringReturnUrlKey{set;get;}=”return”;}

这里老周只按照项目需求设定了三个选项,想添加选项的话得看你的实际需求了。

LoginPath:登录入口,这个属性指定一个URL(一般是相对URL),表示用户输入名称和密码登录的页面(可以是MVC,可以是 RazorPages,这个无所谓,由URL路由和你的代码决定)。

SessionKeyName:这个属性设置 Session 里面存放登录标识时的 Key 名。其实 Session 和字典对象类似,里面每个项都有唯一的 Key。

ReturnUrlKey:指定一个字段名,这个字段名一般附加在URL的参数中,表示要跳转回去的路径。比如,设置为“return”,那么,假如我们要访问 https://localhost/admin/op,但这个路径(或页面)必须要验证,否则不能访问(其实包含授权过程),于是会自动跳转到 https://localhost/Home/login,让用户登录。但用户登录成功后要返回 /admin/op,所以,在 Login 后加个参数:

https://localhost/Home/Login?return=/admin/op

当登录并验证成功后,根据这个 return 查询字段跳转回去。如果你把 ReturnUrlKey 属性设置为“back”,那么登录的URL就是:

https://localhost/Home/Login?back=/admin/op

在实现 IAuthenticationHandler 接口时,可以同时实现 IAuthenticationSignInHandler 接口。而 IAuthenticationSignInHandler 接口是包含 IAuthenticationHandler 和 IAuthenticationSignOutHandler 接口的。这就等于,你只实现 IAuthenticationSignInHandler 接口就行,它包含三个接口的方法成员。

InitializeAsync 方法:初始化时用,一般可以从这里获取当前请求关联的 HttpContext ,以及正在被使用的验证方案信息。

AuthenticateAsync 方法:验证过程,此处老周的做法仅仅看看 Session 中有没有需要的Key就行了。

ChallengeAsync 方法:一旦验证失败,就会调用这个方法,向客户端索要验证信息。这里需要的验证信息是输入用户名和密码。所以,老周在些方法中 Redirect 到登录页面。

ForbidAsync 方法:禁止访问时用,可以直接调用 HttpContext 的 ForbidAsync 方法。

SignInAsync 方法:登入时调用,这里老周只是把用户名放入 Session 就完事了。

SignOutAsync 方法:注销时调用,这里只是把 Session 中的用户名删除即可。

这些方法都可以由 ASP.NET Core 内部自动调用,也可以通过 HttpContext 的扩展方法手动触发,如SignInAsync、AuthenticateAsync、ChallengeAsync等。

publicclassTestAuthenticationHandler:IAuthenticationSignInHandler{///<summary>///验证方案的名称,可以自行按需取名///</summary>publicconststringTEST_SCHEM_NAME=”some_authen”;///<summary>///依赖注入获取的选项///</summary>publicTestAuthenticationOptionsOptions{get;privateset;}publicTestAuthenticationHandler(IOptions<TestAuthenticationOptions>opt){Options=opt.Value;}publicHttpContextHttpContext{get;privateset;}publicAuthenticationSchemeScheme{get;privateset;}publicTask<AuthenticateResult>AuthenticateAsync(){//先要看看验证方案是否与当前方案匹配if(Scheme.Name!=TEST_SCHEM_NAME){returnTask.FromResult(AuthenticateResult.Fail(“验证方案不匹配”));}//再看Sessionif(!HttpContext.Session.Keys.Contains(Options.SessionKeyName)){returnTask.FromResult(AuthenticateResult.Fail(“会话无效”));}//验证通过stringun=HttpContext.Session.GetString(Options.SessionKeyName)??string.Empty;ClaimsIdentityid=new(TEST_SCHEM_NAME);id.AddClaim(new(ClaimTypes.Name,un));ClaimsPrincipalprcp=new(id);AuthenticationTicketticket=new(prcp,TEST_SCHEM_NAME);returnTask.FromResult(AuthenticateResult.Success(ticket));}publicTaskChallengeAsync(AuthenticationProperties?properties){//跳转到登录入口HttpContext.Response.Redirect($”{Options.LoginPath}?{Options.ReturnUrlKey}={HttpContext.Request.Path}”);returnTask.CompletedTask;}publicasyncTaskForbidAsync(AuthenticationProperties?properties){awaitHttpContext.ForbidAsync(Scheme.Name);}publicTaskInitializeAsync(AuthenticationSchemescheme,HttpContextcontext){//获取一些必备对象的引用HttpContext=context;Scheme=scheme;returnTask.CompletedTask;}publicTaskSignInAsync(ClaimsPrincipaluser,AuthenticationProperties?properties){//获取用户名stringuname=user.Identity?.Name??string.Empty;if(!string.IsNullOrEmpty(uname)){HttpContext.Session.SetString(Options.SessionKeyName,uname);}returnTask.CompletedTask;}publicTaskSignOutAsync(AuthenticationProperties?properties){if(HttpContext.Session.Keys.Contains(Options.SessionKeyName)){HttpContext.Session.Remove(Options.SessionKeyName);}returnTask.CompletedTask;}}

在 AuthenticateAsync 方法中,先要检查一下,当前所使用用的验证方案是否与 TEST_SCHEM_NAME 所表示的方案名称相同。这是为了防止把 TestAuthenticationHandler 与错误的验证方案进行注册绑定。例如我这个是实现用Session来验证的,要是把它与“Email-Auth”方案绑定,就会出现逻辑错误,毕竟此类不是用电子邮件来验证的。

不管是实现验证方法AuthenticateAsync 还是登录方法SignInAsync,都不要去检查用户名和密码,而应该把用户名和密码验证放到登录的页面或 Controller 中处理。因为这个自定义的 TestAuthenticationHandler 在许多需要验证的请求中都要调用,如果你在这里去检查用户名和密码,岂不是每次都要跳转到登录页让用户去输入?

B、授权

一旦验证完成,就到了授权过程。

验证过程通过验证方案名称来标识,同样,授权过程也可包含多个策略。

比如,可以基于用户的角色进行授权,管理员的权限多一些,非管理员的少一些;

可以基于用户的年龄进行授权,哪些游戏 15 岁以下的不能玩;

或者,基于用户的信用分来授权,信用差的不能贷款;信用好的允许你贷款

……

授权过程处理是通过收集一系列的声明(Claim)来评估一下用户具有哪些权限。比如

你是管理员吗?

你几岁了?

你过去三年的信用值是多少?

你是不是VIP用户?

你的购物积分多少?

你过去一年在我店买过几次东西?

……

这些【要求】就可以用 IAuthorizationRequirement 接口来表示。好玩的是,这个接口没有规定任何方法成员,你只需要有个类来实现这个接口就行。比如用户积分,写个类叫 UserPoints,实现这个接口,再加个属性叫 PointValue,表示积分数。

然后,你把这个 UserPoints 类添加到某授权策略的 Requirements 集合中,在处理授权评估时,再通过代码检查一下里面的各种实现了 IAuthorizationRequirement 接口的对象,看看符不符合条件。

而自定义的授权策略处理是实现 IAuthorizationHandler 接口。你看看,是不是原理差不多,刚才验证的时候会实现自定义的 Handler,现在授权时又可以实现 Handler。

在 Session 验证这个方案中,我们不需要写自定义的授权 Handler,只需要调用现有API开启授权功能,并注册一个有效的策略名称即可。而 IAuthorizationRequirement 我们也不用实现,直接用扩展方法 RequireAuthenticatedUser 就行。意思是说只要有已登录的用户名就行,毕竟咱们前面在验证时,已经提供了一个有效的用户登录名,还记得 AuthenticateAsync 方法中的这几行吗?

//验证通过stringun=HttpContext.Session.GetString(Options.SessionKeyName)??string.Empty;ClaimsIdentityid=new(TEST_SCHEM_NAME);id.AddClaim(new(ClaimTypes.Name,un));ClaimsPrincipalprcp=new(id);AuthenticationTicketticket=new(prcp,TEST_SCHEM_NAME);returnTask.FromResult(AuthenticateResult.Success(ticket));

其实我们已经添加了一个声明——Name,以用户名为标识,在授权策略中,程序要查找的就是这个声明。只要找到,就能授权;否则拒绝访问。

在 Program.cs 文件中,我们要注册这些服务类。

varbuilder=WebApplication.CreateBuilder(args);//启用Session功能builder.Services.AddSession(o=>{//把时间缩短一些,好测试o.IdleTimeout=TimeSpan.FromSeconds(5);});//这个用来检查用户名和密码是否正确builder.Services.AddSingleton<UserChecker>();//使用MVC功能builder.Services.AddControllersWithViews();//注册刚刚定义的选项类,可以依赖注入//不要忘了,不然出大事builder.Services.AddOptions<TestAuthenticationOptions>();//添加验证功能builder.Services.AddAuthentication(opt=>{//添加我们自定义的验证方案名opt.AddScheme<TestAuthenticationHandler>(TestAuthenticationHandler.TEST_SCHEM_NAME,null);});//添加授权功能builder.Services.AddAuthorization(opt=>{//注册授权策略,名为“demo2”opt.AddPolicy(“demo2″,c=>{//与我们前面定义的验证方案绑定//授权过程跟随该验证后发生c.AddAuthenticationSchemes(TestAuthenticationHandler.TEST_SCHEM_NAME);//要求存在已登录用户的标识c.RequireAuthenticatedUser();});});varapp=builder.Build();

把Session中的过期进间设为5秒,是为了好测试。

上面代码还注册了一个单实例模式的 UserChecker,这只是个测试,老周不使用数据库了,就用一个写“死”了的类来检查用户名和密码是否正确。

publicclassUserChecker{privateclassUserInfo{publicstringName{get;init;}publicstringPassword{get;init;}}//简单粗暴的用户信息,只为测试而生staticreadonlyIEnumerable<UserInfo>_Users=newUserInfo[]{new(){Name=”lucy”,Password=”123456″},new(){Name=”tom”,Password=”abcd”},new(){Name=”jim”,Password=”xyz321″}};///<summary>///验证用户名和密码是否有效///</summary>///<paramname=”name”>用户名</param>///<paramname=”pwd”>用户密码</param>///<returns></returns>publicboolCheckLogin(stringname,stringpwd)=>_Users.Any(u=>u.Name==name.ToLower()&&u.Password==pwd);}

在 App 对象 build 了之后,记得插入这些中间件到HTTP管道。

app.UseSession();app.UseAuthentication();app.UseAuthorization();app.MapControllerRoute(“main”,”{controller=Home}/{action=Index}”);

注意顺序,授权在验证之后,验证和授权要在 Map MVC的处理之前。

测试项目中我用到了两个 Controller。第一个是 Home,可以随便访问,故不需要考虑验证和授权的问题;第二个是 Admin,只有已正确登录的用户才可以访问。

Admin 控制器很简单,只返回对应的视图。

[Authorize(“demo2”)]publicclassAdminController:Controller{publicIActionResultMainLoad(){returnView();}}

注意在此控制器上应用了 Authorize 特性,并且指定了使用的授权策略是“demo2”。表明这个控制器里面的所有 Action 都不能匿名访问,要访问得先登录。

MainLoad 视图如下:

<h2>这是管理后台</h2>

Home 控制器允许匿名访问,其中包含了用户登录入口 Login。

publicclassHomeController:Controller{TestAuthenticationOptions_options;publicHomeController(IOptions<TestAuthenticationOptions>o){_options=o.Value;}publicIActionResultIndex()=>View();publicIActionResultLogin(){//获取返回的URLif(!HttpContext.Request.Query.TryGetValue(_options.ReturnUrlKey,outvarurl)){url=string.Empty;}//用模型来传递URLreturnView((object)url.ToString());}publicasyncTask<IActionResult>PostLogin(stringname,//用户名stringpwd,//密码string_url,//要跳回的URL[FromServices]UserCheckerusrchecker//用来验证用户名和密码){if(string.IsNullOrEmpty(name)||string.IsNullOrEmpty(pwd)){returnView(“Login”,_url);}//如果密码不正确if(!usrchecker.CheckLogin(name,pwd))returnView(“Login”,_url);//准备登入用的材料//1、声明Claimcname=new(ClaimTypes.Name,name);//2、标识ClaimsIdentityid=new(TestAuthenticationHandler.TEST_SCHEM_NAME);id.AddClaim(cname);//3、主体ClaimsPrincipalprincipal=new(id);//登入awaitHttpContext.SignInAsync(TestAuthenticationHandler.TEST_SCHEM_NAME,principal);if(!string.IsNullOrEmpty(_url)){//重定向回到之前的URLreturnRedirect(_url);}returnView(“Login”,_url);}}

Home 控制器中只用到两个视图,一个是Index,默认主页;另一个是 Login,用于显示登录UI。

Login 视图如下:

@injectMicrosoft.Extensions.Options.IOptions<DemoApp5.TestAuthenticationOptions>_opt@modelstring<formmethod=”post”asp-controller=”Home”asp-action=”PostLogin”><p>用户名:<inputname=”name”type=”text”/></p><p>密码:<inputname=”pwd”type=”password”/></p><buttontype=”submit”>确定</button><inputtype=”hidden”name=”_url”value=”@Model”/></form>

这个视图中绑定的 Model 类型为string,实际上就是 Challenge 方法重定向到此URL时传递的回调URL参数(/Home/Login?return=/Admin/XXX)。在Login方法中,通过View方法把这个URL传给视图中的 Model 属性。

之所以要使用模型绑定,是因为HTTP两次请求间是无状态的:

第一次,GET 方式访问 /Home/Login,并用 return 参数传递了回调URL;

第二次,输入完用户名和密码,POST 方式提交时调用的是 PostLogin 方法,这时候,Login?return=xxxxx 传递的URL已经丢失了,无法再获取。只能绑定到 Model 上,再从 Model 中取值绑定到 hidden 元素上。

<inputtype=”hidden”name=”_url”value=”@Model”/>

POST的时候就会连同这个 hidden 一起发回给服务器,这样在 PostLogin 方法中还能够获取到这个回调URL。

运行示例后,先是打开默认的 Index 视图。

成功后就跳回到管理后台。

5 秒钟后就会过期,要访问就得重新登录。当然这个主要为了测试方便。实际运用可以设置 15 -20 分钟。

保存 Session 标识的 Cookie 由运行库自动完成,通过浏览器的开发人员工具能够看到生成的 Cookie。

默认的 Cookie 使用了名称 AspNetCore.Session,如果你觉得这个名字不够高大上,可以自己改。在 AddSession 时设置。

builder.Services.AddSession(o=>{//把时间缩短一些,好测试o.IdleTimeout=TimeSpan.FromSeconds(5);o.Cookie.Name=”dyn_ssi”;});

然后,生成的用来保存Session标识的 Cookie 就会变成:

转自:东邪独孤

链接:cnblogs.com/tcjiaan/p/15846148.html

– EOF –

看完本文有收获?请转发分享给更多人

发表评论

登录后才能评论