How to integrate custom authentication provider in

2020-03-01 02:38发布

问题:

Is it possible to somehow extend IdentityServer4 to run custom authentication logic? I have the requirement to validate credentials against a couple of existing custom identity systems and struggle to find an extension point to do so (they use custom protocols). All of these existing systems have the concept on an API key which the client side knows. The IdentityServer job should now be to validate this API key and also extract some existing claims from the system. I imagine to do something like this:

POST /connect/token
    custom_provider_name=my_custom_provider_1&
    custom_provider_api_key=secret_api_key

Then I do my logic to call my_custom_provider_1, validate the API key, get the claims and pass them back to the IdentityServer flow to do the rest.

Is this possible?

回答1:

I'm assuming you have control over the clients, and the requests they make, so you can make the appropriate calls to your Identity Server.

It is possible to use custom authentication logic, after all that is what the ResourceOwnerPassword flow is all about: the client passes information to the Connect/token endpoint and you write code to decide what that information means and decide whether this is enough to authenticate that client. You'll definitely be going off the beaten track to do what you want though, because convention says that the information the client passes is a username and a password.

In your Startup.ConfigureServices you will need to add your own implementation of an IResourceOwnerPasswordValidator, kind of like this:

services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();

Then in the ValidateAsync method of that class you can do whatever logic you like to decide whether to set the context.Result to a successful GrantValidationResult, or a failed one. One thing that can help you in that method, is that the ResourceOwnerPasswordValidationContext has access to the raw request. So any custom fields you add into the original call to the connect/token endpoint will be available to you. This is where you could add your custom fields (provider name, api key etc).

Good luck!

EDIT: The above could work, but is really abusing a standard grant/flow. Much better is the approach found by the OP to use the IExtensionGrantValidator interface to roll your own grant type and authentication logic. For example:

Call from client to identity server:

POST /connect/token
grant_type=my_crap_grant&
scope=my_desired_scope&
rhubarb=true&
custard=true&
music=ska

Register your extension grant with DI:

services.AddTransient<IExtensionGrantValidator, MyCrapGrantValidator>();

And implement your grant validator:

public class MyCrapGrantValidator : IExtensionGrantValidator
{
    // your custom grant needs a name, used in the Post to /connect/token
    public string GrantType => "my_crap_grant";

    public async Task ValidateAsync(ExtensionGrantValidationContext context)
    {
        // Get the values for the data you expect to be used for your custom grant type
        var rhubarb = context.Request.Raw.Get("rhubarb");
        var custard = context.Request.Raw.Get("custard");
        var music = context.Request.Raw.Get("music");

        if (string.IsNullOrWhiteSpace(rhubarb)||string.IsNullOrWhiteSpace(custard)||string.IsNullOrWhiteSpace(music)
        {
            // this request doesn't have the data we'd expect for our grant type
            context.Result = new     GrantValidationResult(TokenRequestErrors.InvalidGrant);
            return Task.FromResult(false);
        }

        // Do your logic to work out, based on the data provided, whether 
        // this request is valid or not
        if (bool.Parse(rhubarb) && bool.Parse(custard) && music=="ska")
        {
            // This grant gives access to any client that simply makes a 
            // request with rhubarb and custard both true, and has music 
            // equal to ska. You should do better and involve databases and 
            // other technical things
            var sub = "ThisIsNotGoodSub";
            context.Result = new GrantValidationResult(sub,"my_crap_grant");
            Task.FromResult(0);
        }

        // Otherwise they're unauthorised
        context.Result = new GrantValidationResult(TokenRequestErrors.UnauthorizedClient);
        return Task.FromResult(false);
    }
}