Full entry record of. Net core microservices (7) — identityserver4 authorization and authentication

Time:2020-10-25

Tips: This article has been added to the reading list of series articles. You can click to see more related articles.

preface

In the last article, we used cap to complete a simple eventbus, which realized the decoupling and asynchronous call between services, and achieved the final consistency of data. This article will use identity server 4 to build an authentication center to complete the functions related to authorization and authentication.

Official document of identityserver4: https://identityserver4.readthedocs.io/

Authentication center

Create an ids4 project

For the basic introduction and template installation of identityserver4, please take a look at my other blog [the correct posture for configuring scope in identityserver4 4. X version], starting from creating a project.

Come to my project directory to execute:dotnet new is4inmem --name IDS4.AuthCenter

image-20200629210341489

After execution, the following files are generated:

image-20200629210446718

Open the previous solution with vs2019 and add the IDS project you just created:

image-20200629210933318

Set this project as the startup item and run it to see the effect:

image-20200629211848802

image-20200629212102283

The project is running normally. Now we need to modify the default code according to our business.

Authentication center configuration

Modify startup’s configureservices method:

// in-memory, code config
builder.AddInMemoryIdentityResources(Config.IdentityResources);
builder.AddInMemoryApiScopes(Config.ApiScopes);
builder.AddInMemoryApiResources(Config.ApiResources);
builder.AddInMemoryClients(Config.Clients);

Config class:

public static class Config
{
    public static IEnumerable IdentityResources =>
        new IdentityResource[]
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
        };

    public static IEnumerable ApiResources =>
        new ApiResource[]
        {
            New apiresource ("orderapi", "order service")
            {
                ApiSecrets ={ new Secret("orderApi secret".Sha256()) },
                Scopes = { "orderApiScope" }
            },
            New apiresource ("productapi", "product service")
            {
                ApiSecrets ={ new Secret("productApi secret".Sha256()) },
                Scopes = { "productApiScope" }
            }
        };

    public static IEnumerable ApiScopes =>
        new ApiScope[]
        {
            new ApiScope("orderApiScope"),
            new ApiScope("productApiScope"),
        };

    public static IEnumerable Clients =>
        new Client[]
        {
            new Client
            {
                ClientId = "web client",
                ClientName = "Web Client",

                AllowedGrantTypes = GrantTypes.Code,
                ClientSecrets = { new Secret("web client secret".Sha256()) },

                RedirectUris = { "http://localhost:5000/signin-oidc" },
                FrontChannelLogoutUri = "http://localhost:5000/signout-oidc",
                PostLogoutRedirectUris = { "http://localhost:5000/signout-callback-oidc" },

                AllowedScopes = new [] {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "orderApiScope", "productApiScope"
                },
                AllowAccessTokensViaBrowser = true,

                Requireconsent = true, // whether to display the consent interface
                Allowremberconsent = false, // do you want to remember the consent option
            }
        };
}

Two API resources are defined in config: orderapi and productapi. Two scopes: orderapiscope and productapiscope. One client: Web client, using code authorization code mode, with four scopes of openid, profile, orderapiscope and productapiscope.

Testusers class:

public class TestUsers
{
    public static List Users
    {
        get
        {
            var address = new
            {
                street_address = "One Hacker Way",
                locality = "Heidelberg",
                postal_code = 69118,
                country = "Germany"
            };
            
            return new List
            {
                new TestUser
                {
                    SubjectId = "818727",
                    Username = "alice",
                    Password = "alice",
                    Claims =
                    {
                        new Claim(JwtClaimTypes.Name, "Alice Smith"),
                        new Claim(JwtClaimTypes.GivenName, "Alice"),
                        new Claim(JwtClaimTypes.FamilyName, "Smith"),
                        new Claim(JwtClaimTypes.Email, "[email protected]"),
                        new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                        new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
                        new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address), IdentityServerConstants.ClaimValueTypes.Json)
                    }
                },
                new TestUser
                {
                    SubjectId = "88421113",
                    Username = "bob",
                    Password = "bob",
                    Claims =
                    {
                        new Claim(JwtClaimTypes.Name, "Bob Smith"),
                        new Claim(JwtClaimTypes.GivenName, "Bob"),
                        new Claim(JwtClaimTypes.FamilyName, "Smith"),
                        new Claim(JwtClaimTypes.Email, "[email protected]"),
                        new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                        new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
                        new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address), IdentityServerConstants.ClaimValueTypes.Json)
                    }
                }
            };
        }
    }
}

Testusers has not been modified, just use the default generated project template. There are two users, Alice and Bob, whose passwords are the same as the user name.

At this point, the code modification of the authentication center is almost the same. This project also does not put docker, directly use VS to start, let him run on port 9080. /Properties/ launchSettings.json Revise it as follows:"applicationUrl": "http://localhost:9080"

Ocelot integrated with ids4

Ocelot protects API resources

The authentication center is completed, and the following is integrated into the previous one Ocelot.APIGateway Gateway project.

First, nuget installationIdentityServer4.AccessTokenValidation

image-20200706100658900

Modify startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
        .AddIdentityServerAuthentication("orderService", options =>
        {
            options.Authority  = " http://localhost : 9080 "; // authentication center address
            options.ApiName = "orderApi";
            options.SupportedTokens = SupportedTokens.Both;
            options.ApiSecret = "orderApi secret";
            options.RequireHttpsMetadata = false;
        })
        .AddIdentityServerAuthentication("productService", options =>
        {
            options.Authority  = " http://localhost : 9080 "; // authentication center address
            options.ApiName = "productApi";
            options.SupportedTokens = SupportedTokens.Both;
            options.ApiSecret = "productApi secret";
            options.RequireHttpsMetadata = false;
        });

    //Add Ocelot service
    services.AddOcelot()
        //Add consult support
        .AddConsul()
        //Add cache
        .AddCacheManager(x =>
        {
            x.WithDictionaryHandle();
        })
        //Add Polly
        .AddPolly();
}

modify ocelot.json Configuration file:

{
  "DownstreamPathTemplate": "/products",
  "DownstreamScheme": "http",
  "UpstreamPathTemplate": "/products",
  "UpstreamHttpMethod": [ "Get" ],
  "ServiceName": "ProductService",
  ......
  "AuthenticationOptions": {
    "AuthenticationProviderKey": "productService",
    "AllowScopes": []
  }
},
{
  "DownstreamPathTemplate": "/orders",
  "DownstreamScheme": "http",
  "UpstreamPathTemplate": "/orders",
  "UpstreamHttpMethod": [ "Get" ],
  "ServiceName": "OrderService",
  ......
  "AuthenticationOptions": {
    "AuthenticationProviderKey": "orderService",
    "AllowScopes": []
  }
}

The authenticationoptions node is added, and the authenticationproviderkey corresponds to the definition in startup above.

Ocelot proxy ids4

Since the gateway is the unified entrance of the client access API, it can also be used as the entrance of the authentication center. Use Ocelot as the proxy, so that the client does not need to know the address of the authentication center, and also modify it ocelot.json :

{
  "DownstreamPathTemplate": "/{url}",
  "DownstreamScheme": "http",
  "DownstreamHostAndPorts": [
    {
      "Host": "localhost",
      "Port": 9080
    }
  ],
  "UpstreamPathTemplate": "/auth/{url}",
  "UpstreamHttpMethod": [
    "Get",
    "Post"
  ],
  "LoadBalancerOptions": {
    "Type": "RoundRobin"
  }
}

Add an authentication center route. In practice, the authentication center can also deploy multiple instances and integrate the consult service discovery. The implementation method is similar to the previous chapter, so I won’t repeat it here.

Let the gateway service run on port 9070, / properties/ launchSettings.json Revise it as follows:"applicationUrl": "http://localhost:9070"

Client integration

First, nuget installationMicrosoft.AspNetCore.Authentication.OpenIdConnect

image-20200706121544645

Modify startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.Authority  = " http://localhost : 9070 / auth "; // access authentication center through gateway
            //options.Authority = "http://localhost:9080";

            options.ClientId = "web client";
            options.ClientSecret = "web client secret";
            options.ResponseType = "code";

            options.RequireHttpsMetadata = false;

            options.SaveTokens = true;

            options.Scope.Add("orderApiScope");
            options.Scope.Add("productApiScope");
        });

    services.AddControllersWithViews();
    
    //Inject iservicehelper
    //services.AddSingleton();
    
    //Inject iservicehelper
    services.AddSingleton();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceHelper serviceHelper)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });

    //Get the list of services when the program starts
    //serviceHelper.GetServices();
}

Modify / Helper / iservicehelper, add accesstoken parameter to method definition:

/// 
///Get product data
/// 
/// 
/// 
Task GetProduct(string accessToken);

/// 
///Get order data
/// 
/// 
/// 
Task GetOrder(string accessToken);

Modify / Helper / gatewayservicehelper, add the authorization parameter when accessing the interface, and pass in the accesstoken:

public async Task GetOrder(string accessToken)
{
    var Client = new RestClient("http://localhost:9070");
    var request = new RestRequest("/orders", Method.GET);
    request.AddHeader("Authorization", "Bearer " + accessToken);

    var response = await Client.ExecuteAsync(request);
    if (response.StatusCode != HttpStatusCode.OK)
    {
        return response.StatusCode + " " + response.Content;
    }
    return response.Content;
}

public async Task GetProduct(string accessToken)
{
    var Client = new RestClient("http://localhost:9070");
    var request = new RestRequest("/products", Method.GET);
    request.AddHeader("Authorization", "Bearer " + accessToken);

    var response = await Client.ExecuteAsync(request);
    if (response.StatusCode != HttpStatusCode.OK)
    {
        return response.StatusCode + " " + response.Content;
    }
    return response.Content;
}

Finally, the modification of / Controllers / homecontroller. Add the authorize tag:

[Authorize]
public class HomeController : Controller

Modify the index action, get the accesstoken and pass in:

public async Task Index()
{
    var accessToken = await HttpContext.GetTokenAsync("access_token");

    ViewBag.OrderData = await _serviceHelper.GetOrder(accessToken);
    ViewBag.ProductData = await _serviceHelper.GetProduct(accessToken);

    return View();
}

So far, the client integration has been completed.

test

For convenience, the authentication center, gateway and web client all use VS to start, and their ports are 908090705000. The previous order API and product API remain unchanged in docker.

To enable vs to start multiple projects at the same time, you need to set the right-click property of the solution:

image-20200706123144511

Ctor + F5 started the project.

After the three projects are started, the browser accesses the web client: http://localhost :5000/

image-20200706124027549

Because I haven’t logged in yet, the request is directed to the login interface of the authentication center. Log in to the system with Alice / Alice.

image-20200706124523974

After successful login, you can enter the authorization permission interface. You can agree or reject, or check the scope permission. Click Yes and allow to approve the authorization:

image-20200706124924213

After you agree to the authorization, you can access the client interface normally. Next test some authorization, here do not do the logout function, can only manually clear the browser cookie, ids4 login function is also very simple, you can baidu.

image-20200706125257382

After clearing the cookie, the refresh page will go to the login interface of ids4. This time, log in using Bob / Bob:

image-20200706125759968

This time, just check the order API scope and click Yes, allow:

image-20200706130140730

This time, the client can only access the order service. Of course, you can also limit the API permissions of clients in the authentication center, or at the gateway level ocelot.json I believe you already know what to do.

summary

This paper mainly completes the integration of identity server 4 authentication center, Ocelot gateway and web client, and realizes the unified authorization and authentication of the system. Authentication is an essential function of almost every system, and identityserver4 is an excellent authentication scheme under. Net core. Once again, I would like to recommend the video of Mr. Yang from station B, address: https://www.bilibili.com/video/BV16b411k7yM Although the video is a bit old, it is still very useful.

Code required here: https://github.com/xiajingren/NetCoreMicroserviceDemo