asp.net core的认证和授权
在asp.net core中,微软提供了基于认证(Authentication)和授权(Authorization)的方式,来实现权限管理的,本篇博文,介绍基于固定角色的权限管理和自定义角色权限管理,本文内容,更适合传统行业的BS应用,而非互联网应用。
在asp.net core中,我们认证(Authentication)通常是在Login的Post Action中进行用户名或密码来验证用户是否正确,如果通过验证,即该用户就会获得一个或几个特定的角色,通过ClaimTypes.Role来存储角色,从而当一个请求到达时,用这个角色和Controller或Action上加的特性[Authorize(Roles="admin,system")]来授权是否有权访问该Action。本文中的自定义角色,会把验证放在中间件中进行处理。
固定角色:
即把角色与具体的Controller或Action直接关联起来,整个系统中的角色是固定的,每种角色可以访问那些Controller或Action也是固定的,这做法比较适合小型项目,角色分工非常明确的项目。
项目代码:
https://github.com/axzxs2001/Asp.NetCoreExperiment/tree/master/Asp.NetCoreExperiment/%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86/RolePrivilegeManagement
始于startup.cs
需要在ConfigureServices中注入Cookie的相关信息,options是CookieAuthenticationOptions,关于这个类型提供如下属性,可参考:https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?tabs=aspnetcore2x
它提供了登录的一些信息,或登录生成Cookie的一些信息,用以后
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Builder;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.Extensions.Configuration;usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.AspNetCore.Authentication.Cookies;usingMicrosoft.AspNetCore.Http;namespaceRolePrivilegeManagement{publicclassStartup{publicStartup(IConfigurationconfiguration){Configuration=configuration;}publicIConfigurationConfiguration{get;}publicvoidConfigureServices(IServiceCollectionservices){services.AddMvc();//添加认证Cookie信息services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options=>{options.LoginPath=newPathString("/login");options.AccessDeniedPath=newPathString("/denied");});}publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv){if(env.IsDevelopment()){app.UseDeveloperExceptionPage();app.UseBrowserLink();}else{app.UseExceptionHandler("/Home/Error");}app.UseStaticFiles();//验证中间件app.UseAuthentication();app.UseMvc(routes=>{routes.MapRoute(name:"default",template:"{controller=Home}/{action=Index}/{id?}");});}}}
HomeController.cs
对于Login Get的Action,把returnUrl用户想要访问的地址(有可能用户记录下想要访问的url了,但系统会转到登录页,登录成功后直接跳转到想要访问的returnUrl页)
对于Login Post的Action,验证用户密和密码,成功能,定义一个ClaimsIdentity,把用户名和角色,和用户姓名的声明都添回进来(这个角色,就是用来验证可访问action的角色 )作来该用户标识,接下来调用HttpContext.SignInAsync进行登录,注意此方法的第一个参数,必需与StartUp.cs中services.AddAuthentication的参数相同,AddAuthentication是设置登录,SigninAsync是按设置参数进行登录
对于Logout Get的Action,是退出登录
HomeController上的[Authorize(Roles=”admin,system”)]角色和权限的关系时,所有Action只有admin和system两个角色能访问到,About上的[Authorize(Roles=”admin”)]声明这个action只能admin角色访问,Contact上的[Authorize(Roles=”system”)]声明这个action只能system角色访问,如果action上声明的是[AllowAnomymous],说明不受授权管理,可以直接访问。
usingSystem;usingSystem.Collections.Generic;usingSystem.Diagnostics;usingSystem.Linq;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Mvc;usingRolePrivilegeManagement.Models;usingSystem.Security.Claims;usingMicrosoft.AspNetCore.Authentication;usingMicrosoft.AspNetCore.Authentication.Cookies;usingMicrosoft.AspNetCore.Authorization;namespaceRolePrivilegeManagement.Controllers{[Authorize(Roles="admin,system")]publicclassHomeController:Controller{publicIActionResultIndex(){returnView();}[Authorize(Roles="admin")]publicIActionResultAbout(){ViewData["Message"]="Yourapplicationdescriptionpage.";returnView();}[Authorize(Roles="system")]publicIActionResultContact(){ViewData["Message"]="Yourcontactpage.";returnView();}publicIActionResultError(){returnView(newErrorViewModel{RequestId=Activity.Current?.Id??HttpContext.TraceIdentifier});}[AllowAnonymous][HttpGet("login")]publicIActionResultLogin(stringreturnUrl=null){TempData["returnUrl"]=returnUrl;returnView();}[AllowAnonymous][HttpPost("login")]publicasyncTask<IActionResult>Login(stringuserName,stringpassword,stringreturnUrl=null){varlist=newList<dynamic>{new{UserName="gsw",Password="111111",Role="admin",Name="桂素伟"},new{UserName="aaa",Password="222222",Role="system",Name="测试A"}};varuser=list.SingleOrDefault(s=>s.UserName==userName&&s.Password==password);if(user!=null){//用户标识varidentity=newClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);identity.AddClaim(newClaim(ClaimTypes.Sid,userName));identity.AddClaim(newClaim(ClaimTypes.Name,user.Name));identity.AddClaim(newClaim(ClaimTypes.Role,user.Role));awaitHttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,newClaimsPrincipal(identity));if(returnUrl==null){returnUrl=TempData["returnUrl"]?.ToString();}if(returnUrl!=null){returnRedirect(returnUrl);}else{returnRedirectToAction(nameof(HomeController.Index),"Home");}}else{conststringbadUserNameOrPasswordMessage="用户名或密码错误!";returnBadRequest(badUserNameOrPasswordMessage);}}[HttpGet("logout")]publicasyncTask<IActionResult>Logout(){awaitHttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);returnRedirectToAction("Index","Home");}[AllowAnonymous][HttpGet("denied")]publicIActionResultDenied(){returnView();}}}
前端_Layout.cshtml布局页,在登录成功后的任何页面都可以用@User.Identity.Name就可以获取用户姓名,同时用@User.Claims.SingleOrDefault(s=>s.Type==System.Security.Claims.ClaimTypes.Sid).Value可以获取用户名或角色。
<!DOCTYPEhtml><html><head><metacharset="utf-8"/><metaname="viewport"content="width=device-width,initial-scale=1.0"/><title>@ViewData["Title"]-RolePrivilegeManagement</title><environmentinclude="Development"><linkrel="stylesheet"href="~/lib/bootstrap/dist/css/bootstrap.css"/><linkrel="stylesheet"href="~/css/site.css"/></environment><environmentexclude="Development"><linkrel="stylesheet"href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"asp-fallback-test-class="sr-only"asp-fallback-test-property="position"asp-fallback-test-value="absolute"/><linkrel="stylesheet"href="~/css/site.min.css"asp-append-version="true"/></environment><style>/*未访问的链接*/a.logout:link{color:#9d9d9d}/*已访问的链接*/a.logout:visited{color:#9d9d9d}/*当有鼠标悬停在链接上*/a.logout:hover{color:#ffffff}/*被选择的链接*/a.logout:active{color:#9d9d9d}a.logout{text-decoration:none;}</style></head><body><navclass="navbarnavbar-inversenavbar-fixed-top"><divclass="container"><divclass="navbar-header"><buttontype="button"class="navbar-toggle"data-toggle="collapse"data-target=".navbar-collapse"><spanclass="sr-only">Togglenavigation</span><spanclass="icon-bar"></span><spanclass="icon-bar"></span><spanclass="icon-bar"></span></button><aasp-area=""asp-controller="Home"asp-action="Index"class="navbar-brand">RolePrivilegeManagement</a></div><divclass="navbar-collapsecollapse"><ulclass="navnavbar-nav"><li><aasp-area=""asp-controller="Home"asp-action="Index">Home</a></li><li><aasp-area=""asp-controller="Home"asp-action="About">About</a></li><li><aasp-area=""asp-controller="Home"asp-action="Contact">Contact</a></li></ul><ulclass=""><li><div><span>当前用户:@User.Identity.Name</span></div><div><aasp-area=""asp-controller="Home"asp-action="Logout"class="logout">注销</a></div></li></ul></div></div></nav><divclass="containerbody-content">@RenderBody()<hr/><footer><p>©2017-RolePrivilegeManagement</p></footer></div><environmentinclude="Development"><scriptsrc="~/lib/jquery/dist/jquery.js"></script><scriptsrc="~/lib/bootstrap/dist/js/bootstrap.js"></script><scriptsrc="~/js/site.js"asp-append-version="true"></script></environment><environmentexclude="Development"><scriptsrc="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"asp-fallback-src="~/lib/jquery/dist/jquery.min.js"asp-fallback-test="window.jQuery"crossorigin="anonymous"integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk"></script><scriptsrc="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"asp-fallback-test="window.jQuery&&window.jQuery.fn&&window.jQuery.fn.modal"crossorigin="anonymous"integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"></script><scriptsrc="~/js/site.min.js"asp-append-version="true"></script></environment>@RenderSection("Scripts",required:false)</body></html>
现在可以用chrome运行了,进行登录页后F12,查看Network—Cookies,可以看到有一个Cookie,这个是记录returnUrl的Cookie,是否记得HomeController.cs中的Login Get的Action中代码:TempData["returnUrl"]= returnUrl;这个TempData最后转成了一个Cookie返回到客户端了,如下图:
输入用户名,密码登录,再次查看Cookies,发现多了一个.AspNetCore.Cookies,即把用户验证信息加密码保存在了这个Cookie中,当跳转到别的页面时,这两个Cookie会继续在客户端和服务传送,用以验证用户角色。
自定义角色
系统的角色可以自定义,用户是自写到义,权限是固定的,角色对应权限可以自定义,用户对应角色也是自定义的,如下图:
项目代码:
https://github.com/axzxs2001/Asp.NetCoreExperiment/tree/master/Asp.NetCoreExperiment/%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86/PrivilegeManagement
始于startup.cs
自定义角色与固定角色不同之处在于多了一个中间件(关于中间件学习参看:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware),即在Configure方法中,一定要在app.UseAuthentication下面添加验证权限的中间件,因为UseAuthentication要从Cookie中加载通过验证的用户信息到Context.User中,所以一定放在加载完后才能去验用户信息(当然自己读取Cookie也可以)
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Builder;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.Extensions.Configuration;usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.AspNetCore.Authentication.Cookies;usingMicrosoft.AspNetCore.Http;usingPrivilegeManagement.Middleware;namespacePrivilegeManagement{publicclassStartup{publicStartup(IConfigurationconfiguration){Configuration=configuration;}publicIConfigurationConfiguration{get;}publicvoidConfigureServices(IServiceCollectionservices){services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options=>{options.LoginPath=newPathString("/login");options.AccessDeniedPath=newPathString("/denied");});services.AddMvc();}publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv){if(env.IsDevelopment()){app.UseDeveloperExceptionPage();app.UseBrowserLink();}else{app.UseExceptionHandler("/Home/Error");}app.UseStaticFiles();//验证中间件app.UseAuthentication();////添加权限中间件,一定要放在app.UseAuthentication后app.UsePermission(newPermissionMiddlewareOption(){LoginAction=@"/login",NoPermissionAction=@"/denied",//这个集合从数据库中查出所有用户的全部权限UserPerssions=newList<UserPermission>(){newUserPermission{Url="/",UserName="gsw"},newUserPermission{Url="/home/contact",UserName="gsw"},newUserPermission{Url="/home/about",UserName="aaa"},newUserPermission{Url="/",UserName="aaa"}}});app.UseMvc(routes=>{routes.MapRoute(name:"default",template:"{controller=Home}/{action=Index}/{id?}");});}}}
下面看看中间件PermissionMiddleware.cs,在Invoke中用了context.User,如上面所述,首先要调用app.UseAuthentication加载用户信息后才能在这里使用,这个中间件逻辑较简单,如果没有验证的一律放过去,不作处理,如果验证过(登录成功了),就要查看本次请求的url和这个用户可以访问的权限是否匹配,如不匹配,就跳转到拒绝页面(这个是在Startup.cs中添加中间件时,用NoPermissionAction = @"/denied"设置的),这里定义了一个静态的List<UserPermission>,这是为了热更新此集合,而不需要用户权限变更后重新整个web应用。
usingMicrosoft.AspNetCore.Http;usingSystem;usingSystem.Collections.Generic;usingSystem.IO;usingSystem.Linq;usingSystem.Reflection;usingSystem.Security.Claims;usingSystem.Threading.Tasks;namespacePrivilegeManagement.Middleware{///<summary>///权限中间件///</summary>publicclassPermissionMiddleware{///<summary>///管道代理对象///</summary>privatereadonlyRequestDelegate_next;///<summary>///权限中间件的配置选项///</summary>privatereadonlyPermissionMiddlewareOption_option;///<summary>///用户权限集合///</summary>internalstaticList<UserPermission>_userPermissions;///<summary>///权限中间件构造///</summary>///<paramname="next">管道代理对象</param>///<paramname="permissionResitory">权限仓储对象</param>///<paramname="option">权限中间件配置选项</param>publicPermissionMiddleware(RequestDelegatenext,PermissionMiddlewareOptionoption){_option=option;_next=next;_userPermissions=option.UserPerssions;}///<summary>///调用管道///</summary>///<paramname="context">请求上下文</param>///<returns></returns>publicTaskInvoke(HttpContextcontext){//请求UrlvarquestUrl=context.Request.Path.Value.ToLower();//是否经过验证varisAuthenticated=context.User.Identity.IsAuthenticated;if(isAuthenticated){if(_userPermissions.GroupBy(g=>g.Url).Where(w=>w.Key.ToLower()==questUrl).Count()>0){//用户名varuserName=context.User.Claims.SingleOrDefault(s=>s.Type==ClaimTypes.Sid).Value;if(_userPermissions.Where(w=>w.UserName==userName&&w.Url.ToLower()==questUrl).Count()>0){returnthis._next(context);}else{//无权限跳转到拒绝页面context.Response.Redirect(_option.NoPermissionAction);}}}returnthis._next(context);}}}
扩展中间件类PermissionMiddlewareExtensions.cs
usingMicrosoft.AspNetCore.Builder;usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;namespacePrivilegeManagement.Middleware{///<summary>///扩展权限中间件///</summary>publicstaticclassPermissionMiddlewareExtensions{///<summary>///引入权限中间件///</summary>///<paramname="builder">扩展类型</param>///<paramname="option">权限中间件配置选项</param>///<returns></returns>publicstaticIApplicationBuilderUsePermission(thisIApplicationBuilderbuilder,PermissionMiddlewareOptionoption){returnbuilder.UseMiddleware<PermissionMiddleware>(option);}}}
中间件属性PermissionMiddlewareOption.cs
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;namespacePrivilegeManagement.Middleware{///<summary>///权限中间件选项///</summary>publicclassPermissionMiddlewareOption{///<summary>///登录action///</summary>publicstringLoginAction{get;set;}///<summary>///无权限导航action///</summary>publicstringNoPermissionAction{get;set;}///<summary>///用户权限集合///</summary>publicList<UserPermission>UserPerssions{get;set;}=newList<UserPermission>();}}
中间件实体类UserPermission.cs
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;namespacePrivilegeManagement.Middleware{///<summary>///用户权限///</summary>publicclassUserPermission{///<summary>///用户名///</summary>publicstringUserName{get;set;}///<summary>///请求Url///</summary>publicstringUrl{get;set;}}}
关于自定义角色,因为不需要授权时带上角色,所以可以定义一个基Controller类BaseController.cs,其他的Controller都继承BaseController,这样所有的action都可以通过中间件来验证,当然像登录,无权限提示页面还是在Action上加[AllowAnomymous]
usingMicrosoft.AspNetCore.Authorization;usingMicrosoft.AspNetCore.Mvc;namespacePrivilegeManagement.Controllers{[Authorize]publicclassBaseController:Controller{}}
HomeController.cs如下,与固定角色的HomeController.cs差异只在Controller和Action上的Authorize特性。
usingSystem;usingSystem.Collections.Generic;usingSystem.Diagnostics;usingSystem.Linq;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Mvc;usingPrivilegeManagement.Models;usingMicrosoft.AspNetCore.Authorization;usingSystem.Security.Claims;usingMicrosoft.AspNetCore.Authentication.Cookies;usingMicrosoft.AspNetCore.Authentication;namespacePrivilegeManagement.Controllers{publicclassHomeController:BaseController{publicIActionResultIndex(){returnView();}publicIActionResultAbout(){ViewData["Message"]="Yourapplicationdescriptionpage.";returnView();}publicIActionResultContact(){ViewData["Message"]="Yourcontactpage.";returnView();}publicIActionResultError(){returnView(newErrorViewModel{RequestId=Activity.Current?.Id??HttpContext.TraceIdentifier});}[AllowAnonymous][HttpGet("login")]publicIActionResultLogin(stringreturnUrl=null){TempData["returnUrl"]=returnUrl;returnView();}[AllowAnonymous][HttpPost("login")]publicasyncTask<IActionResult>Login(stringuserName,stringpassword,stringreturnUrl=null){varlist=newList<dynamic>{new{UserName="gsw",Password="111111",Role="admin",Name="桂素伟"},new{UserName="aaa",Password="222222",Role="system",Name="测试A"}};varuser=list.SingleOrDefault(s=>s.UserName==userName&&s.Password==password);if(user!=null){//用户标识varidentity=newClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);identity.AddClaim(newClaim(ClaimTypes.Sid,userName));identity.AddClaim(newClaim(ClaimTypes.Name,user.Name));identity.AddClaim(newClaim(ClaimTypes.Role,user.Role));awaitHttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,newClaimsPrincipal(identity));if(returnUrl==null){returnUrl=TempData["returnUrl"]?.ToString();}if(returnUrl!=null){returnRedirect(returnUrl);}else{returnRedirectToAction(nameof(HomeController.Index),"Home");}}else{conststringbadUserNameOrPasswordMessage="用户名或密码错误!";returnBadRequest(badUserNameOrPasswordMessage);}}[HttpGet("logout")]publicasyncTask<IActionResult>Logout(){awaitHttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);returnRedirectToAction("Index","Home");}[HttpGet("denied")]publicIActionResultDenied(){returnView();}}}
全部代码:https://github.com/axzxs2001/Asp.NetCoreExperiment/tree/master/Asp.NetCoreExperiment/%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。