欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

IdentityServer4源码解析_1_项目结构

程序员文章站 2022-04-14 16:11:18
目录 "identityserver4源码解析_1_项目结构" "identityserver4源码解析_2_元数据接口" "identityserver4源码解析_3_认证接口" "identityserver4源码解析_4_令牌发放接口" "identityserver4源码解析_5_查询用户信 ......

目录

简介

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

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

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

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

晓晨姐姐系列文章

官方文档

项目结构

项目地址如下

https://github.com/identityserver/identityserver4

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

IdentityServer4源码解析_1_项目结构

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

依赖注入

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;
}

总结

主干流程大致如图
IdentityServer4源码解析_1_项目结构

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