IdentityServer4實現(xiàn)Token認證登錄以及權限控制

相關知識點

不再對IdentityServer4做相關介紹譬正,博客園上已經(jīng)有人出了相關的系列文章绢陌,不了解的可以看一下:

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

曉晨Master:IdentityServer4

以及Identity,Claim等相關知識:

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

創(chuàng)建IndentityServer4 服務

創(chuàng)建一個名為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 }
                }
            };
        }
    }

因為要使用登錄的時候要使用數(shù)據(jù)中保存的用戶進行驗證,要實IResourceOwnerPasswordValidator接口:

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
    {
        public ResourceOwnerPasswordValidator()
        {
            
        }

        public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
        {
            //根據(jù)context.UserName和context.Password與數(shù)據(jù)庫的數(shù)據(jù)做校驗澈驼,判斷是否合法
            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");
            }
        }
        //可以根據(jù)需要設置相應的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提供了接口訪問用戶信息辛燥,但是默認返回的數(shù)據(jù)只有sub,就是上面設置的subject: context.UserName,要返回更多的信息挎塌,需要實現(xiàn)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就是之前實現(xiàn)IResourceOwnerPasswordValidator接口時claims: GetUserClaims()給到的數(shù)據(jù)徘六。
另外,經(jīng)過調試發(fā)現(xiàn)榴都,顯示執(zhí)行ResourceOwnerPasswordValidator 里的ValidateAsync待锈,然后執(zhí)行ProfileService 里的IsActiveAsync,GetProfileDataAsync缭贡。

啟動項目炉擅,使用postman進行請求就可以獲取到token:

再用token獲取相應的用戶信息:

token認證服務一般是與web程序分開的,上面創(chuàng)建的QuickstartIdentityServer項目就相當于服務端阳惹,我們需要寫業(yè)務邏輯的web程序就相當于客戶端。當用戶請求web程序的時候眶俩,web程序拿著用戶已經(jīng)登錄取得的token去IdentityServer服務端校驗莹汤。

創(chuàng)建web應用

創(chuàng)建一個名為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();
        }
    }

創(chuàng)建IdentityController:

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

    }

分別運行QuickstartIdentityServer纲岭,API項目。用生成的token訪問API:

通過上述程序线罕,已經(jīng)可以做一個前后端分離的登錄功能止潮。

實際上,web應用程序中我們經(jīng)常需要獲取當前用戶的相關信息進行操作钞楼,比如記錄用戶的一些操作日志等喇闸。之前說過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是否需要校驗玻墅。這里也通過自定義特性來實現(xiàn)權限的校驗介牙,并且是在原有的Authorize特性上進行擴展“南幔可行的方案繼承AuthorizeAttribute环础,重寫∈B#可是在.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取出用戶相關信息贯钩,到數(shù)據(jù)庫中取得用戶具備的權限碼,與當前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項目角雷。

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

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

本文中的源碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末需曾,一起剝皮案震驚了整個濱河市吗坚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胯舷,老刑警劉巖刻蚯,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異桑嘶,居然都是意外死亡炊汹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門逃顶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讨便,“玉大人,你說我怎么就攤上這事以政“园” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵盈蛮,是天一觀的道長废菱。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么殊轴? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任衰倦,我火速辦了婚禮,結果婚禮上旁理,老公的妹妹穿的比我還像新娘樊零。我一直安慰自己,他們只是感情好孽文,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布驻襟。 她就那樣靜靜地躺著,像睡著了一般芋哭。 火紅的嫁衣襯著肌膚如雪沉衣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天楷掉,我揣著相機與錄音厢蒜,去河邊找鬼。 笑死烹植,一個胖子當著我的面吹牛,可吹牛的內容都是我干的愕贡。 我是一名探鬼主播草雕,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼固以!你這毒婦竟也來了墩虹?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤憨琳,失蹤者是張志新(化名)和其女友劉穎诫钓,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體篙螟,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡菌湃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了遍略。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惧所。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绪杏,靈堂內的尸體忽然破棺而出下愈,到底是詐尸還是另有隱情,我是刑警寧澤蕾久,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布势似,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏履因。R本人自食惡果不足惜障簿,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搓逾。 院中可真熱鬧卷谈,春花似錦、人聲如沸霞篡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朗兵。三九已至污淋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間余掖,已是汗流浹背寸爆。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盐欺,地道東北人赁豆。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像冗美,于是被迫代替她去往敵國和親魔种。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內容