.Net Core权限认证基于Cookie的认证&授权.Scheme、Policy扩展
在身份认证中,如果某个action需要权限才能访问,最开始的想法就是,哪个action需要权限才能访问,我们写个特性标注到上面即可,[typefilter(typeof(customauthorizeactionfilterattribute))]
/// <summary> /// 这是一个action的filter` 但是用作权限验证 /// </summary> public class customauthorizeactionfilterattribute : attribute, iactionfilter { private ilogger<customauthorizeactionfilterattribute> _logger = null; public customauthorizeactionfilterattribute(ilogger<customauthorizeactionfilterattribute> logger) { this._logger = logger; } public void onactionexecuting(actionexecutingcontext context) { //取出session var struser = context.httpcontext.session.getstring("currentuser"); if (!string.isnullorwhitespace(struser)) { currentuser currentuser = newtonsoft.json.jsonconvert.deserializeobject<currentuser>(struser); _logger.logdebug($"username is {currentuser.name}"); } else { context.result = new redirectresult("~/fourth/login"); } } public void onactionexecuted(actionexecutedcontext context) { //context.httpcontext.response.writeasync("actionfilter executed!"); console.writeline("actionfilter executed!"); //this._logger.logdebug("actionfilter executed!"); } }
当然了,要先在服务里面使用session的服务==》services.addsession();
但是这样不好。.net core框架下,有一个特性authorize,当我们需要使用的时候,在某个action上面标注即可
[authorize] public iactionresult center() { return content("center"); }
我们来运行看一下,会报异常
因为我们没有使用服务,在.net core下面,是默认不启用授权过滤器的。这也是.net core框架的一个好处,我们需要的时候才进行使用。框架做的少,更轻。
下面我们在服务里面使用授权过滤器的服务
services.addauthentication(cookieauthenticationdefaults.authenticationscheme). addcookie(cookieauthenticationdefaults.authenticationscheme, o => { o.loginpath = new pathstring("/home/login"); });
再次浏览刚才的页面,这样就会请求到登录页面,会把刚才请求的页面当做一个参数
当然也要使用app.useauthentication();这个中间件。
在.net core里面,保存登录状态,也是通过cookie的方式。使用claimsidentity与claimsprincipal
public actionresult login(string name, string password) { this._ilogger.logdebug($"{name} {password} 登陆系统"); #region 这里应该是要到数据库中查询验证的 currentuser currentuser = new currentuser() { id = 123, name = "bingle", account = "administrator", password = "123456", email = "415473422@qq.com", logintime = datetime.now, role = name.equals("bingle") ? "admin" : "user" }; #endregion #region cookie { ////就很像一个currentuser,转成一个claimidentity var claimidentity = new claimsidentity("cookie"); claimidentity.addclaim(new claim(claimtypes.nameidentifier, currentuser.id.tostring())); claimidentity.addclaim(new claim(claimtypes.name, currentuser.name)); claimidentity.addclaim(new claim(claimtypes.email, currentuser.email)); claimidentity.addclaim(new claim(claimtypes.role, currentuser.role)); claimidentity.addclaim(new claim(claimtypes.sid, currentuser.id.tostring())); var claimsprincipal = new claimsprincipal(claimidentity); base.httpcontext.signinasync(claimsprincipal).wait();//不就是写到cookie } #endregion return view(); }
再次进行登录,我们就可以看到这样一个cookie
在这之后,我们再去访问genter页面,发现还是和之前返回的结果一样,还是访问不到。这是为什么呢?是因为我们在action上面打的标签[authorize],什么都没给,我们做下修改
[authorize(authenticationschemes = cookieauthenticationdefaults.authenticationscheme)] public iactionresult center() { return content("center"); }
现在我们再次进行访问,发现就可以访问成功了
通过user.findfirstvalue(claimtypes.sid);这种方式,可以获取到我们存入的值。
scheme、policy扩展
scheme
#region 设置自己的schema的handler services.addauthenticationcore(options => options.addscheme<myhandler>("myscheme", "demo myscheme")); #endregion #region schame 验证 services.addauthentication(options => { options.defaultscheme = cookieauthenticationdefaults.authenticationscheme;// "richard";// }) .addcookie(options => { options.loginpath = new pathstring("/fourth/login");// 这里指定如果验证不通过就跳转到这个页面中去 options.claimsissuer = "cookie"; });
myhandler类:
/// <summary> /// 自定义的handler /// 通常会提供一个统一的认证中心,负责证书的颁发及销毁(登入和登出),而其它服务只用来验证证书,并用不到singin/singout。 /// </summary> public class myhandler : iauthenticationhandler, iauthenticationsigninhandler, iauthenticationsignouthandler { public authenticationscheme scheme { get; private set; } protected httpcontext context { get; private set; } public task initializeasync(authenticationscheme scheme, httpcontext context) { scheme = scheme; context = context; return task.completedtask; } /// <summary> /// 认证 /// </summary> /// <returns></returns> public async task<authenticateresult> authenticateasync() { var cookie = context.request.cookies["mycookie"]; if (string.isnullorempty(cookie)) { return authenticateresult.noresult(); } return authenticateresult.success(this.deserialize(cookie)); } /// <summary> /// 没有登录 要求 登录 /// </summary> /// <param name="properties"></param> /// <returns></returns> public task challengeasync(authenticationproperties properties) { context.response.redirect("/login"); return task.completedtask; } /// <summary> /// 没权限 /// </summary> /// <param name="properties"></param> /// <returns></returns> public task forbidasync(authenticationproperties properties) { context.response.statuscode = 403; return task.completedtask; } /// <summary> /// 登录 /// </summary> /// <param name="user"></param> /// <param name="properties"></param> /// <returns></returns> public task signinasync(claimsprincipal user, authenticationproperties properties) { var ticket = new authenticationticket(user, properties, scheme.name); context.response.cookies.append("mycookie", this.serialize(ticket)); return task.completedtask; } /// <summary> /// 退出 /// </summary> /// <param name="properties"></param> /// <returns></returns> public task signoutasync(authenticationproperties properties) { context.response.cookies.delete("mycookie"); return task.completedtask; } private authenticationticket deserialize(string content) { byte[] byteticket = system.text.encoding.default.getbytes(content); return ticketserializer.default.deserialize(byteticket); } private string serialize(authenticationticket ticket) { //需要引入 microsoft.aspnetcore.authentication byte[] byteticket = ticketserializer.default.serialize(ticket); return encoding.default.getstring(byteticket); } }
policy
#region 支持 policy 认证授权的服务 // 指定通过策略验证的策略列 services.addsingleton<iauthorizationhandler, advancedrequirement>(); services.addauthorization(options => { //advancedrequirement可以理解为一个别名 options.addpolicy("advancedrequirement", policy => { policy.addrequirements(new nameauthorizationrequirement("1")); }); }).addauthentication(options => { options.defaultscheme = cookieauthenticationdefaults.authenticationscheme; }) .addcookie(options => { options.loginpath = new pathstring("/fourth/login"); options.claimsissuer = "cookie"; }); #endregion
advancedrequirement类:
/// <summary> /// policy 的策略 或者是规则 /// </summary> public class advancedrequirement : authorizationhandler<nameauthorizationrequirement>, iauthorizationrequirement { protected override task handlerequirementasync(authorizationhandlercontext context, nameauthorizationrequirement requirement) { // 这里可以把用户信息获取到以后通过数据库进行验证 // 这里就可以做一个规则验证 // 也可以通过配置文件来验证 if (context.user != null && context.user.hasclaim(c => c.type == claimtypes.sid)) { string sid = context.user.findfirst(c => c.type == claimtypes.sid).value; if (!sid.equals(requirement.requiredname)) { context.succeed(requirement); } } return task.completedtask; } }
还需要在configure方法中对中间件进行使用
app.usesession(); app.usecookiepolicy(); // app.useauthentication(); // 标识在当前系统中使用这个权限认证
总结:
在.net framwork环境授权一般来说是这个样子的,在登录的时候写入session,在需要控制权限的方法上标机一个权限特性,实现在方法执行前对session进行判断,如果有session,就有权限。但是这种方式比较局限。
.net core下的权限认证,来自于authenticationhttpcontextextensions扩展。
6大方法,可以自行扩展这6个方法:需要自定义一个handler,handler需要继承实现iauthenticationhandler,iauthenticationsigninhandler,iauthenticationsignouthandler。分别实现6个方法,需要制定在core中使用。services.addauthenticationcore(options => options.addscheme<myhandler>("myscheme", "demo myscheme"));
如果使用了sechme验证,验证不通过的时候,就默认跳转到account/login?returnurl=......。权限验证来自于iauthentizedata:authenticationschemes policy roles。权限验证支持action、控制器、全局三种注册方式。