当前位置: 移动技术网 > IT编程>开发语言>.net > AspNetCore网关集成Swagger访问使用IdentityServer保护的webapi项目

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

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

吞食孔明传,闫凤娇第六季,香港洗发水

创建webapi项目

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

apigateway:1999

identityserver:16690

services.api1:2108

services.api2:2343

 

添加swagger支持

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

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

   配置好启动项目看看。

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

 

 

配置网关项目

  使用目前比较流行的开源网关服务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和网关服务看看效果。

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

   看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.

  网关请求业务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错误。

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

   在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...

 

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

相关文章:

验证码:
移动技术网