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

Ocelot(四)- 认证与授权

程序员文章站 2022-04-14 18:48:37
本文是我关于Ocelot系列文章的第四篇,**认证与授权**。在前面的系列文章中,我们的下游服务接口都是公开的,没有经过任何的认证,只要知道接口的调用方法,任何人都可以随意调用,因此,很容易就造成信息泄露或者服务被攻击。 ......

ocelot(四)- 认证与授权

作者:markjiang7m2
原文地址:
源码地址:https://gitee.com/sevenm2/ocelotdemo

本文是我关于ocelot系列文章的第四篇,认证与授权。在前面的系列文章中,我们的下游服务接口都是公开的,没有经过任何的认证,只要知道接口的调用方法,任何人都可以随意调用,因此,很容易就造成信息泄露或者服务被攻击。

正如,我要找willing干活之前,我得先到hr部门那里登记并且拿到属于我自己的工卡,然后我带着我的工卡去找willing,亮出我是公司员工的身份,并且有权利要求他帮我完成一个任务。

在这里集成一套 .net core的服务认证框架identityserver4,以及如何在ocelot中接入identityserver4的认证与授权。

跟上一篇ocelot(三)- 服务发现文章中的consul类似,这一个是关于ocelot的系列文章,我暂时也不打算详细展开说明identityserver4,在本文中也是使用identityserver4最简单的client认证模式。

关于更多的ocelot功能介绍,可以查看我的系列文章

本文中涉及案例的完整代码都可以从我的代码仓库进行下载。

identityserver4使用

identityserver4有多种认证模式,包括用户密码、客户端等等,我这里只需要实现identityserver4的验证过程即可,因此,我选择了使用最简单的客户端模式。

首先我们来看,当没有ocelot网关时系统是如何使用identityserver4进行认证的。

Ocelot(四)- 认证与授权

客户端需要先想identityserver请求认证,获得一个token,然后再带着这个token向下游服务发出请求。

我尝试根据流程图搭建出这样的认证服务。

创建identityserver服务端
新建一个空的asp.net core web api项目,因为这个项目只做identityserver服务端,因此,我将controller也直接删除掉。

使用nuget添加identityserver4,可以直接使用nuget包管理器搜索identityserver4进行安装,或者通过vs中内置的powershell执行下面的命令行

install-package identityserver4

Ocelot(四)- 认证与授权

appsettings.json中添加identityserver4的配置

{
  "logging": {
    "loglevel": {
      "default": "warning"
    }
  },
  "ssoconfig": {
    "apiresources": [
      {
        "name": "identityapiservice",
        "displayname": "identityapiservicename"
      }
    ],
    "clients": [
      {
        "clientid": "mark",
        "clientsecrets": [ "markjiang7m2" ],
        "allowedgranttypes": "clientcredentials",
        "allowedscopes": [ "identityapiservice" ]
      }
    ]
  },
  "allowedhosts": "*"
}

apiresources为数组类型,表示identityserver管理的所有的下游服务列表

  • name: 下游服务名称
  • displayname: 下游服务别名

clients为数组类型,表示identityserver管理的所有的上游客户端列表

  • clientid: 客户端id
  • clientsecrets: 客户端对应的密钥
  • allowedgranttypes: 该客户端支持的认证模式,目前支持如下:
    • implicit
    • implicitandclientcredentials
    • code
    • codeandclientcredentials
    • hybrid
    • hybridandclientcredentials
    • clientcredentials
    • resourceownerpassword
    • resourceownerpasswordandclientcredentials
    • deviceflow
  • allowedscopes: 该客户端支持访问的下游服务列表,必须是在apiresources列表中登记的

新建一个类用于读取identityserver4的配置

using identityserver4.models;
using microsoft.extensions.configuration;
using system;
using system.collections.generic;
using system.linq;
using system.threading.tasks;

namespace identityserver
{
    public class ssoconfig
    {
        public static ienumerable<apiresource> getapiresources(iconfigurationsection section)
        {
            list<apiresource> resource = new list<apiresource>();

            if (section != null)
            {
                list<apiconfig> configs = new list<apiconfig>();
                section.bind("apiresources", configs);
                foreach (var config in configs)
                {
                    resource.add(new apiresource(config.name, config.displayname));
                }
            }

            return resource.toarray();
        }
        /// <summary>
        /// 定义受信任的客户端 client
        /// </summary>
        /// <returns></returns>
        public static ienumerable<client> getclients(iconfigurationsection section)
        {
            list<client> clients = new list<client>();

            if (section != null)
            {
                list<clientconfig> configs = new list<clientconfig>();
                section.bind("clients", configs);
                foreach (var config in configs)
                {
                    client client = new client();
                    client.clientid = config.clientid;
                    list<secret> clientsecrets = new list<secret>();
                    foreach (var secret in config.clientsecrets)
                    {
                        clientsecrets.add(new secret(secret.sha256()));
                    }
                    client.clientsecrets = clientsecrets.toarray();

                    granttypes granttypes = new granttypes();
                    var allowedgranttypes = granttypes.gettype().getproperty(config.allowedgranttypes);
                    client.allowedgranttypes = allowedgranttypes == null ? 
                        granttypes.clientcredentials : (icollection<string>)allowedgranttypes.getvalue(granttypes, null);

                    client.allowedscopes = config.allowedscopes.toarray();

                    clients.add(client);
                }
            }
            return clients.toarray();
        }
    }

    public class apiconfig
    {
        public string name { get; set; }
        public string displayname { get; set; }
    }

    public class clientconfig
    {
        public string clientid { get; set; }
        public list<string> clientsecrets { get; set; }
        public string allowedgranttypes { get; set; }
        public list<string> allowedscopes { get; set; }
    }
}

startup.cs中注入identityserver服务

public void configureservices(iservicecollection services)
{
    var section = configuration.getsection("ssoconfig");
    services.addidentityserver()
        .adddevelopersigningcredential()
        .addinmemoryapiresources(ssoconfig.getapiresources(section))
        .addinmemoryclients(ssoconfig.getclients(section));

    services.addmvc().setcompatibilityversion(compatibilityversion.version_2_2);
}

使用identityserver中间件

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

    app.useidentityserver();
    app.usemvc();
}

配置完成,接下来用debug模式看看identityserver是否可用,尝试向identityserver进行认证。因为需要使用post方式,而且在认证请求的body中加入认证信息,所以我这里借助postman工具完成。

请求路径:<host>+/connect/token

如果认证正确,会得到如下结果:

Ocelot(四)- 认证与授权

如果认证失败,则会返回如下:

Ocelot(四)- 认证与授权

这样,最简单的identityserver服务就配置完成了。当然,我刚刚为了快速验证identityserver服务是否搭建成功,所以使用的是debug模式,接下来要使用的话,还是要通过iis部署使用的,我这里就把identityserver服务部署到8005端口。

下游服务加入认证
ocelotdownapi项目中,使用nuget添加accesstokenvalidation包,可以直接使用nuget包管理器搜索identityserver4.accesstokenvalidation进行安装,或者通过vs中内置的powershell执行下面的命令行

install-package identityserver4.accesstokenvalidation

appsettings.json中加入identityserver服务信息

"identityserverconfig": {
    "serverip": "localhost",
    "serverport": 8005,
    "identityscheme": "bearer",
    "resourcename": "identityapiservice"
}

这里的identityapiservice就是在identityserver服务端配置apiresources列表中登记的其中一个下游服务。

startup.cs中读取identityserver服务信息,加入identityserver验证

public void configureservices(iservicecollection services)
{
    identityserverconfig identityserverconfig = new identityserverconfig();
    configuration.bind("identityserverconfig", identityserverconfig);
    services.addauthentication(identityserverconfig.identityscheme)
        .addidentityserverauthentication(options =>
        {
            options.requirehttpsmetadata = false;
            options.authority = $"http://{identityserverconfig.ip}:{identityserverconfig.port}";
            options.apiname = identityserverconfig.resourcename;
        }
        );

    services.addmvc().setcompatibilityversion(compatibilityversion.version_2_2);
}

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

    app.usemvc();
}

根据前面的配置,我们添加一个需要授权的下游服务api
注意添加属性[authorize]
因为我这里只是为了演示identityserver的认证流程,所以我只是在其中一个api接口中添加该属性,如果还有其他接口需要整个认证,就需要在其他接口中添加该属性,如果是这个controller所有的接口都需要identityserver认证,那就直接在类名前添加该属性。

using microsoft.aspnetcore.authorization;
// get api/ocelot/identitywilling
[httpget("identitywilling")]
[authorize]
public async task<iactionresult> identitywilling(int id)
{
    var result = await task.run(() =>
    {
        responseresult response = new responseresult()
        { comment = $"我是willing,既然你是我公司员工,那我就帮你干活吧, host: {httpcontext.request.host.value}, path: {httpcontext.request.path}" };
        return response;
    });
    return ok(result);
}

重新打包ocelotdownapi项目,并发布到8001端口。

首先,像之前那样直接请求api,得到如下结果:

Ocelot(四)- 认证与授权

得到了401的状态码,即未经授权。

因此,我必须先向identityserver请求认证并授权

Ocelot(四)- 认证与授权

然后将得到的tokenbearer的方式加入到向下游服务的请求当中,这样我们就可以得到了正确的结果

Ocelot(四)- 认证与授权

可能有些朋友在这里会有点疑惑,在postman中我们在authorization中加入这个token,但是在我们实际调用中该怎么加入token?

其实熟悉postman的朋友可能就知道怎么一回事,postman为了我们在使用过程中更加方便填入token信息而单独列出了authorization,实际上,最终还是会转换加入到请求头当中
这个请求头的key就是authorization,对应的值是bearer + (空格) + token

Ocelot(四)- 认证与授权

以上就是没有ocelot网关时,identityserver的认证流程。

案例五 ocelot集成identityserver服务

在上面的例子中,我是直接将下游服务暴露给客户端调用,当接入ocelot网关时,我们要达到内外互隔的特性,于是就把identityserver服务也托管到ocelot网关中,这样我们就能统一认证和服务请求时的入口。
于是,我们可以形成下面这个流程图:

Ocelot(四)- 认证与授权

根据流程图,我在ocelot reroutes中添加两组路由

{
    "downstreampathtemplate": "/connect/token",
    "downstreamscheme": "http",
    "downstreamhostandports": [
    {
        "host": "localhost",
        "port": 8005
    }
    ],
    "upstreampathtemplate": "/token",
    "upstreamhttpmethod": [ "post" ],
    "priority": 2
},
{
    "downstreampathtemplate": "/api/ocelot/identitywilling",
    "downstreamscheme": "http",
    "downstreamhostandports": [
    {
        "host": "localhost",
        "port": 8001
    }
    ],
    "upstreampathtemplate": "/ocelot/identitywilling",
    "upstreamhttpmethod": [ "get" ],
    "priority": 2
}

第一组是将identityserver服务进行托管,这样客户端就可以直接通过ocelot网关访问/token就可以进行认证,第二组是将下游服务进行托管

然后,也是按照之前例子的步骤,先通过http://localhost:4727/token认证,然后将得到的tokenbearer的方式加入到向下游服务的请求当中

Ocelot(四)- 认证与授权

Ocelot(四)- 认证与授权

结果也是跟我预想的是一致的,可以按照这样的流程进行身份认证。

但是!!!但是!!!但是!!!

当外面随便来一个人,跟前台说他要找我做一件事情,然后前台直接告诉他我的具体位置,就让他进公司找我了,然后当我接待他的时候,我才发现这个人根本就是来搞事的,拒绝他的请求。如果一天来这么几十号人,我还要不要正常干活了?

这明显就不符合实际应用场景,外面的人(客户端)在前台(ocelot)的时候,就需要进行身份认证(identityserver),只有通过认证的人才能进公司(路由),我才会接触到这个人(响应),这才叫专人做专事。

于是,认证流程改为下图:

Ocelot(四)- 认证与授权

准备下游服务

为了保证我的案例与上面这个认证流程是一致的,我就把前面在下游服务中的认证配置去掉。而且在实际生产环境中,客户端与下游服务的网络是隔断的,客户端只能通过网关的转发才能向下游服务发出请求。

ocelotdownapi项目

public void configureservices(iservicecollection services)
{
    //identityserverconfig identityserverconfig = new identityserverconfig();
    //configuration.bind("identityserverconfig", identityserverconfig);
    //services.addauthentication(identityserverconfig.identityscheme)
    //    .addidentityserverauthentication(options =>
    //    {
    //        options.requirehttpsmetadata = false;
    //        options.authority = $"http://{identityserverconfig.ip}:{identityserverconfig.port}";
    //        options.apiname = identityserverconfig.resourcename;
    //    }
    //    );

    services.addmvc().setcompatibilityversion(compatibilityversion.version_2_2);
}
public void configure(iapplicationbuilder app, ihostingenvironment env)
{
    if (env.isdevelopment())
    {
        app.usedeveloperexceptionpage();
    }
    //app.useauthentication();

    app.usemvc();
}

同时也把api接口中的[authorize]属性去除。

然后将ocelotdownapi项目重新打包,部署在80018002端口,作为两个独立的下游服务。

配置identityserver

回到identityserver项目的appsettings.json,在apiresources中另外添加两个服务

{
    "name": "identityapiservice8001",
    "displayname": "identityapiservice8001name"
},
{
    "name": "identityapiservice8002",
    "displayname": "identityapiservice8002name"
}

clients中添加两个client

{
    "clientid": "markfull",
    "clientsecrets": [ "markjiang7m2" ],
    "allowedgranttypes": "clientcredentials",
    "allowedscopes": [ "identityapiservice8001", "identityapiservice8002" ]
},
{
    "clientid": "marklimit",
    "clientsecrets": [ "123456" ],
    "allowedgranttypes": "clientcredentials",
    "allowedscopes": [ "identityapiservice8001" ]
}

这里我为了能让大家看出允许访问范围的效果,特意分配了两个不同的allowedscopes
使用markfull登录的客户端可以同时请求identityapiservice8001identityapiservice8002两个下游服务,而使用marklimit登录的客户端只允许请求identityapiservice8001服务。

ocelot集成identityserver认证

跟前面的例子一样,要支持identityserver认证,ocelotdemo项目就需要安装identityserver4.accesstokenvalidation包。

ocelotdemo项目的appsettings.json添加identityserver信息

"identityserverconfig": {
    "ip": "localhost",
    "port": 8005,
    "identityscheme": "bearer",
    "resources": [
      {
        "key": "apiservice8001",
        "name": "identityapiservice8001"
      },
      {
        "key": "apiservice8002",
        "name": "identityapiservice8002"
      }
    ]
}

当然这个配置项的结构是任意的,我这里的resources数组配置的就是ocelot网关支持哪些服务的认证,name就是服务的名称,同时会唯一对应一个key

为了能更加方便读取identityserverconfig的信息,我定义了一个跟它同结构的类

public class identityserverconfig
{
    public string ip { get; set; }
    public string port { get; set; }
    public string identityscheme { get; set; }
    public list<apiresource> resources { get; set; }
}

public class apiresource
{
    public string key { get; set; }
    public string name { get; set; }
}

然后来到startup.csconfigureservices方法,就能很快地将identityserver信息进行注册

var identitybuilder = services.addauthentication();
identityserverconfig identityserverconfig = new identityserverconfig();
configuration.bind("identityserverconfig", identityserverconfig);
if (identityserverconfig != null && identityserverconfig.resources != null)
{
    foreach (var resource in identityserverconfig.resources)
    {
        identitybuilder.addidentityserverauthentication(resource.key, options => 
        {
            options.authority = $"http://{identityserverconfig.ip}:{identityserverconfig.port}";
            options.requirehttpsmetadata = false;
            options.apiname = resource.name;
            options.supportedtokens = supportedtokens.both;
        });
    }
}

configure方法中添加

app.useauthentication();

最后,就是配置ocelot.json文件。
reroutes中添加两组路由

{
    "downstreampathtemplate": "/api/ocelot/identitywilling",
    "downstreamscheme": "http",
    "downstreamhostandports": [
    {
        "host": "localhost",
        "port": 8001
    }
    ],
    "upstreampathtemplate": "/ocelot/8001/identitywilling",
    "upstreamhttpmethod": [ "get" ],
    "priority": 2,
    "authenticationoptions": {
    "authenticationproviderkey": "apiservice8001",
    "allowedscopes": []
    }
},
{
    "downstreampathtemplate": "/api/ocelot/identitywilling",
    "downstreamscheme": "http",
    "downstreamhostandports": [
    {
        "host": "localhost",
        "port": 8002
    }
    ],
    "upstreampathtemplate": "/ocelot/8002/identitywilling",
    "upstreamhttpmethod": [ "get" ],
    "priority": 2,
    "authenticationoptions": {
    "authenticationproviderkey": "apiservice8002",
    "allowedscopes": []
    }
}

跟其他普通路由相比,这两组路由都多了一个authenticationoptions属性,它里面的authenticationproviderkey就是我们在前面configureservices方法中登记过的key

我们来捋顺一下这个路由跟认证授权过程。以markfull的id和这里的第一组路由为例。

  1. 客户端拿着markfull的clientid向identityserver(http://localhost:4727/token)进行认证,得到了一个的token
  2. 客户端带着这个token,因此有了markfull的身份,请求url地址http://localhost:4727/ocelot/8001/identitywilling
  3. ocelot网关接收到请求,根据路由表找到了认证支持关键字为apiservice8001,从而得到了对应的identityserver服务信息:identityserver服务地址为http://localhost:8005,下游服务名称为identityapiservice8001
  4. ocelot带着token向identityserver服务(http://localhost:8005)进行配对,即查看markfull身份的访问范围是否包含了identityapiservice8001服务
  5. ocelot认证过markfull是允许访问的,将请求转发到下游服务中,根据路由配置,下游服务地址为http://localhost:8001/api/ocelot/identitywilling

下面我将ocelot运行起来,并通过postman进行验证。

markfull身份认证
使用markfullclientid向identityserver进行认证

Ocelot(四)- 认证与授权

向8001请求
将得到的token加入到请求中,请求url地址http://localhost:4727/ocelot/8001/identitywilling,得到下游服务返回的响应结果

Ocelot(四)- 认证与授权

向8002请求
将得到的token加入到请求中,请求url地址http://localhost:4727/ocelot/8002/identitywilling,得到下游服务返回的响应结果

Ocelot(四)- 认证与授权

然后,更换marklimit身份再验证一遍

marklimit身份认证
使用marklimitclientid向identityserver进行认证

Ocelot(四)- 认证与授权

向8001请求
将得到的token加入到请求中,请求url地址http://localhost:4727/ocelot/8001/identitywilling,得到下游服务返回的响应结果

Ocelot(四)- 认证与授权

向8002请求
将得到的token加入到请求中,请求url地址http://localhost:4727/ocelot/8002/identitywilling,此时,我们得到了401的状态码,即未授权。

Ocelot(四)- 认证与授权

总结

在这篇文章中就跟大家介绍了基于identityserver4为认证服务器的ocelot认证与授权,主要是通过一些案例的实践,让大家理解ocelot对客户端身份的验证过程,使用了identityserver中最简单的客户端认证模式,因为这种模式下identityserver的认证没有复杂的层级关系。但通常在我们实际开发时,更多的可能是通过用户密码等方式进行身份认证的,之后我会尽快给大家分享关于identityserver如何使用其它模式进行认证。今天就先跟大家介绍到这里,希望大家能持续关注我们。