相關知識點
不再對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項目角雷。
通過修改權限碼,驗證是否起作用
除了使用過濾器和特性結合使用性穿,貌似還有別的方法勺三,有空再研究。
本文中的源碼