zoukankan      html  css  js  c++  java
  • IdentityServer4实现Token认证登录以及权限控制

    相关知识点

    不再对IdentityServer4做相关介绍,博客园上已经有人出了相关的系列文章,不了解的可以看一下:

    蟋蟀大神的:小菜学习编程-IdentityServer4

    晓晨Master:IdentityServer4

    以及Identity,Claim等相关知识:

    Savorboard:ASP.NET Core 之 Identity 入门(一)ASP.NET Core 之 Identity 入门(二)

    创建IndentityServer4 服务

    创建一个名为QuickstartIdentityServer的ASP.NET Core Web 空项目(asp.net core 2.0),端口5000

    NuGet包:

    修改Startup.cs 设置使用IdentityServer:

    public class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
                // configure identity server with in-memory stores, keys, clients and scopes
                services.AddIdentityServer()
                    .AddDeveloperSigningCredential()
                    .AddInMemoryIdentityResources(Config.GetIdentityResourceResources())
                    .AddInMemoryApiResources(Config.GetApiResources())
                    .AddInMemoryClients(Config.GetClients())
                    .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
                    .AddProfileService<ProfileService>();
            }
    
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseIdentityServer();
            }
        }
    

      

    添加Config.cs配置IdentityResource,ApiResource以及Client:

     public class Config
        {
            public static IEnumerable<IdentityResource> GetIdentityResourceResources()
            {
                return new List<IdentityResource>
                {
                    new IdentityResources.OpenId(), //必须要添加,否则报无效的scope错误
                    new IdentityResources.Profile()
                };
            }
            // scopes define the API resources in your system
            public static IEnumerable<ApiResource> GetApiResources()
            {
                return new List<ApiResource>
                {
                    new ApiResource("api1", "My API")
                };
            }
    
            // clients want to access resources (aka scopes)
            public static IEnumerable<Client> GetClients()
            {
                // client credentials client
                return new List<Client>
                {
                    new Client
                    {
                        ClientId = "client1",
                        AllowedGrantTypes = GrantTypes.ClientCredentials,
    
                        ClientSecrets = 
                        {
                            new Secret("secret".Sha256())
                        },
                        AllowedScopes = { "api1",IdentityServerConstants.StandardScopes.OpenId, //必须要添加,否则报forbidden错误
                      IdentityServerConstants.StandardScopes.Profile},
                        
                    },
    
                    // resource owner password grant client
                    new Client
                    {
                        ClientId = "client2",
                        AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
    
                        ClientSecrets = 
                        {
                            new Secret("secret".Sha256())
                        },
                        AllowedScopes = { "api1",IdentityServerConstants.StandardScopes.OpenId, //必须要添加,否则报forbidden错误
                      IdentityServerConstants.StandardScopes.Profile }
                    }
                };
            }
        }
    

      

    因为要使用登录的时候要使用数据中保存的用户进行验证,要实IResourceOwnerPasswordValidator接口:

    public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
        {
            public ResourceOwnerPasswordValidator()
            {
                
            }
    
            public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
            {
                //根据context.UserName和context.Password与数据库的数据做校验,判断是否合法
                if (context.UserName=="wjk"&&context.Password=="123")
                {
                    context.Result = new GrantValidationResult(
                     subject: context.UserName,
                     authenticationMethod: "custom",
                     claims: GetUserClaims());
                }
                else
                {
                    
                     //验证失败
                     context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential");
                }
            }
            //可以根据需要设置相应的Claim
            private Claim[] GetUserClaims()
            {
                return new Claim[]
                {
                new Claim("UserId", 1.ToString()),
                new Claim(JwtClaimTypes.Name,"wjk"),
                new Claim(JwtClaimTypes.GivenName, "jaycewu"),
                new Claim(JwtClaimTypes.FamilyName, "yyy"),
                new Claim(JwtClaimTypes.Email, "977865769@qq.com"),
                new Claim(JwtClaimTypes.Role,"admin")
                };
            }
        }
    

      

    IdentityServer提供了接口访问用户信息,但是默认返回的数据只有sub,就是上面设置的subject: context.UserName,要返回更多的信息,需要实现IProfileService接口:

    public class ProfileService : IProfileService
        {
            public async Task GetProfileDataAsync(ProfileDataRequestContext context)
            {
                try
                {
                    //depending on the scope accessing the user data.
                    var claims = context.Subject.Claims.ToList();
    
                    //set issued claims to return
                    context.IssuedClaims = claims.ToList();
                }
                catch (Exception ex)
                {
                    //log your error
                }
            }
    
            public async Task IsActiveAsync(IsActiveContext context)
            {
                context.IsActive = true;
            }
    

      

    context.Subject.Claims就是之前实现IResourceOwnerPasswordValidator接口时claims: GetUserClaims()给到的数据。
    另外,经过调试发现,显示执行ResourceOwnerPasswordValidator 里的ValidateAsync,然后执行ProfileService 里的IsActiveAsync,GetProfileDataAsync。

    启动项目,使用postman进行请求就可以获取到token:

    再用token获取相应的用户信息:

    token认证服务一般是与web程序分开的,上面创建的QuickstartIdentityServer项目就相当于服务端,我们需要写业务逻辑的web程序就相当于客户端。当用户请求web程序的时候,web程序拿着用户已经登录取得的token去IdentityServer服务端校验。

    创建web应用

    创建一个名为API的ASP.NET Core Web 空项目(asp.net core 2.0),端口5001。

    NuGet包:

    修改Startup.cs 设置使用IdentityServer进行校验:

    public class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddMvcCore(option=>
                {
                    option.Filters.Add(new TestAuthorizationFilter());
                }).AddAuthorization()
                    .AddJsonFormatters();
    
                services.AddAuthentication("Bearer")
                    .AddIdentityServerAuthentication(options =>
                    {
                        options.Authority = "http://localhost:5000";
                        options.RequireHttpsMetadata = false;
    
                        options.ApiName = "api1";
                    });
            }
    
            public void Configure(IApplicationBuilder app)
            {
                app.UseAuthentication();
    
                app.UseMvc();
            }
        }
    

      

    创建IdentityController:

    [Route("[controller]")]
        public class IdentityController : ControllerBase
        {
            [HttpGet]
            [Authorize]
            public IActionResult Get()
            {
                return new JsonResult("Hello Word");
            }
    
        }
    

      

    分别运行QuickstartIdentityServer,API项目。用生成的token访问API:

    通过上述程序,已经可以做一个前后端分离的登录功能。

    实际上,web应用程序中我们经常需要获取当前用户的相关信息进行操作,比如记录用户的一些操作日志等。之前说过IdentityServer提供了接口/connect/userinfo来获取用户的相关信息。之前我的想法也是web程序中拿着token去请求这个接口来获取用户信息,并且第一次获取后做相应的缓冲。但是感觉有点怪怪的,IdentityServer不可能没有想到这一点,正常的做法应该是校验通过会将用户的信息返回的web程序中。问题又来了,如果IdentityServer真的是这么做的,web程序该怎么获取到呢,查了官方文档也没有找到。然后就拿着"Claim"关键字查了一通(之前没了解过ASP.NET Identity),最后通过HttpContext.User.Claims取到了设置的用户信息:

    修改IdentityController :

    [Route("[controller]")]
        public class IdentityController : ControllerBase
        {
            [HttpGet]
            [Authorize]
            public IActionResult Get()
            {
                return new JsonResult(from c in HttpContext.User.Claims select new { c.Type, c.Value });
            }
    
        }
    

      



    权限控制

    IdentityServer4 也提供了权限管理的功能,大概看了一眼,没有达到我想要(没耐心去研究)。
    我需要的是针对不同的模块,功能定义权限码(字符串),每个权限码对应相应的功能权限。当用户进行请求的时候,判断用户是否具备相应功能的权限(是否赋予了相应的权限字符串编码),来达到权限控制。

    IdentityServer的校验是通过Authorize特性来判断相应的Controller或Action是否需要校验。这里也通过自定义特性来实现权限的校验,并且是在原有的Authorize特性上进行扩展。可行的方案继承AuthorizeAttribute,重写。可是在.net core中提示没有OnAuthorization方法可进行重写。最后参考的ABP的做法,过滤器和特性共同使用。

    新建TestAuthorizationFilter.cs

    public class TestAuthorizationFilter : IAuthorizationFilter
        {
            public void OnAuthorization(AuthorizationFilterContext context)
            {
                if (context.Filters.Any(item => item is IAllowAnonymousFilter))
                {
                    return;
                }
    
                if (!(context.ActionDescriptor is ControllerActionDescriptor))
                {
                    return;
                }
                var attributeList = new List<object>();
                attributeList.AddRange((context.ActionDescriptor as ControllerActionDescriptor).MethodInfo.GetCustomAttributes(true));
                attributeList.AddRange((context.ActionDescriptor as ControllerActionDescriptor).MethodInfo.DeclaringType.GetCustomAttributes(true));
                var authorizeAttributes = attributeList.OfType<TestAuthorizeAttribute>().ToList();
                var claims = context.HttpContext.User.Claims;
                // 从claims取出用户相关信息,到数据库中取得用户具备的权限码,与当前Controller或Action标识的权限码做比较
                var userPermissions = "User_Edit";
                if (!authorizeAttributes.Any(s => s.Permission.Equals(userPermissions)))
                {
                    context.Result = new JsonResult("没有权限");
                }
                return;
    
            }
        }
    

      

    新建TestAuthorizeAttribute

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
        public class TestAuthorizeAttribute: AuthorizeAttribute
        {
            
            public string Permission { get; set; }
    
            public TestAuthorizeAttribute(string permission)
            {
                Permission = permission;
            }
    
        }
    

      

    将IdentityController [Authorize]改为[TestAuthorize("User_Edit")],再运行API项目。

    通过修改权限码,验证是否起作用

    除了使用过滤器和特性结合使用,貌似还有别的方法,有空再研究。

    本文中的源码

  • 相关阅读:
    题解 P3960 【列队】
    题解 P3825 【[NOI2017]游戏】
    题解 P3385 【【模板】负环】
    luoguP1555 尴尬的数字(暴力+map)
    luogu P1775 古代人的难题_NOI导刊2010提高(02)(斐波纳契+数学)
    luogu P1405 苦恼的小明(欧拉定理)
    luogu P2117 小Z的矩阵(结论题)
    BZOJ2870 最长道路tree(并查集+LCA)
    BZOJ 4668 冷战(按秩合并并查集+LCA)
    BZOJ 3376 [Usaco2004 Open]Cube Stacking 方块游戏(带权并查集)
  • 原文地址:https://www.cnblogs.com/zhurunlai/p/10564508.html
Copyright © 2011-2022 走看看