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

AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

程序员文章站 2023-08-21 19:18:45
创建webapi项目 创建四个webapi项目,两个处理业务,一个网关,一个验证中心。四个项目对应的端口如下, ApiGateway:1999 IdentityServer:16690 Services.Api1:2108 Services.Api2:2343 添加Swagger支持 在两个业务项目 ......

创建webapi项目

  创建四个webapi项目,两个处理业务,一个网关,一个验证中心。四个项目对应的端口如下,

apigateway:1999

identityserver:16690

services.api1:2108

services.api2:2343

AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

 

添加swagger支持

  在两个业务项目中分别引用swashbuckle.aspnetcore,目前是最新版本是4.0.1。在项目属性面板,设置输出xml文档,swagger可以读取xml注释生成json文件,在swagger ui页面中显示。但是选择输出xml文档后,对于没有xml注释的类和方法会显示警告,可以在项目属性面板中【错误和警告】选项,取消显示警告中添加1591,这样就可以不显示缺少xml注释的警告了。对于强迫症的你暂时可以平复一下了,当然,真的强迫症的话,肯定会全部加上xml注释的。(¬_¬)瞄

AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

   startup.configureservices

public void configureservices(iservicecollection services)
        {
           services.addmvc().setcompatibilityversion(compatibilityversion.version_2_2);

            services.addswaggergen(options =>
            {
                //swaggerdoc的第一个参数要与configure中swaggerendpoint中的版本名称一致
                //既可以使用版本号,也可以使用自定义名称
                options.swaggerdoc("serviceapitwo", new info
                {
                    title = "services.api #two",
                    version = "v1",
                    description = "服务api #two",
                    license = new license
                    {
                        name = "apl2.0",
                        url = "https://opensource.org/licenses/apache-2.0"
                    },
                    contact = new contact
                    {
                        name = "原来是李",
                        url = "https://www.cnblogs.com/falltakeman"
                    }
                });
                var xmlfile = $"{assembly.getexecutingassembly().getname().name}.xml";
                var xmlpath = path.combine(appdomain.currentdomain.basedirectory, xmlfile);
                options.includexmlcomments(xmlpath);
                
            });
        }

  startup.configure

public void configure(iapplicationbuilder app, ihostingenvironment env)
        {
            if (env.isdevelopment())
            {
                app.usedeveloperexceptionpage();

                app.useswagger(c=>
                {
                    c.routetemplate = "{documentname}/swagger.json";
                });
                app.useswaggerui(u =>
                {
                    u.swaggerendpoint("/serviceapitwo/swagger.json", "serviceapitwo");
                    u.documenttitle = "service api #2 文档";
                });
            }
            
            app.usemvc();
        }

   配置好启动项目看看。

AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

  通过swagger发起请求,响应正常。

 AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

 

配置网关项目

  使用目前比较流行的开源网关服务ocelot,apigateway项目添加引用ocelot、ocelot.provider.consul、ocelot.provider.polly、ocelot.cache.cachemanager,目前用到的版本是13.5.2。要在网关中统一查看各个服务的swagger文档,还需引用swash.aspnetcore。consul的配置先不说了。

  在项目根路径下添加ocelotconfig.json配置文件。处理正常的路由转发外,要在网关swagger页面访问业务api,还需要配置swagger路由转发。

{
  "globalconfiguration": {
    //外部访问路径
    "baseurl": "http://localhost:1999",
    //限速配置
    "ratelimitoptions": {
      //白名单
      "clientwhitelist": [],
      "enableratelimiting": true,
      //限制时间段,例如1s,5m,1h,1d
      "period": "1s",
      //等待重试等待的时间间隔(秒)
      "periodtimespan": 1,
      //限制
      "limit": 1,
      //自定义消息
      "quotaexceededmessage": "单位时间内请求次数超过限制。",
      "httpstatuscode": 999
    },
    //服务发现配置
    "servicediscoveryprovider": {
      "host": "localhost",
      "port": 8500,
      "type": "pollconsul",
      "pollinginterval": 1000
    },
    //熔断配置
    "qosoptions": {
      "exceptionsallowedbeforebreaking": 3,
      "durationofbreak": 5,
      //超时值(毫秒)
      "timeoutvalue": 5000
    }
  },
  "reroutes": [
    // api#one项目配置
    {
      "upstreampathtemplate": "/gateway/one/{url}", //上游路径模板
      "upstreamhttpmethod": [ "get", "post", "put", "delete" ], //上游http请求方法
      "downstreampathtemplate": "/api/{url}", //下游路径模板
      "downstreamscheme": "http", //下游协议 https/http
      "servicename": "serviceapione", //服务名称(结合服务发现使用)
      "useservicediscovery": true, //启用服务发现
      "loadbalancer": "roundrobin", //负载均衡算法:roundrobin-轮询;leastconnection-最少连接数(最空闲的服务器);noloadbalancer-总是发送往第一个请求或者服务发现
      //下游主机与端口,允许配置多个
      "downstreamhostandports": [
        //{
        //  "host": "ip",
        //  "port": 80
        //},
        {
          "host": "localhost",
          "port": 2108
        }
      ],
      //熔断配置,在请求下游服务时使用断路
      "qosoptions": {
        "exceptionsallowedbeforebreaking": 3,
        "durationofbreak": 10,
        "timeoutvalue": 5000
      },
      //权限配置
      //"authenticationoptions": {
      //  "authenticationproviderkey": "bearer",
      //  "allowedscopes": []
      //}
    },
    // api#two项目配置
    {
      "upstreampathtemplate": "/gateway/two/{url}",
      "upstreamhttpmethod": [ "get", "post", "put", "delete" ],
      "downstreampathtemplate": "/api/{url}",
      "downstreamscheme": "http",
      "servicename": "serviceapitwo",
      "useservicediscovery": true,
      "loadbalancer": "roundrobin",
      "downstreamhostandports": [
        //{
        //  "host": "ip",
        //  "port": 80
        //},
        {
          "host": "localhost",
          "port": 2343
        }
      ],
      "qosoptions": {
        "exceptionsallowedbeforebreaking": 3,
        "durationofbreak": 10,
        "timeoutvalue": 5000
      },
      //"authenticationoptions": {
      //  "authenticationproviderkey": "bearer",
      //  "allowedscopes": []
      //}
    },
    //swagger api2配置
    {
      "upstreampathtemplate": "/serviceapitwo/swagger.json",
      "upstreamhttpmethod": [ "get", "post", "put", "delete" ],
      "downstreampathtemplate": "/serviceapitwo/swagger.json",
      "downstreamscheme": "http",
      "downstreamhostandports": [
        {
          "host": "localhost",
          "port": 2343
        }
      ]
    },
    //swagger api1多版本配置v1.0
    {
      "upstreampathtemplate": "/serviceapione/1.0/swagger.json",
      "upstreamhttpmethod": [ "get", "post", "put", "delete" ],
      "downstreampathtemplate": "/serviceapione/1.0/swagger.json",
      "downstreamscheme": "http",
      "downstreamhostandports": [
        {
          "host": "localhost",
          "port": 2108
        }
      ]
    },
    //swagger api1多版本配置v2.0
    {
      "upstreampathtemplate": "/serviceapione/2.0/swagger.json",
      "upstreamhttpmethod": [ "get", "post", "put", "delete" ],
      "downstreampathtemplate": "/serviceapione/2.0/swagger.json",
      "downstreamscheme": "http",
      "downstreamhostandports": [
        {
          "host": "localhost",
          "port": 2108
        }
      ]
    }
  ]
}

 

  startup.configureservices注册swagger和ocelot网关服务,configureservices中的swagger配置和业务api中一样,

services.addocelot(configuration)
                .addconsul()
                .addcachemanager(c => c.withdictionaryhandle())
                .addpolly();

services.addswaggergen(options =>
            {
                options.swaggerdoc(configuration["swagger:name"], new info
                {
                    title = configuration["swagger:title"],
                    version = configuration["swagger:version"],
                    description = configuration["swagger:description"],
                    license = new license
                    {
                        name = configuration["swagger:license:name"],
                        url = configuration["swagger:license:url"]
                    },
                    contact = new contact
                    {
                        name = configuration["swagger:contact:name"],
                        email = configuration["swagger:contact:email"],
                        url = configuration["swagger:contact:url"]
                    }
                });
            });

  startup.configure中,我是使用了配置文件,将业务api的swagger节点写在了配置文件中。

public void configure(iapplicationbuilder app, ihostingenvironment env)
        {
            if (env.isdevelopment())
            {
                app.usedeveloperexceptionpage();
                // 配置文件中的swaggername为业务api中是swaggerendpoint名称,有版本号的带上版本号
                var apis = configuration["swaggerapis:swaggername"].split(';').tolist();
                app.useswagger();
                app.useswaggerui(options=>
                {
                    //显示注册到网关的api接口
                    apis.foreach(key =>
                    {
                        options.swaggerendpoint($"/{key}/swagger.json", key);
                    });
                    options.documenttitle = "api网关";
                });

            }
            else
            {
                app.useexceptionhandler("/home/error");
            }

            app.usestaticfiles();
            app.usecookiepolicy();

            app.usemvc(routes =>
            {
                routes.maproute(
                    name: "default",
                    template: "{controller=home}/{action=index}/{id?}");
            });

            app.useocelot().wait(); // 使用ocelot网关中间件

        }

  修改apigateway项目program.cs将配置文件添加进来。

public static iwebhostbuilder createwebhostbuilder(string[] args) =>
            webhost.createdefaultbuilder(args)
            .configureappconfiguration((hostingcontext,config)=>
            {
                var env = hostingcontext.hostingenvironment;
                //根据环境变量加载不同的json配置
                config.addjsonfile($"appsettings.{env.environmentname}.json", optional: true, reloadonchange: true)
                .addjsonfile("ocelotconfig.json")//网关配置
                .addenvironmentvariables();//环境变量
            })
            .configurelogging((hostingcontext,logging)=>
            {
                logging.addconfiguration(hostingcontext.configuration.getsection("logging"));
                
                logging.addconsole();
                //添加调试日志
                logging.adddebug();
            })
            .usestartup<startup>();

  网关配置了,swagger也配置了,启动业务api和网关服务看看效果。

AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

   两个业务api的swagger文档都可以正常查看。发请求看一下,结果响应404,仔细看一下,请求的服务地址是网关服务的地址,而不是业务api的地址,难道是ocelot网关路由配置错了?使用postman发一个get请求看看,localhost:1999/gateway/two/values,网关转发到localhost:2343/api/values,响应正常。

AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

   看swashbuckle文档这一段,将业务api中configure加上这一段后再次通过网关发起请求,结果出现typeerror。既然出错了,打开浏览器调试工具看一下就明白了,failed to load http://localhost:2343/api/apitwo: no 'access-control-allow-origin' header is present on the requested resource. origin 'http://localhost:1999' is therefore not allowed access.

AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

  网关请求业务api跨域了,要让业务api允许来自网关的请求,需要设置业务api跨域请求政策。加上下面的配置后,网关请求正常了。

  修改startup.configure

public void configure(iapplicationbuilder app, ihostingenvironment env)
        {
            if (env.isdevelopment())
            {
                app.usedeveloperexceptionpage();

                app.useswagger(c=>
                {
                    //处理网关通过swagger访问
                    c.preserializefilters.add((swaggerdoc, httpreq) => swaggerdoc.host = httpreq.host.value);
                    c.routetemplate = "{documentname}/swagger.json";
                });
                app.useswaggerui(u =>
                {
                    u.swaggerendpoint("/serviceapitwo/swagger.json", "serviceapitwo");
                    u.documenttitle = "service api #2 文档";
                });
            }

            // 允许网关访问
            app.usecors(options =>
            {
                options.withorigins("http://localhost:1999")
                .allowanyheader()
                .allowanymethod();
            });
            app.usemvc();
        }

 

 使用identityserver4保护webapi

   先前已经创建identityserver项目,添加引用identityserver4.aspnetidentity(2.5.0)、identityserver4.entityframework(2.5.0)。新建一个类identityserverconfig,里面定义四个方法getapiresources、getidentityresources、getclients、gettestusers,具体代码就不贴了,看一下startup。生产环境的话,当然要用数据库,这里不讨论identityserver4的使用。

public void configureservices(iservicecollection services)
        {
            services.addmvc().setcompatibilityversion(compatibilityversion.version_2_2);

            services.addidentityserver()
                .adddevelopersigningcredential()
                .addinmemorypersistedgrants()
                .addtestusers(identityserverconfig.gettestusers())
                .addinmemoryidentityresources(identityserverconfig.getidentityresources())
                .addinmemoryapiresources(identityserverconfig.getapiresources())
                .addinmemoryclients(identityserverconfig.getclients(configuration));
        }

        // this method gets called by the runtime. use this method to configure the http request pipeline.
        public void configure(iapplicationbuilder app, ihostingenvironment env)
        {
            if (env.isdevelopment())
            {
                app.usedeveloperexceptionpage();
            }

            app.useidentityserver();

            app.usemvc();
        }

  将网关和两个api项目对应的apiresources和clients分别为api-gateway、service-api-one、service-api-two,两个api客户端allowedscope为自己,网关的allowedscope为自己和两个api客户端。在需要保护的三个项目中添加引用identityserver4.accesstokenvalidation(2.7.0),修改startup的configureservices,添加如下代码。

//使用identityserver4
            services.addauthentication(identityserverauthenticationdefaults.authenticationscheme)
                .addidentityserverauthentication(options =>
                {
                    options.apiname = "service-api-two";
                    options.authority = "http://localhost:16690"; // identityserver验证服务
                    options.requirehttpsmetadata = false;
                    options.enablecaching = true;
                });

  startup.configure中添加app.useauthentication();

  要在swagger中访问需要验证的api,需要在swagger配置中添加安全验证。

services.addswaggergen(options =>
            {
                //swaggerdoc的第一个参数要与configure中swaggerendpoint中的版本名称一致
                //既可以使用版本号,也可以使用自定义名称
                options.swaggerdoc("serviceapitwo", new info
                {
                    title = "services.api #two",
                    version = "v1",
                    description = "服务api #two",
                    license = new license
                    {
                        name = "mit",
                        url = "https://mit-license.org/"
                    },
                    contact = new contact
                    {
                        name = "原来是李",
                        url = "http://www.cnblogs.com/falltakeman"
                    }
                });
                var xmlfile = $"{assembly.getexecutingassembly().getname().name}.xml";
                var xmlpath = path.combine(appdomain.currentdomain.basedirectory, xmlfile);
                options.includexmlcomments(xmlpath);
                // swagger访问需要验证的api
                options.addsecuritydefinition("bearer", new apikeyscheme
                {
                    in = "header",
                    name = "authorization",
                    type = "apikey",
                    description = "bearer {token}"
                });
                options.addsecurityrequirement(new dictionary<string, ienumerable<string>>
                {
                    {
                        "bearer",
                        enumerable.empty<string>()
                    }
                });
            });

  在api控制器中,在需要保护的api上添加[authorize]特性,没有授权的情况下访问受限api会报401错误。

AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

   使用postman获取token,在swagger中填写token,再次发起请求,响应正常。

AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

   在apigateway的startup.configureservices添加authentication,在services.addswaggergen添加相应代码,启动项目在app.useocelot().wait()抛出异常:scheme already exists: beareridentityserverauthenticationjwt. 最终使用了下面的方式。在apigateway项目中通过swagger也可以访问业务api了。

action<identityserverauthenticationoptions> isaopt = option =>
            {
                option.authority = configuration["identityservice:uri"];
                option.requirehttpsmetadata = convert.toboolean(configuration["identityservice:usehttps"]);
                option.apiname = configuration["identityservice:apiname"];
                option.apisecret = configuration["identityservice:apisecret"];
                option.supportedtokens = supportedtokens.both;
            };
            services.addauthentication().addidentityserverauthentication(configuration["identityservice:defaultscheme"], isaopt);

  但是配置中的identityservice:defaultscheme不可以是"bearer",试验配置的是"identitybearer",不知为何不可以是"bearer",不知道有没有懂这个的可以指点一二。

 

 

the end...