嫦娥照片,郑州代办信用卡公司,刁牌蛮妃
目录
以相对简单优雅的方式实现用户身份验证和鉴权,解决以下两个问题:
创建一个asp.net core web应用程序
通过nuget安装swashbuckle.aspnetcore
程序包,并在startup类中启用swagger支持
因为这个示例项目不打算编写前端网页,所以直接使用swagger来调试,真的很方便。
添加一个空的mvc控制器(homecontroller)和一个空的api控制器(authcontroller)
homecontroller.index()
方法中只写一句简单的跳转代码即可:
return new redirectresult("~/swagger");
authcontroller
类中随便写一两个骨架方法,方便看效果。
运行项目,会自动打开浏览器并跳转到swagger页面。
claimtypes 定义一些常用的声明类型常量
iclaimssession 表示当前会话信息的接口
claimssession 会话信息实现类
根据声明类型从claimsprincipal.claimsidentity属性中读取用户id、用户名等信息。
实际项目中可从此类继承或完全重新实现自己的session类,以添加更多的会话信息(例如工作部门)
itoken 登录令牌接口
包含访问令牌、刷新令牌、令牌时效等令牌
iidentity 身份证明接口
包含用户基本信息及令牌信息
iauthenticationservice 验证服务接口
抽象出来的验证服务接口,仅规定了四个身份验证相关的方法,如需扩展可定义由此接口派生的接口。
方法名 | 返回值类型 | 说明 |
---|---|---|
login(username, password) | iidentity | 根据用户名及密码验证其身份,成功则返回身份证明 |
logout() | void | 注销本次登录,即使未登录也不报错 |
refreshtoken(refreshtoken) | token | 刷新登录令牌,如果当前用户未登录则报错 |
validatetoken(accesstoken) | iidentity | 验证访问令牌,成功则返回身份证明 |
simpletoken 登录令牌的简化实现
这个类提不提供都可以,实际项目中大家生成token的算法肯定是各不相同的,提供简单实现仅用于演示
bearerdefaults 定义了一些与身份验证相关的常量
如:authenticationscheme
beareroptions 身份验证选项类
从authenticationschemeoptions
继承而来
bearervalidatedcontext 验证结果上下文
bearerhandler 身份验证处理器 <= 关键类
覆盖了handleauthenticateasync()
方法,实现自定义的身份验证逻辑,简述如下:
获取访问令牌。从请求头中获取authorization
信息,如果没有则从请求的参数中获取
如果访问令牌为空,则终止验证,但不报错,直接返回authenticateresult.noresult()
调用从构造函数注入的iauthenticationservice
实例的validatetoken()
方法,验证访问令牌是否有效,如果该方法触发异常(例如令牌过期)则捕获后通过authenticateresult.fail()
返回错误信息,如果该方法返回值为空(例如访问令牌根本不存在)则返回authenticateresult.noresult()
,不报错。
到这一步说明身份验证已经通过,而且拿到身份证明信息,根据该信息创建claim
数组,然后再创建一个包含这些claim
数据的claimsprincipal
实例,并将thread.currentprincipal设置为该实例。
重点:其实,httpcontext.user
属性的类型正是currentprincipal
,而其值应该就是来自于thread.currentprincipal
。
构造bearervalidatedcontext
实例,并将其principal
属性赋值为上面创建的claimsprincipal
实例,然后调用success()
方法,表示验证成功。最后返回该实例的result
属性值。
bearerextensions 包含一些扩展方法,提供使用便利
重点在于addbearer()
方法内调用builder.addscheme<toptions,thandler>()
泛型方法时,分别使用了前面编写的beareroptions
、bearerhandler
类作为泛型参数。
public static authenticationbuilder addbearer(...) { return builder.addscheme<beareroptions, bearerhandler>(...); }
如果想要自己实现bearerhandler
类的验证逻辑,可以抛弃此类,重新编写使用新handler类的扩展方法
这部分是身份验证的落地,实际项目中应该将上面两步(定义基本类型和接口、编写验证处理器)的代码抽象出来,成为独立可复用的软件包,利用该软件包进行身份验证的实现逻辑可参照此示例代码。
identity 身份证明实现类
sampleauthenticationservice 验证服务的简单实现
出于演示方便,固化了三个用户(admin/123456、user/123、tester/123)
authcontroller 通过http向前端提供验证服务的控制器类
提供了用户登录、令牌刷新、令牌验证等方法。
还需要修改项目中startup.cs
文件,添加依赖注入规则、身份验证,并启用身份验证中间件。
在configureservices
方法内添加代码:
//添加依赖注入规则 services.addscoped<iclaimssession, claimssession>(); services.addscoped<iauthenticationservice, sampleauthenticationservice>(); //添加身份验证 services.addauthentication(options => { options.defaultauthenticatescheme = bearerdefaults.authenticationscheme; options.defaultchallengescheme = bearerdefaults.authenticationscheme; }).addbearer();
在configure()
方法内添加代码:
//启用身份验证中间件 app.useauthentication();
测试登录功能
启动项目,自动进入[swagger ui]界面,点击/api/auth/login
方法,不修改输入框中的内容直接点击[execute]按钮,可以见到返回401错误码。
在输入框中输入{"username": "admin", "password": "123456"}
,然后点击[execute]按钮,系统验证成功并返回身份证明信息。
记下访问令牌2ad43df2c11d48a18a88441adbf4994a
和刷新令牌9bbaf811ed8b4d29b638777d4f89238e
测试刷新登录令牌
点击/api/auth/refresh
方法,在输入框中输入上面获取到的刷新令牌9bbaf811ed8b4d29b638777d4f89238e
,然后点击[execute]按钮,返回401错误码。原因是因为我们并未提供访问令牌。
点击方法名右侧的[锁]图标,在弹出框中输入之前获取的访问令牌2ad43df2c11d48a18a88441adbf4994a
并点击[authorize]按钮后关闭对话框,重新点击[execute]按钮,成功获取到新的登录令牌。
测试验证访问令牌
点击/api/auth/validate
方法,在输入框中输入第一次获取的到访问令牌2ad43df2c11d48a18a88441adbf4994a
,然后点击[execute]按钮,返回400错误码,表明发起的请求参数有误。因为此方法是支持匿名访问的,所以错误码不会是401.
将输入框内容修改为新的访问令牌f37542e162ed4855921ddf26b05c3f25
,然后点击[execute]按钮,验证成功,返回了对应的用户身份证明信息。
在asp.net core项目中实现基于角色的授权很容易,在一些权限管理并不复杂的项目中,采取这种方式来实现权限鉴定简单可行。有兴趣可以参考这篇博文asp.net core 认证与授权5:初识授权
但是,对于稍微复杂一些的项目,权限划分又细又多,如果采用这种方式,要覆盖到各种各样的权限组合,需要在代码中定义相当多的角色,大大增加项目维护工作,并且很不灵活。
这里借鉴abp框架中权限鉴定的一些思想,来实现基于功能点的权限访问控制。
非常感谢asp.net core和abp等诸多优秀的开源项目,向你们致敬!
不得不说abp框架非常优秀,但是我并不喜欢使用它,因为我没有能力和精力搞清楚它的详细设计思路,而且很多功能我根本不需要。
asp.net core提供了一个iauthorizationfilter
接口,如果在控制器类上添加[授权过滤]特性,相应的authorizationfilter类的onauthorization()
方法会在控制器的action
之前运行,如果在该方法中设置authorizationfiltercontext.result为一个错误的response,action
将不会被调用。
基于这个思路,我们设计了以下方案:
编写一个attribute(特性)类,包含以下两个属性:
permissions:需要检查的权限数组
requireallpermissions:是否需要拥有数组中全部权限,如果为否则拥有任一权限即可
定义一个ipermissionchecker
接口,在接口中定义isgrantedasync()
方法,用于执行权限鉴定逻辑
编写一个authorizationfilterattribute特性类(应用目标为class),通过属性注入ipermissionchecker
实例。然后在onauthorization()
方法内调用ipermissionchecker
实例的isgrantedasync()
方法,如果该方法返回值为false,则返回403错误,否则正常放行。
apiauthorizeattribute类
[attributeusage(attributetargets.method)] public class apiauthorizeattribute : attribute, ifiltermetadata { public string[] permissions { get; } public bool requireallpermissions { get; set; } public apiauthorizeattribute(params string[] permissions) { permissions = permissions; } }
ipermissionchecker接口定义
public interface ipermissionchecker { task<bool> isgrantedasync(string permissionname); }
authorizationfilterattribute类
[attributeusage(attributetargets.class)] public class authorizationfilterattribute : attribute, iauthorizationfilter { [injection] //属性注入 public ipermissionchecker permissionchecker { get; set; } = nullpermissionchecker.instance; public void onauthorization(authorizationfiltercontext context) { if(存在[allowanonymous]特性) return; var authorizeattribute = 从context.filters中析出apiauthorizeattribute foreach (var permission in authorizeattribute.permissions) { //检查各项权限 var granted = permissionchecker.isgrantedasync(permission).result; } if(检查未通过) context.result = new objectresult("未授权") { statuscode = 403 }; } }
配合属性注入提供nullpermissionchecker类,在isgrantedasync()
方法内直接返回true。
做好上面的准备,我们应该可以开始着手在项目内应用权限鉴定功能了,不过asp.net core内置的di框架并不支持属性注入,所以还得添加属性注入的功能。
定义injectionattribute类,用于显式声明应用了此特性的属性将使用依赖注入
/// <summary> /// 在属性上添加此特性,以声明该属性需要使用依赖注入 /// </summary> [attributeusage(attributetargets.property)] public class injectionattribute : attribute { }
添加一个propertiesautowiredfilterprovider
类,从defaultfilterprovider
类派生
public class propertiesautowiredfilterprovider : defaultfilterprovider { private static idictionary<string, ienumerable<propertyinfo>> _publicpropertycache = new dictionary<string, ienumerable<propertyinfo>>(); public override void providefilter(filterprovidercontext context, filteritem filteritem) { base.providefilter(context, filteritem); //在调用基类方法之前filteritem变量不会有值 var filtertype = filteritem.filter.gettype(); if (!_publicpropertycache.containskey(filtertype.fullname)) { var ps=filtertype.getproperties(bindingflags.public|bindingflags.instance) .where(c => c.getcustomattribute<injectionattribute>() != null); _publicpropertycache[filtertype.fullname] = ps; } var injectionproperties = _publicpropertycache[filtertype.fullname]; if (injectionproperties?.count() == 0) return; //下面是注入属性实例的关键代码 var serviceprovider = context.actioncontext.httpcontext.requestservices; foreach (var item in injectionproperties) { var service = serviceprovider.getservice(item.propertytype); if (service == null) { throw new invalidoperationexception($"unable to resolve service for type '{item.propertytype.fullname}' while attempting to activate '{filtertype.fullname}'"); } item.setvalue(filteritem.filter, service); } } }
还有非常关键的一步,在startup.configureservices()
中添加下面的代码,替换ifilterprovider
接口的实现类为上面编写的propertiesautowiredfilterprovider
类
services.replace(servicedescriptor.singleton<microsoft.aspnetcore.mvc.filters.ifilterprovider, propertiesautowiredfilterprovider>());
终于,我们可以在项目内应用权限鉴定功能了。
首先,我们定义一些功能点权限常量
public static class permissionnames { public const string testadd = "test.add"; public const string testedit = "test.edit"; public const string testdelete = "test.delete"; }
接着,添加一个新的用于测试的控制器类
[authorizationfilter] [route("api/[controller]")] [apicontroller] public class testcontroller : controllerbase { [injection] public iclaimssession session { get; set; } [httpget] [route("[action]")] public iactionresult currentuser() => ok(session?.username); [apiauthorize] [httpget("{id}")] public iactionresult get(int id)=> ok(id); [apiauthorize(permissionnames.testadd)] [httppost] [route("[action]")] public iactionresult create()=> ok(); [apiauthorize(permissionnames.testedit, requireallpermissions = false)] [httppost] [route("[action]")] public iactionresult update()=> ok(); [apiauthorize(permissionnames.testadd, permissionnames.testedit, requireallpermissions = false)] [httppost] [route("[action]")] public iactionresult patch() => ok(); [apiauthorize(permissionnames.testdelete)] [httpdelete("{id}")] public iactionresult delete(int id) => ok(); }
在控制器类上添加了[authorizationfilter]特性,除了currentuser()
方法以外,都添加了[apiauthorize]特性,所需的权限各不相同,为简化测试所有的action
都直接返回okresult
。
实现一个用于演示的权限检查器类
public class samplepermissionchecker : ipermissionchecker { private readonly dictionary<long, string[]> userpermissions = new dictionary<long, string[]> { //id=1的用户具有test模块的全部功能 { 1, new[] { permissionnames.testadd, permissionnames.testedit, permissionnames.testdelete } }, //id=2的用户具有test模块的编辑和删除功能 { 2, new[] { permissionnames.testedit, permissionnames.testdelete } } }; public iclaimssession session { get; } //通过构造函数注入iclaimssession实例,以便在权限鉴定方法中获取用户信息 public samplepermissionchecker(iclaimssession session) { this.session = session; } public task<bool> isgrantedasync(string permissionname) { if(!userpermissions.any(p => p.key == session.userid)) return task.fromresult(false); var up = userpermissions.where(p => p.key == session.userid).first(); var granted = up.value.any(permission => permission.equals(permissionname, stringcomparison.invariantcultureignorecase)); return task.fromresult(granted); } }
最后还需要修改项目中startup.cs
文件,添加依赖注入规则
services.addsingleton<ipermissionchecker, samplepermissionchecker>();
因为samplepermissionchecker类中并没有需要进程间隔离的数据,所以使用单例模式注册就可以了。不过这样一来,因为该类通过构造函数注入了iclaimssession
接口实例,在构建checker类实例时将触发异常。考虑到cliamssession
类中只有方法没有数据 ,改为单例也并无妨,于是将该接口也改为单例模式注册。
测试未登录时仅可访问/api/test/currentuser
测试以用户user登录,可以访问/api/test/currentuser
和get请求/api/test/{id}
测试以用户admin登录,可以访问除/api/test/add
以外的接口
编写了命令行程序,用来测试前面实现的web api服务。
测试方法
同时运行三个测试程序,都选择[测试身份验证],然后分别输入不同的用户身份序号,快速切换三个程序并按下回车键,三个测试程序会各自发起100次请求,每次请求间隔100毫秒。
例如同时打开三个命令行终端执行:dotnet .\customauthorization.test.dll
测试结果
三个测试程序从后台服务所获取到的当前用户信息完全匹配。
测试方法
预设的权限为:admin=>全部权限,user=>除test.add
以外权限,tester=>无。
分别以admin、user、tester三个用户身份请求/api/test
下的所有接口,并模拟令牌过期的场景。
测试结果
可以见到,以过期的令牌发起请求时,后台返回的状态为unauthorized,当用户未获得足够的授权时后台返回的状态为forbidden。
测试通过!
源代码托管在gitee.com
欢迎转载,请在明显位置给出出处及链接。
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
Blazor server side 自家的一些开源的, 实用型项目的进度之 CEF客户端
.NET IoC模式依赖反转(DIP)、控制反转(Ioc)、依赖注入(DI)
vue+.netcore可支持业务代码扩展的开发框架 VOL.Vue 2.0版本发布
网友评论