目录

  • identityserver4源码解析_1_项目结构
  • identityserver4源码解析_2_元数据接口
  • identityserver4源码解析_3_认证接口
  • identityserver4源码解析_4_令牌发放接口
  • identityserver4源码解析_5_查询用户信息接口
  • identityserver4源码解析_6_结束会话接口
  • identityserver4源码解析_7_查询令牌信息接口
  • identityserver4源码解析_8_撤销令牌接口

简介

security源码解析系列介绍了微软提供的各种认证架构,其中oauth2.0,openidconnect属于远程认证架构,所谓远程认证,是指token的颁发是由其他站点完成的。

identityserver4是基于openidconnect协议的认证中心框架,可以帮助我们快速搭建微服务认证中心。

初学者可能看到生涩的概念比较头疼,可以将oauth, openidconnect协议简单理解成需求文档,idsv4基于需求提供了一系列的api实现。

对于idsv还不太了解的可以看下面的资料,本系列主要学习梳理idsv4的源码,结合协议加深理解。

晓晨姐姐系列文章

官方文档

项目结构

项目地址如下

https://github.com/identityserver/identityserver4

克隆到本地,项目结构如图

核心项目是identityserver4,其余的都是与微软框架集成、以及处理持久化的项目。
项目结构如图。endpoints文件夹就是接口文件,我们先看下依赖注入、中间件的代码,然后看下每个接口。

依赖注入

public static iidentityserverbuilder addidentityserver(this iservicecollection services)
{
    var builder = services.addidentityserverbuilder();

    builder
        .addrequiredplatformservices()
        .addcookieauthentication()
        .addcoreservices()
        .adddefaultendpoints()
        .addpluggableservices()
        .addvalidators()
        .addresponsegenerators()
        .adddefaultsecretparsers()
        .adddefaultsecretvalidators();

    // provide default in-memory implementation, not suitable for most production scenarios
    builder.addinmemorypersistedgrants();

    return builder;
}
  • addrequiredplatformservices – 注入平台服务
    • ihttpcontextaccessor:httpcontext访问器
    • identityserveroptions:配置类
 public static iidentityserverbuilder addrequiredplatformservices(this iidentityserverbuilder builder)
{
    builder.services.tryaddsingleton<ihttpcontextaccessor, httpcontextaccessor>();            
    builder.services.addoptions();
    builder.services.addsingleton(
        resolver => resolver.getrequiredservice<ioptions<identityserveroptions>>().value);
    builder.services.addhttpclient();

    return builder;
}
  • addcookieauthentication – 注入cookie服务
    • 注入名称为idsrv的cookie认证架构
    • 注入iauthenticationservice的实现identityserverauthenticationservice
    • 注入iauthenticationhandlerprovider的实现federatedsignoutauthenticationhandlerprovider
public static iidentityserverbuilder addcookieauthentication(this iidentityserverbuilder builder)
{
    builder.services.addauthentication(identityserverconstants.defaultcookieauthenticationscheme)
        .addcookie(identityserverconstants.defaultcookieauthenticationscheme)
        .addcookie(identityserverconstants.externalcookieauthenticationscheme);

    builder.services.addsingleton<iconfigureoptions<cookieauthenticationoptions>, configureinternalcookieoptions>();
    builder.services.addsingleton<ipostconfigureoptions<cookieauthenticationoptions>, postconfigureinternalcookieoptions>();
    builder.services.addtransientdecorator<iauthenticationservice, identityserverauthenticationservice>();
    builder.services.addtransientdecorator<iauthenticationhandlerprovider, federatedsignoutauthenticationhandlerprovider>();

    return builder;
}
  • addcoreservices – 注入核心服务
/// <summary>
/// adds the core services.
/// </summary>
/// <param name="builder">the builder.</param>
/// <returns></returns>
public static iidentityserverbuilder addcoreservices(this iidentityserverbuilder builder)
{
    builder.services.addtransient<secretparser>();
    builder.services.addtransient<secretvalidator>();
    builder.services.addtransient<scopevalidator>();
    builder.services.addtransient<extensiongrantvalidator>();
    builder.services.addtransient<bearertokenusagevalidator>();
    builder.services.addtransient<jwtrequestvalidator>();

    // todo: remove in 3.0
#pragma warning disable cs0618 // type or member is obsolete
    builder.services.addtransient<backchannelhttpclient>();
#pragma warning restore cs0618 // type or member is obsolete

    builder.services.addtransient<returnurlparser>();
    builder.services.addtransient<identityservertools>();

    builder.services.addtransient<ireturnurlparser, oidcreturnurlparser>();
    builder.services.addscoped<iusersession, defaultusersession>();
    builder.services.addtransient(typeof(messagecookie<>));

    builder.services.addcors();
    builder.services.addtransientdecorator<icorspolicyprovider, corspolicyprovider>();

    return builder;
}
  • adddefaultendpoints – 注入接口
    • authorizecallbackendpoint:认证回调接口
    • authorizeendpoint:认证接口
    • checksessionendpoint:检查会话接口
    • deviceauthorizationendpoint:设备认证接口
    • discoveryendpoint:元数据键接口
    • discoveryendpoint:元数据接口
    • endsessioncallbackendpoint:结束会话回调接口
    • endsessionendpoint:结束会话接口
    • introspectionendpoint:查询令牌信息接口
    • tokenrevocationendpoint:撤销令牌接口
    • tokenendpoint:发放令牌接口
    • userinfoendpoint:查询用户信息接口

注入所有默认接口,包括接口名称和地址。请求进来之后,路由类endpointrouter通过路由来寻找匹配的处理器。

 public static iidentityserverbuilder adddefaultendpoints(this iidentityserverbuilder builder)
{
    builder.services.addtransient<iendpointrouter, endpointrouter>();

    builder.addendpoint<authorizecallbackendpoint>(endpointnames.authorize, protocolroutepaths.authorizecallback.ensureleadingslash());
    builder.addendpoint<authorizeendpoint>(endpointnames.authorize, protocolroutepaths.authorize.ensureleadingslash());
    builder.addendpoint<checksessionendpoint>(endpointnames.checksession, protocolroutepaths.checksession.ensureleadingslash());
    builder.addendpoint<deviceauthorizationendpoint>(endpointnames.deviceauthorization, protocolroutepaths.deviceauthorization.ensureleadingslash());
    builder.addendpoint<discoverykeyendpoint>(endpointnames.discovery, protocolroutepaths.discoverywebkeys.ensureleadingslash());
    builder.addendpoint<discoveryendpoint>(endpointnames.discovery, protocolroutepaths.discoveryconfiguration.ensureleadingslash());
    builder.addendpoint<endsessioncallbackendpoint>(endpointnames.endsession, protocolroutepaths.endsessioncallback.ensureleadingslash());
    builder.addendpoint<endsessionendpoint>(endpointnames.endsession, protocolroutepaths.endsession.ensureleadingslash());
    builder.addendpoint<introspectionendpoint>(endpointnames.introspection, protocolroutepaths.introspection.ensureleadingslash());
    builder.addendpoint<tokenrevocationendpoint>(endpointnames.revocation, protocolroutepaths.revocation.ensureleadingslash());
    builder.addendpoint<tokenendpoint>(endpointnames.token, protocolroutepaths.token.ensureleadingslash());
    builder.addendpoint<userinfoendpoint>(endpointnames.userinfo, protocolroutepaths.userinfo.ensureleadingslash());

    return builder;
}
  • addpluggableservices – 注入可插拔服务
public static iidentityserverbuilder addpluggableservices(this iidentityserverbuilder builder)
{
    builder.services.tryaddtransient<ipersistedgrantservice, defaultpersistedgrantservice>();
    builder.services.tryaddtransient<ikeymaterialservice, defaultkeymaterialservice>();
    builder.services.tryaddtransient<itokenservice, defaulttokenservice>();
    builder.services.tryaddtransient<itokencreationservice, defaulttokencreationservice>();
    builder.services.tryaddtransient<iclaimsservice, defaultclaimsservice>();
    builder.services.tryaddtransient<irefreshtokenservice, defaultrefreshtokenservice>();
    builder.services.tryaddtransient<ideviceflowcodeservice, defaultdeviceflowcodeservice>();
    builder.services.tryaddtransient<iconsentservice, defaultconsentservice>();
    builder.services.tryaddtransient<icorspolicyservice, defaultcorspolicyservice>();
    builder.services.tryaddtransient<iprofileservice, defaultprofileservice>();
    builder.services.tryaddtransient<iconsentmessagestore, consentmessagestore>();
    builder.services.tryaddtransient<imessagestore<logoutmessage>, protecteddatamessagestore<logoutmessage>>();
    builder.services.tryaddtransient<imessagestore<endsession>, protecteddatamessagestore<endsession>>();
    builder.services.tryaddtransient<imessagestore<errormessage>, protecteddatamessagestore<errormessage>>();
    builder.services.tryaddtransient<iidentityserverinteractionservice, defaultidentityserverinteractionservice>();
    builder.services.tryaddtransient<ideviceflowinteractionservice, defaultdeviceflowinteractionservice>();
    builder.services.tryaddtransient<iauthorizationcodestore, defaultauthorizationcodestore>();
    builder.services.tryaddtransient<irefreshtokenstore, defaultrefreshtokenstore>();
    builder.services.tryaddtransient<ireferencetokenstore, defaultreferencetokenstore>();
    builder.services.tryaddtransient<iuserconsentstore, defaultuserconsentstore>();
    builder.services.tryaddtransient<ihandlegenerationservice, defaulthandlegenerationservice>();
    builder.services.tryaddtransient<ipersistentgrantserializer, persistentgrantserializer>();
    builder.services.tryaddtransient<ieventservice, defaulteventservice>();
    builder.services.tryaddtransient<ieventsink, defaulteventsink>();
    builder.services.tryaddtransient<iusercodeservice, defaultusercodeservice>();
    builder.services.tryaddtransient<iusercodegenerator, numericusercodegenerator>();
    builder.services.tryaddtransient<ibackchannellogoutservice, defaultbackchannellogoutservice>();

    builder.addjwtrequesturihttpclient();
    builder.addbackchannellogouthttpclient();
    //builder.services.addhttpclient<backchannellogouthttpclient>();
    //builder.services.addhttpclient<jwtrequesturihttpclient>();

    builder.services.addtransient<iclientsecretvalidator, clientsecretvalidator>();
    builder.services.addtransient<iapisecretvalidator, apisecretvalidator>();

    builder.services.tryaddtransient<ideviceflowthrottlingservice, distributeddeviceflowthrottlingservice>();
    builder.services.adddistributedmemorycache();

    return builder;
}
  • addvalidators – 注入校验类
public static iidentityserverbuilder addvalidators(this iidentityserverbuilder builder)
{
    // core
    builder.services.tryaddtransient<iendsessionrequestvalidator, endsessionrequestvalidator>();
    builder.services.tryaddtransient<itokenrevocationrequestvalidator, tokenrevocationrequestvalidator>();
    builder.services.tryaddtransient<iauthorizerequestvalidator, authorizerequestvalidator>();
    builder.services.tryaddtransient<itokenrequestvalidator, tokenrequestvalidator>();
    builder.services.tryaddtransient<iredirecturivalidator, strictredirecturivalidator>();
    builder.services.tryaddtransient<itokenvalidator, tokenvalidator>();
    builder.services.tryaddtransient<iintrospectionrequestvalidator, introspectionrequestvalidator>();
    builder.services.tryaddtransient<iresourceownerpasswordvalidator, notsupportedresourceownerpasswordvalidator>();
    builder.services.tryaddtransient<icustomtokenrequestvalidator, defaultcustomtokenrequestvalidator>();
    builder.services.tryaddtransient<iuserinforequestvalidator, userinforequestvalidator>();
    builder.services.tryaddtransient<iclientconfigurationvalidator, defaultclientconfigurationvalidator>();
    builder.services.tryaddtransient<ideviceauthorizationrequestvalidator, deviceauthorizationrequestvalidator>();
    builder.services.tryaddtransient<idevicecodevalidator, devicecodevalidator>();

    // optional
    builder.services.tryaddtransient<icustomtokenvalidator, defaultcustomtokenvalidator>();
    builder.services.tryaddtransient<icustomauthorizerequestvalidator, defaultcustomauthorizerequestvalidator>();
    
    return builder;
}
  • addresponsegenerators – 注入响应生成类
public static iidentityserverbuilder addresponsegenerators(this iidentityserverbuilder builder)
{
    builder.services.tryaddtransient<itokenresponsegenerator, tokenresponsegenerator>();
    builder.services.tryaddtransient<iuserinforesponsegenerator, userinforesponsegenerator>();
    builder.services.tryaddtransient<iintrospectionresponsegenerator, introspectionresponsegenerator>();
    builder.services.tryaddtransient<iauthorizeinteractionresponsegenerator, authorizeinteractionresponsegenerator>();
    builder.services.tryaddtransient<iauthorizeresponsegenerator, authorizeresponsegenerator>();
    builder.services.tryaddtransient<idiscoveryresponsegenerator, discoveryresponsegenerator>();
    builder.services.tryaddtransient<itokenrevocationresponsegenerator, tokenrevocationresponsegenerator>();
    builder.services.tryaddtransient<ideviceauthorizationresponsegenerator, deviceauthorizationresponsegenerator>();

    return builder;
}
  • adddefaultsecretparsers & adddefaultsecretvalidators
/// <summary>
/// adds the default secret parsers.
/// </summary>
/// <param name="builder">the builder.</param>
/// <returns></returns>
public static iidentityserverbuilder adddefaultsecretparsers(this iidentityserverbuilder builder)
{
    builder.services.addtransient<isecretparser, basicauthenticationsecretparser>();
    builder.services.addtransient<isecretparser, postbodysecretparser>();

    return builder;
}

/// <summary>
/// adds the default secret validators.
/// </summary>
/// <param name="builder">the builder.</param>
/// <returns></returns>
public static iidentityserverbuilder adddefaultsecretvalidators(this iidentityserverbuilder builder)
{
    builder.services.addtransient<isecretvalidator, hashedsharedsecretvalidator>();

    return builder;
}

identityserveroptions – 配置类

 /// <summary>
/// the identityserveroptions class is the top level container for all configuration settings of identityserver.
/// </summary>
public class identityserveroptions
{
    /// <summary>
    /// gets or sets the unique name of this server instance, e.g. https://myissuer.com.
    /// if not set, the issuer name is inferred from the request
    /// </summary>
    /// <value>
    /// unique name of this server instance, e.g. https://myissuer.com
    /// </value>
    public string issueruri { get; set; }

    /// <summary>
    /// gets or sets the origin of this server instance, e.g. https://myorigin.com.
    /// if not set, the origin name is inferred from the request
    /// note: do not set a url or include a path.
    /// </summary>
    /// <value>
    /// origin of this server instance, e.g. https://myorigin.com
    /// </value>
    public string publicorigin { get; set; }

    /// <summary>
    /// gets or sets the value for the jwt typ header for access tokens.
    /// </summary>
    /// <value>
    /// the jwt typ value.
    /// </value>
    public string accesstokenjwttype { get; set; } = "at+jwt";

    /// <summary>
    /// emits an aud claim with the format issuer/resources. that's needed for some older access token validation plumbing. defaults to false.
    /// </summary>
    public bool emitlegacyresourceaudienceclaim { get; set; } = false;

    /// <summary>
    /// gets or sets the endpoint configuration.
    /// </summary>
    /// <value>
    /// the endpoints configuration.
    /// </value>
    public endpointsoptions endpoints { get; set; } = new endpointsoptions();

    /// <summary>
    /// gets or sets the discovery endpoint configuration.
    /// </summary>
    /// <value>
    /// the discovery endpoint configuration.
    /// </value>
    public discoveryoptions discovery { get; set; } = new discoveryoptions();

    /// <summary>
    /// gets or sets the authentication options.
    /// </summary>
    /// <value>
    /// the authentication options.
    /// </value>
    public authenticationoptions authentication { get; set; } = new authenticationoptions();

    /// <summary>
    /// gets or sets the events options.
    /// </summary>
    /// <value>
    /// the events options.
    /// </value>
    public eventsoptions events { get; set; } = new eventsoptions();

    /// <summary>
    /// gets or sets the max input length restrictions.
    /// </summary>
    /// <value>
    /// the length restrictions.
    /// </value>
    public inputlengthrestrictions inputlengthrestrictions { get; set; } = new inputlengthrestrictions();

    /// <summary>
    /// gets or sets the options for the user interaction.
    /// </summary>
    /// <value>
    /// the user interaction options.
    /// </value>
    public userinteractionoptions userinteraction { get; set; } = new userinteractionoptions();

    /// <summary>
    /// gets or sets the caching options.
    /// </summary>
    /// <value>
    /// the caching options.
    /// </value>
    public cachingoptions caching { get; set; } = new cachingoptions();

    /// <summary>
    /// gets or sets the cors options.
    /// </summary>
    /// <value>
    /// the cors options.
    /// </value>
    public corsoptions cors { get; set; } = new corsoptions();

    /// <summary>
    /// gets or sets the content security policy options.
    /// </summary>
    public cspoptions csp { get; set; } = new cspoptions();

    /// <summary>
    /// gets or sets the validation options.
    /// </summary>
    public validationoptions validation { get; set; } = new validationoptions();

    /// <summary>
    /// gets or sets the device flow options.
    /// </summary>
    public deviceflowoptions deviceflow { get; set; } = new deviceflowoptions();

    /// <summary>
    /// gets or sets the mutual tls options.
    /// </summary>
    public mutualtlsoptions mutualtls { get; set; } = new mutualtlsoptions();
}

useridentityserver – 中间件逻辑

  • 执行校验
  • baseurlmiddleware中间件:设置baseurl
  • 配置cors跨域:corspolicyprovider根据client信息生成动态策略
  • identityservermiddlewareoptions默认调用了useauthentication,所以如果使用identityserver不用重复注册authentication中间件
  • 使用mutualtlstokenendpointmiddleware中间件:要求客户端、服务端都使用https,默认不开启
  • 使用identityservermiddleware中间件:iendpointrouter根据请求寻找匹配的iendpointhandler,如果找到的话则由endpointhandler处理请求。
public static iapplicationbuilder useidentityserver(this iapplicationbuilder app, identityservermiddlewareoptions options = null)
{
    app.validate();

    app.usemiddleware<baseurlmiddleware>();

    app.configurecors();

    // it seems ok if we have useauthentication more than once in the pipeline --
    // this will just re-run the various callback handlers and the default authn 
    // handler, which just re-assigns the user on the context. claims transformation
    // will run twice, since that's not cached (whereas the authn handler result is)
    // related: https://github.com/aspnet/security/issues/1399
    if (options == null) options = new identityservermiddlewareoptions();
    options.authenticationmiddleware(app);

    app.usemiddleware<mutualtlstokenendpointmiddleware>();
    app.usemiddleware<identityservermiddleware>();

    return app;
}

核心中间件identityservermiddleware的代码,逻辑比较清晰

  • iendpointrouter路由类旬斋匹配接口
  • 匹配接口处理请求返回结果iendpointresult
  • iendpointresult执行结果,写入上下文,返回报文
 public async task invoke(httpcontext context, iendpointrouter router, iusersession session, ieventservice events)
{
    // this will check the authentication session and from it emit the check session
    // cookie needed from js-based signout clients.
    await session.ensuresessionidcookieasync();

    try
    {
        var endpoint = router.find(context);
        if (endpoint != null)
        {
            _logger.loginformation("invoking identityserver endpoint: {endpointtype} for {url}", endpoint.gettype().fullname, context.request.path.tostring());

            var result = await endpoint.processasync(context);

            if (result != null)
            {
                _logger.logtrace("invoking result: {type}", result.gettype().fullname);
                await result.executeasync(context);
            }

            return;
        }
    }
    catch (exception ex)
    {
        await events.raiseasync(new unhandledexceptionevent(ex));
        _logger.logcritical(ex, "unhandled exception: {exception}", ex.message);
        throw;
    }

    await _next(context);
}

看一下路由类的处理逻辑
之前adddefaultendpoints注入了所有默认接口,路由类可以通过依赖注入拿到所有接口信息,将请求地址与接口地址对比得到匹配的接口,然后从容器拿到对应的接口处理器。

public endpointrouter(ienumerable<endpoint> endpoints, identityserveroptions options, ilogger<endpointrouter> logger)
{
    _endpoints = endpoints;
    _options = options;
    _logger = logger;
}

public iendpointhandler find(httpcontext context)
{
    if (context == null) throw new argumentnullexception(nameof(context));

    foreach(var endpoint in _endpoints)
    {
        var path = endpoint.path;
        if (context.request.path.equals(path, stringcomparison.ordinalignorecase))
        {
            var endpointname = endpoint.name;
            _logger.logdebug("request path {path} matched to endpoint type {endpoint}", context.request.path, endpointname);

            return getendpointhandler(endpoint, context);
        }
    }

    _logger.logtrace("no endpoint entry found for request path: {path}", context.request.path);

    return null;
}

 private iendpointhandler getendpointhandler(endpoint endpoint, httpcontext context)
{
    if (_options.endpoints.isendpointenabled(endpoint))
    {
        var handler = context.requestservices.getservice(endpoint.handler) as iendpointhandler;
        if (handler != null)
        {
            _logger.logdebug("endpoint enabled: {endpoint}, successfully created handler: {endpointhandler}", endpoint.name, endpoint.handler.fullname);
            return handler;
        }
        else
        {
            _logger.logdebug("endpoint enabled: {endpoint}, failed to create handler: {endpointhandler}", endpoint.name, endpoint.handler.fullname);
        }
    }
    else
    {
        _logger.logwarning("endpoint disabled: {endpoint}", endpoint.name);
    }

    return null;
}

总结

主干流程大致如图

idsv的代码量还是比较大的,有很多的类,但是代码还是要写的挺规范清晰,梳理下来脉络还是很明了的。