当前位置: 移动技术网 > IT编程>开发语言>.net > 【.NET Core项目实战-统一认证平台】第十四章 授权篇-自定义授权方式

【.NET Core项目实战-统一认证平台】第十四章 授权篇-自定义授权方式

2019年01月29日  | 移动技术网IT编程  | 我要评论

枪毙女死刑犯图片,滨江道二姐,激光雕刻机

【.net core项目实战-统一认证平台】开篇及目录索引

上篇文章我介绍了如何强制令牌过期的实现,相信大家对identityserver4的验证流程有了更深的了解,本篇我将介绍如何使用自定义的授权方式集成老的业务系统验证,然后根据不同的客户端使用不同的认证方式来集成到统一认证平台。

.netcore项目实战交流群(637326624),有兴趣的朋友可以在群里交流讨论。

一、自定授权源码剖析

当我们需要使用开源项目的某些功能时,最好了解实现的原理,才能正确和熟练使用功能,避免出现各种未知bug问题和出现问题无法解决的被动场面。

在使用此功能前,我们需要了解完整的实现流程,下面我将从源码开始讲解identityserver4是如何实现自定义的授权方式。

从我之前的文章中我们知道授权方式是通过grant_type的值来判断的,所以我们自定义的授权方式,也是通过此值来区分,所以需要了解自定义的值处理流程。tokenrequestvalidator是请求验证的方法,除了常规验证外,还增加了自定义的验证方式。

public async task<tokenrequestvalidationresult> validaterequestasync(namevaluecollection parameters, clientsecretvalidationresult clientvalidationresult)
{
    _logger.logdebug("start token request validation");

    _validatedrequest = new validatedtokenrequest
    {
        raw = parameters ?? throw new argumentnullexception(nameof(parameters)),
        options = _options
    };

    if (clientvalidationresult == null) throw new argumentnullexception(nameof(clientvalidationresult));

    _validatedrequest.setclient(clientvalidationresult.client, clientvalidationresult.secret, clientvalidationresult.confirmation);

    /////////////////////////////////////////////
    // check client protocol type
    /////////////////////////////////////////////
    if (_validatedrequest.client.protocoltype != identityserverconstants.protocoltypes.openidconnect)
    {
        logerror("client {clientid} has invalid protocol type for token endpoint: expected {expectedprotocoltype} but found {protocoltype}",
                 _validatedrequest.client.clientid,
                 identityserverconstants.protocoltypes.openidconnect,
                 _validatedrequest.client.protocoltype);
        return invalid(oidcconstants.tokenerrors.invalidclient);
    }

    /////////////////////////////////////////////
    // check grant type
    /////////////////////////////////////////////
    var granttype = parameters.get(oidcconstants.tokenrequest.granttype);
    if (granttype.ismissing())
    {
        logerror("grant type is missing");
        return invalid(oidcconstants.tokenerrors.unsupportedgranttype);
    }

    if (granttype.length > _options.inputlengthrestrictions.granttype)
    {
        logerror("grant type is too long");
        return invalid(oidcconstants.tokenerrors.unsupportedgranttype);
    }

    _validatedrequest.granttype = granttype;

    switch (granttype)
    {
        case oidcconstants.granttypes.authorizationcode:
            return await runvalidationasync(validateauthorizationcoderequestasync, parameters);
        case oidcconstants.granttypes.clientcredentials:
            return await runvalidationasync(validateclientcredentialsrequestasync, parameters);
        case oidcconstants.granttypes.password:
            return await runvalidationasync(validateresourceownercredentialrequestasync, parameters);
        case oidcconstants.granttypes.refreshtoken:
            return await runvalidationasync(validaterefreshtokenrequestasync, parameters);
        default://统一的自定义的验证方式
            return await runvalidationasync(validateextensiongrantrequestasync, parameters);
    }
}

从上面代码可以看出,除了内置的授权方式,其他的都是用validateextensiongrantrequestasync来进行验证,详细的验证规则继续分析实现过程。

private async task<tokenrequestvalidationresult> validateextensiongrantrequestasync(namevaluecollection parameters)
{
    _logger.logdebug("start validation of custom grant token request");

    /////////////////////////////////////////////
    // 校验客户端是否开启了此授权方式
    /////////////////////////////////////////////
    if (!_validatedrequest.client.allowedgranttypes.contains(_validatedrequest.granttype))
    {
        logerror("{clientid} does not have the custom grant type in the allowed list, therefore requested grant is not allowed", _validatedrequest.client.clientid);
        return invalid(oidcconstants.tokenerrors.unsupportedgranttype);
    }

    /////////////////////////////////////////////
    // 判断是否注入了此自定义的授权实现
    /////////////////////////////////////////////
    if (!_extensiongrantvalidator.getavailablegranttypes().contains(_validatedrequest.granttype, stringcomparer.ordinal))
    {
        logerror("no validator is registered for the grant type: {granttype}", _validatedrequest.granttype);
        return invalid(oidcconstants.tokenerrors.unsupportedgranttype);
    }

    /////////////////////////////////////////////
    // 校验是否支持scope
    /////////////////////////////////////////////
    if (!await validaterequestedscopesasync(parameters))
    {
        return invalid(oidcconstants.tokenerrors.invalidscope);
    }

    /////////////////////////////////////////////
    // 调用自定义的验证实现方法
    /////////////////////////////////////////////
    var result = await _extensiongrantvalidator.validateasync(_validatedrequest);

    if (result == null)
    {
        logerror("invalid extension grant");
        return invalid(oidcconstants.tokenerrors.invalidgrant);
    }

    if (result.iserror)
    {
        if (result.error.ispresent())
        {
            logerror("invalid extension grant: {error}", result.error);
            return invalid(result.error, result.errordescription, result.customresponse);
        }
        else
        {
            logerror("invalid extension grant");
            return invalid(oidcconstants.tokenerrors.invalidgrant, customresponse: result.customresponse);
        }
    }

    if (result.subject != null)
    {
        /////////////////////////////////////////////
        // 判断当前的用户是否可用
        /////////////////////////////////////////////
        var isactivectx = new isactivecontext(
            result.subject,
            _validatedrequest.client,
            identityserverconstants.profileisactivecallers.extensiongrantvalidation);

        await _profile.isactiveasync(isactivectx);

        if (isactivectx.isactive == false)
        {
            // todo: raise event?

            logerror("user has been disabled: {subjectid}", result.subject.getsubjectid());
            return invalid(oidcconstants.tokenerrors.invalidgrant);
        }

        _validatedrequest.subject = result.subject;
    }

    _logger.logdebug("validation of extension grant token request success");
    return valid(result.customresponse);
}

从代码中可以看出,实现流程如下:

  • 1、客户端是否配置了自定义的授权方式。
  • 2、是否注入了自定义的授权实现。
  • 3、授权的scope客户端是否有权限。
  • 4、使用自定义的授权验证方式校验请求数据是否合法。
  • 5、判断是否有有效数据信息,可自行实现接口。

从源码中,可以发现流程已经非常清晰了,核心类extensiongrantvalidator实现了自定义授权的校验过程,进一步分析下此类的代码实现。

using identityserver4.models;
using microsoft.extensions.logging;
using system;
using system.collections.generic;
using system.linq;
using system.threading.tasks;

namespace identityserver4.validation
{
    /// <summary>
    /// validates an extension grant request using the registered validators
    /// </summary>
    public class extensiongrantvalidator
    {
        private readonly ilogger _logger;
        private readonly ienumerable<iextensiongrantvalidator> _validators;

        /// <summary>
        /// initializes a new instance of the <see cref="extensiongrantvalidator"/> class.
        /// </summary>
        /// <param name="validators">the validators.</param>
        /// <param name="logger">the logger.</param>
        public extensiongrantvalidator(ienumerable<iextensiongrantvalidator> validators, ilogger<extensiongrantvalidator> logger)
        {
            if (validators == null)
            {
                _validators = enumerable.empty<iextensiongrantvalidator>();
            }
            else
            {
                _validators = validators;
            }

            _logger = logger;
        }

        /// <summary>
        /// gets the available grant types.
        /// </summary>
        /// <returns></returns>
        public ienumerable<string> getavailablegranttypes()
        {
            return _validators.select(v => v.granttype);
        }

        /// <summary>
        /// validates the request.
        /// </summary>
        /// <param name="request">the request.</param>
        /// <returns></returns>
        public async task<grantvalidationresult> validateasync(validatedtokenrequest request)
        {
            var validator = _validators.firstordefault(v => v.granttype.equals(request.granttype, stringcomparison.ordinal));

            if (validator == null)
            {
                _logger.logerror("no validator found for grant type");
                return new grantvalidationresult(tokenrequesterrors.unsupportedgranttype);
            }

            try
            {
                _logger.logtrace("calling into custom grant validator: {type}", validator.gettype().fullname);

                var context = new extensiongrantvalidationcontext
                {
                    request = request
                };
            
                await validator.validateasync(context);
                return context.result;
            }
            catch (exception e)
            {
                _logger.logerror(1, e, "grant validation error: {message}", e.message);
                return new grantvalidationresult(tokenrequesterrors.invalidgrant);
            }
        }
    }
}

从上面代码可以发现,自定义授权方式,只需要实现iextensiongrantvalidator接口即可,然后支持多个自定义授权方式的共同使用。

到此整个验证过程解析完毕了,然后再查看下生成token流程,实现方法为tokenresponsegenerator,这个方法并不陌生,前几篇介绍不同的授权方式都介绍了,所以直接看实现代码。

public virtual async task<tokenresponse> processasync(tokenrequestvalidationresult request)
{
    switch (request.validatedrequest.granttype)
    {
        case oidcconstants.granttypes.clientcredentials:
            return await processclientcredentialsrequestasync(request);
        case oidcconstants.granttypes.password:
            return await processpasswordrequestasync(request);
        case oidcconstants.granttypes.authorizationcode:
            return await processauthorizationcoderequestasync(request);
        case oidcconstants.granttypes.refreshtoken:
            return await processrefreshtokenrequestasync(request);
        default://自定义授权生成token的方式
            return await processextensiongrantrequestasync(request);
    }
}

protected virtual task<tokenresponse> processextensiongrantrequestasync(tokenrequestvalidationresult request)
{
    logger.logtrace("creating response for extension grant request");
    return processtokenrequestasync(request);
}

实现的代码方式和客户端模式及密码模式一样,这里就不多介绍了。

最后我们查看下是如何注入iextensiongrantvalidator,是否对外提供接入方式,发现identityserver4提供了addextensiongrantvalidator扩展方法,我们自己实现自定义授权后添加即可,详细实现代码如下。

public static iidentityserverbuilder addextensiongrantvalidator<t>(this iidentityserverbuilder builder)
            where t : class, iextensiongrantvalidator
        {
            builder.services.addtransient<iextensiongrantvalidator, t>();
            return builder;
        }

二、自定义授权实现

现在开始开发第一个自定义授权方式,granttype定义为czarcustomuser,然后实现iextensiongrantvalidator接口,为了演示方便,我新建一个测试用户表,用来模拟老系统的登录方式。

create table czarcustomuser
(
    iid int identity,
    username varchar(50),
    usertruename varchar(50),
    userpwd varchar(100)
)
--插入测试用户密码信息,测试数据密码不加密
insert into czarcustomuser values('jinyancao','金焰的世界','777777')

然后把实现验证的方法,由于代码太简单,我就直接贴代码如下。

namespace czar.authplatform.web.application.irepository
{
    public interface iczarcustomuserrepository
    {
        /// <summary>
        /// 根据账号密码获取用户实体
        /// </summary>
        /// <param name="uaccount">账号</param>
        /// <param name="upassword">密码</param>
        /// <returns></returns>
        czarcustomuser finduserbyuaccount(string uaccount, string upassword);
    }
}

namespace czar.authplatform.web.application.repository
{
    public class czarcustomuserrepository : iczarcustomuserrepository
    {
        private readonly string dbconn = "";
        public czarcustomuserrepository(ioptions<czarconfig> czarconfig)
        {
            dbconn = czarconfig.value.dbconnectionstrings;
        }

        /// <summary>
        /// 根据账号密码获取用户实体
        /// </summary>
        /// <param name="uaccount">账号</param>
        /// <param name="upassword">密码</param>
        /// <returns></returns>
        public czarcustomuser finduserbyuaccount(string uaccount, string upassword)
        {
            using (var connection = new sqlconnection(dbconn))
            {
                string sql = @"select * from czarcustomuser where username=@uaccount and userpwd=upassword ";
                var result = connection.queryfirstordefault<czarcustomuser>(sql, new { uaccount, upassword });
                return result;
            }
        }
    }
}

namespace czar.authplatform.web.application.iservices
{
    public interface iczarcustomuserservices
    {
        /// <summary>
        /// 根据账号密码获取用户实体
        /// </summary>
        /// <param name="uaccount">账号</param>
        /// <param name="upassword">密码</param>
        /// <returns></returns>
        czarcustomuser finduserbyuaccount(string uaccount, string upassword);
    }
}

namespace czar.authplatform.web.application.services
{
    public class czarcustomuserservices: iczarcustomuserservices
    {
        private readonly iczarcustomuserrepository czarcustomuserrepository;
        public czarcustomuserservices(iczarcustomuserrepository czarcustomuserrepository)
        {
            this.czarcustomuserrepository = czarcustomuserrepository;
        }

        /// <summary>
        /// 根据账号密码获取用户实体
        /// </summary>
        /// <param name="uaccount">账号</param>
        /// <param name="upassword">密码</param>
        /// <returns></returns>
        public czarcustomuser finduserbyuaccount(string uaccount, string upassword)
        {
            return czarcustomuserrepository.finduserbyuaccount(uaccount, upassword);
        }
    }
}

现在可以定义自定义的授权类型了,我起名为czarcustomusergrantvalidator,实现代码如下。

using czar.authplatform.web.application.iservices;
using identityserver4.models;
using identityserver4.validation;
using system.threading.tasks;

namespace czar.authplatform.web.application.ids4
{
    /// <summary>
    /// 金焰的世界
    /// 2019-01-28
    /// 自定义用户授权
    /// </summary>
    public class czarcustomusergrantvalidator : iextensiongrantvalidator
    {
        public string granttype => "czarcustomuser";

        private readonly iczarcustomuserservices czarcustomuserservices;

        public czarcustomusergrantvalidator(iczarcustomuserservices czarcustomuserservices)
        {
            this.czarcustomuserservices = czarcustomuserservices;
        }

        public task validateasync(extensiongrantvalidationcontext context)
        {
            var username = context.request.raw.get("czar_name");
            var userpassword = context.request.raw.get("czar_password");

            if (string.isnullorempty(username) || string.isnullorempty(userpassword))
            {
                context.result = new grantvalidationresult(tokenrequesterrors.invalidgrant);
            }
            //校验登录
            var result = czarcustomuserservices.finduserbyuaccount(username, userpassword);
            if (result==null)
            {
                context.result = new grantvalidationresult(tokenrequesterrors.invalidgrant);
            }
            //添加指定的claims
            context.result = new grantvalidationresult(
                         subject: result.iid.tostring(),
                         authenticationmethod: granttype,
                         claims: result.claims);
            return task.completedtask;
        }
    }
}

这就实现了自定义授权的功能,是不是很简单呢?然后添加此扩展方法。

services.addidentityserver(option =>
            {
                option.publicorigin = configuration["czarconfig:publicorigin"];
            })
                .adddevelopersigningcredential()
                .adddapperstore(option =>
                {
                    option.dbconnectionstrings = configuration["czarconfig:dbconnectionstrings"];
                })
                .addresourceownervalidator<czarresourceownerpasswordvalidator>()
                .addprofileservice<czarprofileservice>()
                .addsecretvalidator<jwtsecretvalidator>()
                //添加自定义授权
                .addextensiongrantvalidator<czarcustomusergrantvalidator>();

现在是不是就可以使用自定义授权的方式了呢?打开postman测试,按照源码解析和设计参数,测试信息如下,发现报错,原来是还未配置好客户端访问权限,开启权限测试如下。

三、客户端权限配置

在使用identityserver4时我们一定要理解整个验证流程。根据这次配置,我再梳理下流程如下:

  • 1、校验客户端client_id和client_secret。
  • 2、校验客户端是否有当前的授权方式。
  • 3、校验是否有请求scope权限。
  • 4、如果非客户端验证,校验账号密码或自定义规则是否正确。
  • 5、非客户端验证,校验授权信息是否有效。

通过此流程会发现我们缺少授权方式配置,所以请求时提示上面的提示,既然知道原因了,那就很简单的来实现,添加客户端自定义授权模式。此信息是在clientgranttypes表中,字段为客户端id和授权方式。我测试的客户端id为21,授权方式为czarcustomuser,那直接使用sql语句插入关系,然后再测试。

insert into clientgranttypes values(21,'czarcustomuser');

发现可以获取到预期结果,然后查看access_token是什么内容,显示如下。

显示的信息和我们定义的信息相同,而且可以通过amr来区分授权类型,不同的业务系统使用不同的认证方式,然后统一集成到认证平台即可。

四、总结与思考

本篇我介绍了自定义授权方式,从源码解析到最后的实现详细讲解了实现原理,并使用测试的用户来实现自定义的认证流程,本篇涉及的知识点不多,但是非常重要,因为我们在使用统一身份认证时经常会遇到多种认证方式的结合,和多套不同应用用户的使用,在掌握了授权原理后,就能在不同的授权方式中切换的游刃有余。

思考下,有了这些知识后,关于短信验证码登录和扫码登录是不是有心理有底了呢?如果自己实现这类登录应该都知道从哪里下手了吧。

下篇我将介绍常用登录的短信验证码授权方式,尽情期待吧。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网