身份认证

在本文中,你将学会基于asp.net core 用Ocelot搭建一个简单的API网关。或许你会问:什么是API网关?我们先看下面的截图:

在上一节中,我已经介绍了使用Ocelot来搭建网关的最简单的demo。在本文,将继续在.NET Core 中构建网关的主题。稍后将向您展示有关身份验证(authentication)的内容。

API服务是需要保护的资源,我们应该尽可能的保证他们的安全。

通常,我们会使用security来保证我们项目的安全,aspnet/security代码库囊括了基于asp.net core 设计的安全和授权中间件。它总是让事情变得简单。

那么我们在项目中引入API网关会不会导致巨大的改变?答案是,不会。我们的修改微乎其微,但是却让安全和授权变得简单。

先看下面的截图。


截图显示,当我们访问我们的服务,API网关会让我们首先访问其授权模块。我们必须访问授权服务获得访问令牌(access token),然后用访问令牌访问受保护的服务。

可能你门当中有些人会将授权服务独立出来,这意味客户端得先访问授权服务获得访问令牌。然后携带令牌访问API网关。毫无疑问这样做没问题,但是我建议将授权服务和其他服务放在一起。

网关(APIGateway)是我们所有服务的入口,我们只是让客户端无需身份验证即可访问身份验证服务(auth service)。

OK,开始我们的表演。

我会使用上一个demo的部分内容,便于理解。

Step1

项目名称

项目类型

描述

APIGateway

ASP.NET Core Empty

示例的入口

CustomersAPIServices

ASP.NET Core Web API

API 服务处理消费者的操作

AuthServer

ASP.NET Core Web API

API 服务处理授权操作

ClientApp

Console App

控制台程序模拟客户端

APIGateway和CustomerAPIServices和上篇文章的例子一样,你可以在APIGatewayBasicDemo获得。

Step2

创建AuthServer,AuthServer主要是为request请求生成访问令牌(access token),我们需要添加一个方法处理。

[HttpGet]  
public IActionResult Get(string name, string pwd)  
{  
    //just hard code here.  
    if (name == "catcher" && pwd == "123")  
    {  
        var now = DateTime.UtcNow;
        var claims = new Claim[]  
        {  
            new Claim(JwtRegisteredClaimNames.Sub, name),  
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),  
            new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)  
        };  
        var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_settings.Value.Secret));  
        var tokenValidationParameters = new TokenValidationParameters  
        {  
            ValidateIssuerSigningKey = true,  
            IssuerSigningKey = signingKey,  
            ValidateIssuer = true,  
            ValidIssuer = _settings.Value.Iss,  
            ValidateAudience = true,  
            ValidAudience = _settings.Value.Aud,  
            ValidateLifetime = true,  
            ClockSkew = TimeSpan.Zero,  
            RequireExpirationTime = true,  
        }; 
         var jwt = new JwtSecurityToken(  
            issuer: _settings.Value.Iss,  
            audience: _settings.Value.Aud,  
            claims: claims,  
            notBefore: now,  
            expires: now.Add(TimeSpan.FromMinutes(2)),  
            signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)  
        );  
        var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);  
        var responseJson = new  
        {  
            access_token = encodedJwt,  
            expires_in = (int)TimeSpan.FromMinutes(2).TotalSeconds  
        }; 
        return Json(responseJson);  
    } 
    else  
    {  
        return Json("");  
    }  
}  

  在验证用户时。我使用硬编码将用户名写死,因为对于本文这个不是那么重要。

  这样我们就完成了授权服务,现在跑起来。



Step3

回到CustomersAPIServices项目,我们应该保护这个服务。

修改Startup,我们可以使用授权。在这我用JwtBearer进行授权,我会给TestKey设置默认的授权方案。TestKey我将在下面提到。

public void ConfigureServices(IServiceCollection services)  
{  
    var audienceConfig = Configuration.GetSection("Audience");  

    var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));  
    var tokenValidationParameters = new TokenValidationParameters  
    {  
        ValidateIssuerSigningKey = true,  
        IssuerSigningKey = signingKey,  
        ValidateIssuer = true,  
        ValidIssuer = audienceConfig["Iss"],  
        ValidateAudience = true,  
        ValidAudience = audienceConfig["Aud"],  
        ValidateLifetime = true,  
        ClockSkew = TimeSpan.Zero,  
        RequireExpirationTime = true,  
    };  

    services.AddAuthentication()  
            .AddJwtBearer("TestKey", x =>  
             {  
                 x.RequireHttpsMetadata = false;  
                 x.TokenValidationParameters = tokenValidationParameters;  
             });  

    services.AddMvc();  
}  
public void Configure(IApplicationBuilder app)  
{              
    app.UseAuthentication();  
    app.UseMvc();  
} 

  接下来,对需要保护的方法,添加Authorize。

[Authorize]  
[HttpGet]  
public IEnumerable<string> Get()  
{  
    return new string[] { "Catcher Wong", "James Li" };  
}  

注意

该项目基于asp.net core 2.0. 如果你的项目是1.X,可能有些不同,建议用迁移迁移到2.0.以上。接下来就是见证奇迹的时候了。


Step4

最重要的步骤来了,在APIGateway中配置授权。

public void ConfigureServices(IServiceCollection services)  
{  
    var audienceConfig = Configuration.GetSection("Audience");  

    var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));  
    var tokenValidationParameters = new TokenValidationParameters  
    {  
        ValidateIssuerSigningKey = true,  
        IssuerSigningKey = signingKey,  
        ValidateIssuer = true,  
        ValidIssuer = audienceConfig["Iss"],  
        ValidateAudience = true,  
        ValidAudience = audienceConfig["Aud"],  
        ValidateLifetime = true,  
        ClockSkew = TimeSpan.Zero,  
        RequireExpirationTime = true,  
    };  

    services.AddAuthentication()  
            .AddJwtBearer("TestKey", x =>  
             {  
                 x.RequireHttpsMetadata = false;  
                 x.TokenValidationParameters = tokenValidationParameters;  
             });  

    services.AddOcelot(Configuration);  
}  

  这个配置的大部分代码和Customer Service一样。

  当Ocelot启动,它会查看ReRoutes》AuthenticationOptions 》AuthenticationProviderKey 的值,

检查该值是否被授权服务注册,如果没有,Ocelot不会启动,如果有,Ocelot在执行时使用授权服务。

所以,我们得修改configuration.json,我们需要添加新的节点,并将其值赋为在Startup 类中定义的一样。

{  
    "DownstreamPathTemplate": "/api/customers",  
    "DownstreamScheme": "http",  
    "DownstreamHost": "localhost",  
    "DownstreamPort": 9001,  
    "UpstreamPathTemplate": "/customers",  
    "UpstreamHttpMethod": [ "Get" ],  
    "AuthenticationOptions": {  
        "AuthenticationProviderKey": "TestKey",  
        "AllowedScopes": []  
    }  
}  

  启动服务。



注意

当你启动项目时遇到下面的错误,你应该检查你的代码,查看AddJwtBearer方法是否指明授权方案。

Unhandled Exception: System.InvalidOperationException: Scheme already exists: Bearer

Step5

我们已经准备完毕,我们用我们的客户端模拟APIGateway的某些请求。

我们先添加获得访问令牌的方法。

private static string GetJwt()  
{  
    HttpClient client = new HttpClient();  
    client.BaseAddress = new Uri( "http://localhost:9000");  
    client.DefaultRequestHeaders.Clear();  
    var res2 = client.GetAsync("/api/auth?name=catcher&pwd=123").Result;  
    dynamic jwt = JsonConvert.DeserializeObject(res2.Content.ReadAsStringAsync().Result);  
    return jwt.access_token;  
} 

接下来,编写通过APIGateway访问Customer Service方法的代码。

static void Main(string[] args)  
{  
    HttpClient client = new HttpClient();  

    client.DefaultRequestHeaders.Clear();  
    client.BaseAddress = new Uri("http://localhost:9000");  

    // 1. without access_token will not access the service  
    //    and return 401 .  
    var resWithoutToken = client.GetAsync("/customers").Result;  

    //print something here   

    //2. with access_token will access the service  
    //   and return result.  
    client.DefaultRequestHeaders.Clear();  
    var jwt = GetJwt();  

    client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}");  
    var resWithToken = client.GetAsync("/customers").Result;  

    //print something here   

    //3. visit no auth service   
    client.DefaultRequestHeaders.Clear();  
    var res = client.GetAsync("/customers/1").Result;  

    //print something here   
      
    Console.Read();  
}  

运行结果。

  完工。

  源码在此

总结

本文介绍在使用Ocelot搭建网关时,如何配置身份认证(authentication)。希望能够对你有所帮助。

版权声明: 本文为智客工坊「楠木大叔」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

results matching ""

    No results matching ""