本文主要介绍在Asp.net core 中 使用 IdentityServer4 的客户端凭证、客户端凭证+用户+密码两种验证模式。
在个人开发的小型项目中,往往并不需要单独建立IS4授权服务器,而是将 IS4 服务与 要提供客户端访问的API资源放在一个项目中。 此外,本文展示了在IS4验证用户时,如何从数据库中读取用户。
本文在 asp.net core 3.1 + WPF客户端,环境下测试通过。
1、导入程序集
*IS4 服务端* IdentityServer4 IdentityServer4.AspNetIdentity Microsoft.AspNetCore.Authentication.JwtBearer *客户端,只需下面一个* IdentityModel
2、创建IS4配置类IS4Config.cs
/// <summary> /// IdentityServer4的配置类 /// </summary> public class IS4Config { /// <summary> /// 获取IS4自身内置的标准API资源 /// </summary> /// <returns></returns> public static IEnumerable<IdentityResource> GetIdentityResources() { return new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; } /// <summary> /// 获取应用需调用的(用户自己编写的)API资源 /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { return new ApiResource[] { new ApiResource("Apis","My Apis") }; } /// <summary> /// 定义将访问IS4 的客户端 /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { return new Client[] { new Client(){ //定义客户端,支持两种验证模式 ClientId="theClient", AllowedGrantTypes={ GrantType.ClientCredentials, //客户端模式 GrantType.ResourceOwnerPassword //密码模式 }, ClientSecrets={new Secret("theClientPwd".Sha256())}, AllowedScopes={ "Apis", IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.OfflineAccess } }, }; } }
3、创建IS4 用户查询类ResourceOwnerPasswordValidator.cs
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator { private readonly DataUserRepository _UserRepository; //用户数据表操作对象 public ResourceOwnerPasswordValidator(GHDbContext ghDbContet) { _UserRepository = new DataUserRepository(ghDbContet); } public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) { try { var user = await _UserRepository.GetOneByIdPwdAsync(context.UserName, context.Password); //如用户名与密码都正确,User 才不会为 null if (user != null) { context.Result = new GrantValidationResult( subject: user.Id.ToString(), authenticationMethod: "custom", claims: GetUserClaims(user) //视情况,可以不需要此claims ); return; } else { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "无效的用户名或密码"); return; } } catch (Exception ex) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, ex.Message); } } //build claims array from user data public static Claim[] GetUserClaims(ModelUser user) { return new Claim[] { new Claim("user_id", user.Id ?? ""), new Claim(JwtClaimTypes.Name,user.Id ?? ""), new Claim(JwtClaimTypes.Role, user.Post) }; } }
4、修改setup.cs类
public void ConfigureServices(IServiceCollection services) { //注册IS4 服务 var is4Buider = services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(IS4Config.GetApiResources()) //IS4 导入定义的应用资源API .AddInMemoryIdentityResources(IS4Config.GetIdentityResources()) //IS4 自身API .AddInMemoryClients(IS4Config.GetClients()) //IS4 导入定义的客户端 .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>();//IS4 从数据表中验证用户类 //注册验证(*用于保护API资源,与IS4无关* ) services.AddAuthentication("Bearer").AddJwtBearer(r => { //认证服务地址 r.Authority = "http://localhost:5000"; //权限标识 r.Audience = "Apis"; //是否必需HTTPS r.RequireHttpsMetadata = false; }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseRouting(); //使用 IS4 中间件。 app.UseIdentityServer(); //添加验证(*用于保护API资源,与IS4无关* ) app.UseAuthentication(); //注意:验证中间件必须放在授权之前。** //添加授权(*用于保护API资源,与IS4无关* ) app.UseAuthorization();// //上两行要放在app.UseRouting、app.UseCors("cors")之后,并且在app.UseEndpoints之前 app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
5、欲进行验证的客户端代码(比如 WPF客户端、控制台应用等)
/// <summary> /// 获取AccessToaken /// </summary> /// <returns></returns> public static async Task GetAccessToken(string P_UserName = null, string P_Pwd = null) { IS4_AccessToken = ""; HttpClient _Client = new HttpClient(); var disco = await _Client.GetDiscoveryDocumentAsync("http://localhost:5000"); //发现 IS4 各类功能所在的网址和其他相关信息 if (disco.IsError) { Console.WriteLine(disco.Error); return; } ////ClientCredentials 客户端凭据许可 //var tokenResponse = await _Client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest //{ // Address = disco.TokenEndpoint, //令牌端点 = http://localhost:5000/connect/token // ClientId = "WorkPlatformClient", //id // ClientSecret = "Work_Platform_ClientPwd", //pwd // Scope = "Apis", //请求的api //}); //ClientPassword 客户端加用户密码模式 var tokenResponse = await _Client.RequestPasswordTokenAsync(new PasswordTokenRequest { Address = disco.TokenEndpoint, ClientId = "theClient", ClientSecret = "theClientPwd", Scope = "Apis", //以下为用户与密码 UserName = P_UserName, Password = P_Pwd }); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); return; } //JObject TokenJsonObj = tokenResponse.Json; if (tokenResponse.AccessToken != null) { IS4_AccessToken = tokenResponse.AccessToken; } //// 调用 指定的API //var Apiclient = new HttpClient(); //Apiclient.SetBearerToken(tokenResponse.AccessToken); //var response = await Apiclient.GetAsync("http://localhost:5001/identity"); //if (!response.IsSuccessStatusCode) //{ // Console.WriteLine(response.StatusCode); //} //else //{ // var content = await response.Content.ReadAsStringAsync(); // Console.WriteLine(JArray.Parse(content)); //} }