ChuanGoing 2019-11-24html
asp.net core系列已经来到了第五篇,经过以前的基础介绍,咱们了解了事件订阅/发布的eventbus整个流程,初探dapper ORM实现,而且简单的介绍了领域模型、领域仓储及服务实现,结合上一篇的日志、错误处理及事务和本篇将要介绍的权限,大体的能够造成一个简单的后端系统架构。固然这些都是零散的一些技术概念的介绍,后面若是有时间的话,我想详细的介绍下如何利用领域驱动来实现一个实际案例。git
话很少讲,下面来看下本篇的学习曲线:github
1.认识Identityserver4web
2.Identityserver4实现认证与受权后端
3.自定义权限的实现api
认识Identityserver4浏览器
关于Identityserver4(ids4)的概念介绍,请查看IdentityServer4 知多少-简书一文。我这里要说的是,asp.net core 下的ids4集成了认证与受权两大功能,使得咱们很是方便的实现一个开放的认证与受权平台,好比公司内部多个系统的集成登陆(单点登陆)/第三方系统数据共享/统一的认证中心等。整个业务流程大体为:缓存
1.用户首先的有用户中心的帐号信息,所以须要注册一个帐号架构
2.用户访问某个站点应用,须要去到用户中心认证app
3.认证经过,用户获得其在用户中心注册的相应信息及其权限时限、范围、大小
4.认证不经过,即非法用户,提示用户注册
5.在第3步的前提下,若用户访问到另外一个站点(采用同一认证平台),这时用户能够用以前认证经过后拿到的访问令牌访问此站点,若此令牌中包含此站点的相应权限便可以前登陆。
Identityserver4实现认证与受权
首先,新建一个asp.net core web 空项目,而且添加以下IdentityServer4 Nuget包
在ConfigureServices添加以下代码
注册IdentityServer中间件,以下5个配置分别表示:
1.AddDeveloperSigningCredential:开发模式下的签名证书,开发环境启用便可
2.AddInMemoryApiResources:相关资源配置
public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource("WebApi", "ChuanGoingWebApi"), new ApiResource("ProductApi", "ChuanGoingWebProduct") }; }
这里配置了两个Api资源
3.AddInMemoryIdentityResources:OpenID Connect相关认证信息配置
public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile() }; }
4.AddInMemoryClients:客户端信息配置
public static IEnumerable<Client> GetClients(IConfiguration Configuration) { var OnlineConfig = Configuration.GetSection("OnlineClient"); var List = new List<Client> { new Client() { ClientId = "ClientCredentials", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("ClientSecret".Sha256()) }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "WebApi", "ProductApi" }, AccessTokenLifetime = 10 * 60 * 1 }, new Client() { ClientId = "ResourceOwnerPassword", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("ClientSecret".Sha256()) }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "WebApi", "ProductApi" }, AccessTokenLifetime = 10 * 60 * 1 }, /* 隐式模式:https://localhost:6005/connect/authorize?client_id=Implicit&redirect_uri=http://localhost:5000/Home&response_type=token&scope=WebApi */ new Client() { ClientId = "Implicit", ClientName = "ImplicitClient", AllowedGrantTypes = GrantTypes.Implicit, ClientSecrets = { new Secret("ImplicitSecret".Sha256()) }, RedirectUris ={OnlineConfig.GetValue<string>("RedirectUris") }, PostLogoutRedirectUris = {OnlineConfig.GetValue<string>("LogoutRedirectUris") }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "WebApi", "ProductApi" }, AccessTokenLifetime = 10 * 60 * 1, //容许将token经过浏览器传递 AllowAccessTokensViaBrowser=true }, /* * 受权码模式:https://localhost:6005/connect/authorize?client_id=GrantCode&redirect_uri=http://localhost:5000/Home&response_type=code&scope=WebApi */ new Client() { //客户端Id ClientId="GrantCode", ClientName="GrantCodeClient", //客户端密码 ClientSecrets={new Secret("CodeSecret".Sha256()) }, //客户端受权类型,Code:受权码模式 AllowedGrantTypes=GrantTypes.Code, //容许登陆后重定向的地址列表,能够有多个 RedirectUris ={OnlineConfig.GetValue<string>("RedirectUris") }, //容许访问的资源 AllowedScopes={ "WebApi", "ProductApi" } } }; return List; }
分别对象Auth2.0的四种模式,本篇将用到的是ResourceOwnerPassword模式,其余几种可在篇尾github连接查看源码的实现
5.AddTestUsers:用户配置,可结合缓存/持久化
public static List<TestUser> GetUsers() { return new List<TestUser> { new TestUser { SubjectId = Guid.NewGuid().ToString(), Username = "admin", Password = "123456" //Claims = new List<Claim> //{ // new Claim("name", "admin"), // new Claim("website", "https://www.cnblogs.com/chuangoing") //} }, new TestUser { SubjectId = Guid.NewGuid().ToString(), Username = "chuangoing", Password = "123456" //Claims = new List<Claim> //{ // new Claim("name", "chuangoing"), // new Claim("website", "https://github.com/chuangoing") //} } }; }
定义两个测试用户,注意这里的SubjectId,用做用户中心注册的openid(认证惟一),后面将会用到
而后,Configure中添加app.UseIdentityServer();//启用ids4
至此,ids4 服务完成
用postman测试下:
返回jwt accesstoken:
将token内容解码,以下:
能够看到,里面包含咱们配置的ProductApi/WebApi的权限
将token信息加入到http的header中:
注意Bearer后面有个空格,访问order的获取订单信息:
自定义权限的实现
这里,咱们将api中的action分别定义一个权限代码,用户拥有了此action访问权限(拥有此权限代码)便可访问,简单实现以下:
1.定义权限特性标识,api的action指定某个标识
public class PermissionAttribute : Attribute { /// <summary> /// 权限代码 /// </summary> public string Code { get; } /// <summary> /// /// </summary> /// <param name="code">权限代码</param> public PermissionAttribute(string code) { Code = code; } }
此处,get action定义了访问权限标识为"XYZ"
一样,咱们这里须要用到一个权限过滤器,利用过滤器的Aop实现权限过滤业务处理:
public class PermissionFilter : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { var user = context.HttpContext.User; if (user.Identity.IsAuthenticated) { //TODO:用户自定义权限验证 Guid userId = context.HttpContext.GetId(); bool right; #region 自定义权限验证 //根据userId判断用户内部系统权限信息 //var userPermissions = repo.GetUserPermissions(userId); //var permissions = repo.GetPermissions(); var metas = context.ActionDescriptor.EndpointMetadata; foreach (var meta in metas) { if (meta is PermissionAttribute permission) { //if (!permissions.Any(p => permission.Code.Any(c => c == p.Code)) // && !userPermissions.Any(p => permission.Code.Any(c => c == p.Code))) //{ // throw new WebException(HttpStatusCode.Forbidden, MessageCodes.AccessDenied, "你没有访问该资源的权限"); //} //break; } } right = false; #endregion if (!right) { context.Result = new ContentResult() { StatusCode = (int)HttpStatusCode.Forbidden, Content = "你没有访问该资源的权限" }; } } }
同时,启用权限过滤器配置
部分代码略过,详细的请查看篇尾的源码连接
利用第二节的认证受权获得的token,咱们用postman测试下:
过滤器切面成功工做
还记得第一节说的SubjectId么?这里利用这个openid,去内部系统去匹配相关用户信息,相关业务就不深刻了,有兴趣的朋友能够下载示例完善下
至此,整个权限认证、受权、自定义权限介绍完。
WebApi详细代码在Github的https://github.com/ChuanGoing/Start.git 的Domain分支能够找到,AuthServer详细代码在https://github.com/ChuanGoing/Demo/tree/master/ChuanGoing.AuthorizationServer中。