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

IdentityServer4与ocelot实现认证与客户端统一入口

程序员文章站 2022-03-24 10:57:42
关于IdentityServer4与ocelot博客园里已经有很多介绍我这里就不再重复了。 ocelot与IdentityServer4组合认证博客园里也有很多,但大多使用ocelot内置的认证,而且大多都是用来认证API的,查找了很多资料也没看到如何认证oidc,所以这里的ocelot实际只是作为 ......

关于identityserver4与ocelot博客园里已经有很多介绍我这里就不再重复了。

ocelot与identityserver4组合认证博客园里也有很多,但大多使用ocelot内置的认证,而且大多都是用来认证api的,查找了很多资料也没看到如何认证oidc,所以这里的ocelot实际只是作为统一入口而不参与认证,认证的完成依然在客户端。代码是使用identityserver4的quickstart5_hybridandapi 示例修改的。项目结构如下

IdentityServer4与ocelot实现认证与客户端统一入口

 

一 ocelot网关

我们先在示例添加一个网关。

修改launchsettings.json中的端口为54660

IdentityServer4与ocelot实现认证与客户端统一入口

 "nanofabricapplication": {
      "commandname": "project",
      "launchbrowser": true,
      "applicationurl": "http://localhost:54660",
      "environmentvariables": {
        "aspnetcore_environment": "development"
      }
    }

配置文件如下

IdentityServer4与ocelot实现认证与客户端统一入口
{
  "reroutes": [
    { // mvcclient
      "downstreampathtemplate": "/mvcclient/{route}",
      "downstreamscheme": "http",
      "downstreamhostandports": [
        {
          "host": "localhost",
          "port": 50891
        }
      ],
      "upstreampathtemplate": "/mvcclient/{route}",
      "upstreamheadertransform": {
        "x-forwarded-for": "{remoteipaddress}"
      }
    },
    { // signin-oidc
      "downstreampathtemplate": "/signin-oidc",
      "downstreamscheme": "http",
      "downstreamhostandports": [
        {
          "host": "localhost",
          "port": 50891
        }
      ],
      "upstreampathtemplate": "/signin-oidc",
      "upstreamheadertransform": {
        "x-forwarded-for": "{remoteipaddress}"
      }
    },
    { // signout-callback-oidc
      "downstreampathtemplate": "/signout-callback-oidc",
      "downstreamscheme": "http",
      "downstreamhostandports": [
        {
          "host": "localhost",
          "port": 50891
        }
      ],
      "upstreampathtemplate": "/signout-callback-oidc",
      "upstreamheadertransform": {
        "x-forwarded-for": "{remoteipaddress}"
      }
    },
    { // myapi
      "downstreampathtemplate": "/myapi/{route}",
      "downstreamscheme": "http",
      "downstreamhostandports": [
        {
          "host": "localhost",
          "port": 50890
        }
      ],
      "upstreampathtemplate": "/myapi/{route}",
      "upstreamheadertransform": {
        "x-forwarded-for": "{remoteipaddress}"
      }
    },
    { // identityserver
      "downstreampathtemplate": "/{route}",
      "downstreamscheme": "http",
      "downstreamhostandports": [
        {
          "host": "localhost",
          "port": 50875
        }
      ],
      "upstreampathtemplate": "/identityserver/{route}",
      "upstreamheadertransform": {
        "x-forwarded-for": "{remoteipaddress}"
      }
    },
    { // identityserver
      "downstreampathtemplate": "/{route}",
      "downstreamscheme": "http",
      "downstreamhostandports": [
        {
          "host": "localhost",
          "port": 50875
        }
      ],
      "upstreampathtemplate": "/{route}",
      "upstreamheadertransform": {
        "x-forwarded-for": "{remoteipaddress}"
      }
    }
  ]
}
view code

这里我们定义3个下游服务,mvcclient,myapi,identityserver,并使用路由特性把signin-oidc,signout-callback-oidc导航到mvcclient,由mvcclient负责生成最后的cooike。并将默认路由指定到identityserver服务。

在configureservices中添加ocelot服务。

services.addocelot()
            .addcachemanager(x =>
            {
                x.withdictionaryhandle();
            })
            .addpolly()

在configure中使用ocelot中间件

app.useocelot().wait();

ocelot网关就部署完成了。

二 修改quickstartidentityserver配置

首先依然是修改launchsettings.json中的端口为50875

在configureservices中修改addidentityserver配置中的publicorigin和issueruri的url为http://localhost:54660/identityserver/

 services.addidentityserver(option =>
            {
                option.publicorigin = "http://localhost:54660/identityserver/";
                option.issueruri = "http://localhost:54660/identityserver/";
            })
                .adddevelopersigningcredential()
                .addinmemoryidentityresources(config.getidentityresources())
                .addinmemoryapiresources(config.getapiresources())
                .addinmemoryclients(config.getclients())
                .addtestusers(config.getusers());

这样一来发现文档中的identityserver地址就变为网关的地址了,进一步实现identityserver的负载均衡也是没有问题的。

修改config.cs中mvc客户端配置如下

  clientid = "mvc",
                    clientname = "mvc client",
                    allowedgranttypes = granttypes.hybridandclientcredentials,
                    clientsecrets =
                    {
                        new secret("secret".sha256())
                    },
                   // accesstokentype = accesstokentype.reference,
                    requireconsent = true,
                    redirecturis = { "http://localhost:54660/signin-oidc" },
                    postlogoutredirecturis = { "http://localhost:54660/signout-callback-oidc" },
                    allowedscopes =
                    {
                        identityserverconstants.standardscopes.openid,
                        identityserverconstants.standardscopes.profile,
                        "api1"
                    },
                    allowofflineaccess = true,
                    //直接返回客户端需要的claims
                    alwaysincludeuserclaimsinidtoken = true,

主要修改redirecturis和postlogoutredirecturis为网关地址,在网关也设置了signin-oidc和signout-callback-oidc转发请求到mvc客户端。

三 修改mvcclient

修改mvcclient的launchsettings.json端口为50891。

修改mvcclient的authority地址为http://localhost:54660/identityserver和默认路由地址mvcclient/{controller=home}/{action=index}/{id?}

IdentityServer4与ocelot实现认证与客户端统一入口
          jwtsecuritytokenhandler.defaultinboundclaimtypemap.clear();

            services.addauthentication(options =>
                {
                    options.defaultscheme = "cookies";
                    options.defaultchallengescheme = "oidc";
                    //options.defaultsigninscheme = cookieauthenticationdefaults.authenticationscheme;
                })
                .addcookie("cookies",options=> {
                    options.expiretimespan = timespan.fromminutes(30);
                    options.slidingexpiration = true;
                })
                .addopenidconnect("oidc", options =>
                {
                    options.signinscheme = "cookies";

                    options.authority = "http://localhost:54660/identityserver";
                    options.requirehttpsmetadata = false;

                    options.clientid = "mvc";
                    options.clientsecret = "secret";
                    options.responsetype = "code id_token";

                    options.savetokens = true;
                    options.getclaimsfromuserinfoendpoint = true;

                    options.scope.add("api1");

                    options.scope.add("offline_access");
                });
view code
IdentityServer4与ocelot实现认证与客户端统一入口
   app.usemvc(routes =>
            {
   
                    routes.maproute(
                        name: "default",
                        template: "mvcclient/{controller=home}/{action=index}/{id?}");
           
            });
view code

修改homecontroller,将相关地址修改为网关地址

IdentityServer4与ocelot实现认证与客户端统一入口
        public async task<iactionresult> callapiusingclientcredentials()
        {
            var tokenclient = new tokenclient("http://localhost:54660/identityserver/connect/token", "mvc", "secret");
            var tokenresponse = await tokenclient.requestclientcredentialsasync("api1");

            var client = new httpclient();
            client.setbearertoken(tokenresponse.accesstoken);
            var content = await client.getstringasync("http://localhost:54660/myapi/identity");

            viewbag.json = jarray.parse(content).tostring();
            return view("json");
        }

        public async task<iactionresult> callapiusinguseraccesstoken()
        {
            var accesstoken = await httpcontext.gettokenasync("access_token");
            //openidconnectparameternames
            var client = new httpclient();
            client.setbearertoken(accesstoken);
            var content = await client.getstringasync("http://localhost:54660/myapi/identity");

            viewbag.json = jarray.parse(content).tostring();
            return view("json");
        }
view code

 

四 修改api项目

api项目修改多一点。

将mvcclient的homecontroller和相关视图复制过来,模拟mvc与api同时存在的项目。

修改api的launchsettings.json端口为50890。

修改startup

IdentityServer4与ocelot实现认证与客户端统一入口
public class startup
    {
        public void configureservices(iservicecollection services)
        {
            services.adddataprotection(options => options.applicationdiscriminator = "00000").setapplicationname("00000");

            services.addmvc();
            jwtsecuritytokenhandler.defaultinboundclaimtypemap.clear();

            services.addauthentication(options =>
            {
                options.defaultscheme = "cookies";
                options.defaultchallengescheme = "oidc";
            }).addcookie("cookies")
               .addopenidconnect("oidc", options =>
               {
                   options.signinscheme = "cookies";

                   options.authority = "http://localhost:54660/identityserver";
                   options.requirehttpsmetadata = false;

                   options.clientid = "mvc";
                   options.clientsecret = "secret";
                   options.responsetype = "code id_token";

                   options.savetokens = true;
                   options.getclaimsfromuserinfoendpoint = true;

                   options.scope.add("api1");
                   options.scope.add("offline_access");
               })
                    .addidentityserverauthentication("bearer", options =>
                     {
                         options.authority = "http://localhost:54660/identityserver";
                         options.requirehttpsmetadata = false;
                         options.apisecret = "secret123";
                         options.apiname = "api1";
                         options.supportedtokens= supportedtokens.both;
                     });

            services.addauthorization(option =>
            {
                //默认 只写 [authorize],表示使用oidc进行认证
                option.defaultpolicy = new authorizationpolicybuilder("oidc").requireauthenticateduser().build();
                //apicontroller使用这个  [authorize(policy = "apipolicy")],使用jwt认证方案
                option.addpolicy("apipolicy", policy =>
                {
                    policy.addauthenticationschemes(jwtbearerdefaults.authenticationscheme);
                    policy.requireauthenticateduser();
                });
            });
        }

        public void configure(iapplicationbuilder app, ihostingenvironment env)
        {
            //var options = new forwardedheadersoptions
            //{
            //    forwardedheaders = forwardedheaders.xforwardedfor | forwardedheaders.xforwardedproto | forwardedheaders.xforwardedhost,
            //    forwardlimit = 1
            //};
            //options.knownnetworks.clear();
            //options.knownproxies.clear();
            //app.useforwardedheaders(options);
            //if (env.isdevelopment())
            //{
            //    app.usedeveloperexceptionpage();
            //}
            //else
            //{
            //    app.useexceptionhandler("/home/error");
            //}

            app.useauthentication();

            app.usestaticfiles();
            app.usemvc(routes =>
            {
                routes.maproute(
                    name: "default",
                    template: "myapi/{controller=maccount}/{action=login}/{id?}");

            });
        }
    }
view code

主要添加了oidc认证配置和配置验证策略来同时支持oidc认证和bearer认证。

修改identitycontroller中的[authorize]特性为[authorize(policy = "apipolicy")]

 

 

 依次使用调试-开始执行(不调试)并选择项目名称启动quickstartidentityserver,gateway,mvcclient,api,启动方式如图

IdentityServer4与ocelot实现认证与客户端统一入口

应该可以看到gateway启动后直接显示了identityserver的默认首页

IdentityServer4与ocelot实现认证与客户端统一入口

 

在浏览器输入http://localhost:54660/mvcclient/home/index进入mvcclient

IdentityServer4与ocelot实现认证与客户端统一入口

 

点击secure进入需要授权的页面,这时候会跳转到登陆页面(才怪

IdentityServer4与ocelot实现认证与客户端统一入口

实际上我们会遇到一个错误,这是因为ocelot做网关时下游服务获取到的host实际为localhost:50891,而在identityserver中设置的redirecturis为网关的54660,我们可以通过ocelot转发x-forwarded-host头,并在客户端通过useforwardedheaders中间件来获取头。但是useforwardedheaders中间件为了防止ip欺骗攻击需要设置knownnetworks和knownproxies以实现严格匹配。当然也可以通过清空knownnetworks和knownproxies的默认值来不执行严格匹配,这样一来就有可能受到攻击。所以这里我直接使用硬编码的方式设置host,实际使用时应从配置文件获取,同时修改mvcclient和api相关代码

            app.use(async (context, next) =>
            {
                context.request.host = hoststring.fromuricomponent(new uri("http://localhost:54660/"));
                await next.invoke();
            });
            //var options = new forwardedheadersoptions
            //{
            //    forwardedheaders = forwardedheaders.xforwardedfor | forwardedheaders.xforwardedproto | forwardedheaders.xforwardedhost,
            //    forwardlimit = 1
            //};
            //options.knownnetworks.clear();
            //options.knownproxies.clear();
            //app.useforwardedheaders(options);

在反向代理情况下通过转发x-forwarded-host头来获取host地址应该时常见设置不知道还有没有其他更好的解决办法。

再次启动mvcclient并输入http://localhost:54660/mvcclient/home/secure。

IdentityServer4与ocelot实现认证与客户端统一入口

 

 使用bob,password登陆一下

IdentityServer4与ocelot实现认证与客户端统一入口

点击yes, allow返回http://localhost:54660/mvcclient/home/secure,此时可以查看到登陆后的信息

IdentityServer4与ocelot实现认证与客户端统一入口

 

 

 分别点击call api using user token和call api using application identity来验证一下通过access_token和clientcredent模式请求来请求api

IdentityServer4与ocelot实现认证与客户端统一入口

 

成功获取到返回值。

 输入http://localhost:54660/myapi/home/index来查看api情况

IdentityServer4与ocelot实现认证与客户端统一入口

请求成功。

点击secure从api项目查看用户信息,此时展示信息应该和mvcclient一致

IdentityServer4与ocelot实现认证与客户端统一入口

 

嗯,并没有看到用户信息而是又到了授权页.....,这是因为.netcore使用dataprotection来保护数据(),api项目不能解析由mvcclient生成的cookie,而被重定向到了identityserver服务中。

在mvcclient和api的configureservices下添加如下代码来同步密钥环。

            services.adddataprotection(options => options.applicationdiscriminator = "00000").setapplicationname("00000");

 

 

再次启动mvcclient和api项目并在浏览器中输入http://localhost:54660/mvcclient/home/secure,此时被要求重新授权,点击yes, allow后看到用户信息

再输入http://localhost:54660/myapi/home/secure从api项目查看用户信息

IdentityServer4与ocelot实现认证与客户端统一入口

 

 

 分别点击call api using user token和call api using application identity来验证一下通过access_token和clientcredent模式请求来请求api

IdentityServer4与ocelot实现认证与客户端统一入口

请求成功。

 

如此我们便实现了通过ocelot实现统一入口,通过identityserver4来实现认证的需求

 

源代码 https://github.com/saber-wang/quickstart5_hybridandapi

参考