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

通過修改權限碼,驗證是否起作用

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

本文中的源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM