Related knowledge points

No longer covering IdentityServer4, there are a series of posts on The blog for those who don’t know:

Crickets: Side dish Learning programming -IdentityServer4

Master: IdentityServer4

Identity,Claim and other relevant knowledge:

Savorboard: ASP.NET Core Identity Introduction (part 1)

Create the IndentityServer4 service

Create an ASP.NET Core Web empty project named QuickstartIdentityServer (ASP.NET Core 2.0) with port 5000


NuGet package:


Modify startup. cs Settings to use 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(); }}Copy the code

Add config. cs Config to IdentityResource, ApiResource, and Client:

public class Config { public static IEnumerable<IdentityResource> GetIdentityResourceResources() { return new A List < IdentityResource > {new IdentityResources. OpenId (), / / have to add, otherwise an error report invalid 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, must want to add / /, Otherwise the forbidden error 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, must want to add / /, Otherwise the forbidden error IdentityServerConstants. StandardScopes. Profile}}}; }}Copy the code

Because when use to login to use the data stored in user verification, to real IResourceOwnerPasswordValidator interface:

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator { public ResourceOwnerPasswordValidator() { } public async Task ValidateAsync (ResourceOwnerPasswordValidationContext context) {/ / according to the context. The UserName and the context. The Password and the database data check, If (context.UserName==" WJK "&&context.Password=="123") {context.Result = new GrantValidationResult(subject: context.UserName, authenticationMethod: "custom", claims: GetUserClaims()); } else {/ / validation failure context. The Result = new GrantValidationResult (TokenRequestErrors InvalidGrant, "invalid custom credential"); Private Claim[] getUserClaim () {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, "[email protected]"), new Claim(JwtClaimTypes.Role,"admin") }; }}Copy the code

IProfileService = IProfileService = IProfileService = IProfileService = 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;
        }Copy the code

Context. Subject. Claims is implemented before IResourceOwnerPasswordValidator interface Claims: GetUserClaims () to the data. In addition, after debugging found that perform ValidateAsync ResourceOwnerPasswordValidator, then execute the ProfileService IsActiveAsync, GetProfileDataAsync.

Start the project and make a request using Postman to get the token:

Then use token to obtain the corresponding user information:

Token authentication services are usually separated from Web applications. The QuickstartIdentityServer project created above is the server, and the Web application that needs to write the business logic is the client. When a user requests a web application, the web application uses the token that the user has logged in to obtain to verify the IdentityServer.

Creating a Web Application

Create an empty ASP.NET Core Web project called API (ASP.NET Core 2.0) with port 5001.

NuGet package:

Modify startup. cs to use IdentityServer for authentication:

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(); }}Copy the code

Create IdentityController:

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

Run the QuickstartIdentityServer and API projects respectively. Access the API with the generated token:


Through the above program, it is possible to do a separate login function.

In fact, in web applications, we often need to retrieve information about the current user to perform operations, such as logging some user operations. As mentioned earlier, IdentityServer provides the /connect/ userInfo interface to retrieve user information. My previous idea was that the Web program would request this interface with token to obtain user information, and make corresponding buffer after the first acquisition. But it feels a little strange. IdentityServer can’t have failed to think of this, and the normal thing to do is to verify that it passes through a Web application that returns the user’s information. Again, if this is what IdentityServer does, how do web applications get it? A search of official documentation failed to find it. Net Identity (httpContext.user. Claims); httpContext.user. Claims;

Modify 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 }); }}Copy the code


Access control

IdentityServer4 also offers permission management capabilities, which at first glance didn’t do what I wanted (and didn’t have the patience to explore). What I need is the function definition permission code (string) for different modules, each permission code corresponds to the corresponding function permission. When the user makes a request, judge whether the user has the permission of the corresponding function (whether the corresponding permission string encoding is given) to achieve the permission control.

The verification of IdentityServer is based on the Authorize feature to determine whether the Controller or Action needs verification. Here also through the custom features to achieve authority verification, and is extended on the original Authorize feature. The feasible scheme inherits AuthorizeAttribute and overwrites it. However,.net Core indicates that no OnAuthorization method can be overridden. Finally reference ABP practices, filters and features used together.

New 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; Var userPermissions = "User_Edit"; var userPermissions = "User_Edit"; if (! AuthorizeAttributes.Any(s => s.permission.equals (userPermissions))) {context.result = new JsonResult(" no permissions "); } return; }}Copy the code

New TestAuthorizeAttribute

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class TestAuthorizeAttribute: AuthorizeAttribute { public string Permission { get; set; } public TestAuthorizeAttribute(string permission) { Permission = permission; }}Copy the code

Change IdentityController [Authorize] to [TestAuthorize(” User_Edit “)] and run the API project.

You can modify the permission code to verify the validity

There seems to be an alternative to using filters and features in combination, but we’ll look into that later.

Source code in this article