IdentityServer4實現Token認證登陸以及權限控制

相關知識點

再也不對IdentityServer4作相關介紹,博客園上已經有人出了相關的系列文章,不瞭解的能夠看一下:html

蟋蟀大神的:小菜學習編程-IdentityServer4git

曉晨Master:IdentityServer4github

以及Identity,Claim等相關知識:web

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:api

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:app

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接口:asp.net

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項目。

經過修改權限碼,驗證是否起做用

除了使用過濾器和特性結合使用,貌似還有別的方法,有空再研究。

本文中的源碼

相關文章
相關標籤/搜索