mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 18:22:49 +08:00
hacky auth working
This commit is contained in:
parent
bd07af6926
commit
112a9c303e
46
README.md
46
README.md
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
[](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
[ Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results)
|
||||||
|
|
||||||
Attempt at a .NET Api Gateway
|
Attempt at a .NET Api Gateway
|
||||||
|
|
||||||
This project is aimed at people using .NET running
|
This project is aimed at people using .NET running
|
||||||
@ -101,15 +103,14 @@ Currently this is the only way to get configuration into Ocelot.
|
|||||||
};
|
};
|
||||||
|
|
||||||
services.AddOcelotOutputCaching(settings);
|
services.AddOcelotOutputCaching(settings);
|
||||||
services.AddOcelotFileConfiguration(Configuration);
|
services.AddOcelot(Configuration);
|
||||||
services.AddOcelot();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
||||||
|
|
||||||
app.UseOcelot();
|
await app.UseOcelot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,6 +387,43 @@ In orde to use caching on a route in your ReRoute configuration add this setting
|
|||||||
|
|
||||||
In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds.
|
In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds.
|
||||||
|
|
||||||
|
## Administration
|
||||||
|
|
||||||
|
Ocelot supports changing configuration during runtime via an authenticated HTTP API. The API is authenticated
|
||||||
|
using bearer tokens that you request from iteself. This support is provided by the amazing IdentityServer
|
||||||
|
project that I have been using for a few years now. Check them out.
|
||||||
|
|
||||||
|
In order to enable the administration section you need to do a few things. First of all add this to your
|
||||||
|
initial configuration.json. The value can be anything you want and it is obviously reccomended don't use
|
||||||
|
a url you would like to route through with Ocelot as this will not work. The administration uses the
|
||||||
|
MapWhen functionality of asp.net core and all requests to root/administration will be sent there not
|
||||||
|
to the Ocelot middleware.
|
||||||
|
|
||||||
|
"GlobalConfiguration": {
|
||||||
|
"AdministrationPath": "/administration"
|
||||||
|
}
|
||||||
|
|
||||||
|
This will get the admin area set up but not the authentication. You need to set 3 environmental variables.
|
||||||
|
|
||||||
|
OCELOT_USERNAME
|
||||||
|
OCELOT_HASH
|
||||||
|
OCELOT_SALT
|
||||||
|
|
||||||
|
These need to be the admin username you want to use with Ocelot and the hash and salt of the password you want to
|
||||||
|
use given hashing algorythm. When requesting bearer tokens for use with the administration api you will need to
|
||||||
|
supply username and password.
|
||||||
|
|
||||||
|
In order to create a hash and salt of your password please check out HashCreationTests.should_create_hash_and_salt() this technique is based on MS doc I found online TODO find and link...
|
||||||
|
|
||||||
|
OK next thing is to get this config into Ocelot...
|
||||||
|
|
||||||
|
|
||||||
|
At the moment Ocelot supports really limited options in terms of users and authentication for the admin API. At
|
||||||
|
least your stuff needs to be hashed!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Ocelot Middleware injection and overrides
|
## Ocelot Middleware injection and overrides
|
||||||
|
|
||||||
Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware
|
Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware
|
||||||
|
@ -1 +1 @@
|
|||||||
{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null},{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/test","UpstreamHttpMethod":"get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":"RequestId","ServiceDiscoveryProvider":{"Provider":"test","Host":"127.0.0.1","Port":0},"AdministrationPath":"/administration"}}
|
{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}}
|
22
src/Ocelot/Configuration/Authentication/HashMatcher.cs
Normal file
22
src/Ocelot/Configuration/Authentication/HashMatcher.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Authentication
|
||||||
|
{
|
||||||
|
public class HashMatcher : IHashMatcher
|
||||||
|
{
|
||||||
|
public bool Match(string password, string salt, string hash)
|
||||||
|
{
|
||||||
|
byte[] s = Convert.FromBase64String(salt);
|
||||||
|
|
||||||
|
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
|
||||||
|
password: password,
|
||||||
|
salt: s,
|
||||||
|
prf: KeyDerivationPrf.HMACSHA256,
|
||||||
|
iterationCount: 10000,
|
||||||
|
numBytesRequested: 256 / 8));
|
||||||
|
|
||||||
|
return hashed == hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/Ocelot/Configuration/Authentication/IHashMatcher.cs
Normal file
7
src/Ocelot/Configuration/Authentication/IHashMatcher.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Ocelot.Configuration.Authentication
|
||||||
|
{
|
||||||
|
public interface IHashMatcher
|
||||||
|
{
|
||||||
|
bool Match(string password, string salt, string hash);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using IdentityServer4.Models;
|
||||||
|
using IdentityServer4.Validation;
|
||||||
|
using Ocelot.Configuration.Provider;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Authentication
|
||||||
|
{
|
||||||
|
public class OcelotResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
|
||||||
|
{
|
||||||
|
private readonly IHashMatcher _matcher;
|
||||||
|
private readonly IIdentityServerConfiguration _identityServerConfiguration;
|
||||||
|
|
||||||
|
public OcelotResourceOwnerPasswordValidator(IHashMatcher matcher, IIdentityServerConfiguration identityServerConfiguration)
|
||||||
|
{
|
||||||
|
_identityServerConfiguration = identityServerConfiguration;
|
||||||
|
_matcher = matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var user = _identityServerConfiguration.Users.FirstOrDefault(u => u.UserName == context.UserName);
|
||||||
|
|
||||||
|
if(user == null)
|
||||||
|
{
|
||||||
|
context.Result = new GrantValidationResult(
|
||||||
|
TokenRequestErrors.InvalidGrant,
|
||||||
|
"invalid custom credential");
|
||||||
|
}
|
||||||
|
else if(_matcher.Match(context.Password, user.Salt, user.Hash))
|
||||||
|
{
|
||||||
|
context.Result = new GrantValidationResult(
|
||||||
|
subject: "admin",
|
||||||
|
authenticationMethod: "custom");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Result = new GrantValidationResult(
|
||||||
|
TokenRequestErrors.InvalidGrant,
|
||||||
|
"invalid custom credential");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using IdentityServer4.AccessTokenValidation;
|
||||||
|
using IdentityServer4.Models;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Provider
|
||||||
|
{
|
||||||
|
public interface IIdentityServerConfiguration
|
||||||
|
{
|
||||||
|
string ApiName { get; }
|
||||||
|
bool RequireHttps { get; }
|
||||||
|
List<string> AllowedScopes { get; }
|
||||||
|
SupportedTokens SupportedTokens { get; }
|
||||||
|
string ApiSecret { get; }
|
||||||
|
string Description {get;}
|
||||||
|
bool Enabled {get;}
|
||||||
|
IEnumerable<string> AllowedGrantTypes {get;}
|
||||||
|
AccessTokenType AccessTokenType {get;}
|
||||||
|
bool RequireClientSecret {get;}
|
||||||
|
List<User> Users {get;}
|
||||||
|
}
|
||||||
|
}
|
@ -1,49 +1,12 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using IdentityServer4.AccessTokenValidation;
|
using IdentityServer4.AccessTokenValidation;
|
||||||
using IdentityServer4.Models;
|
using IdentityServer4.Models;
|
||||||
using IdentityServer4.Test;
|
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Provider
|
namespace Ocelot.Configuration.Provider
|
||||||
{
|
{
|
||||||
public class HardCodedIdentityServerConfigurationProvider : IIdentityServerConfigurationProvider
|
public class IdentityServerConfiguration : IIdentityServerConfiguration
|
||||||
{
|
|
||||||
public IdentityServerConfiguration Get()
|
|
||||||
{
|
|
||||||
var url = "";
|
|
||||||
return new IdentityServerConfiguration(
|
|
||||||
url,
|
|
||||||
"admin",
|
|
||||||
false,
|
|
||||||
SupportedTokens.Both,
|
|
||||||
"secret",
|
|
||||||
new List<string> {"admin", "openid", "offline_access"},
|
|
||||||
"Ocelot Administration",
|
|
||||||
true,
|
|
||||||
GrantTypes.ResourceOwnerPassword,
|
|
||||||
AccessTokenType.Jwt,
|
|
||||||
false,
|
|
||||||
new List<TestUser> {
|
|
||||||
new TestUser
|
|
||||||
{
|
|
||||||
Username = "admin",
|
|
||||||
Password = "admin",
|
|
||||||
SubjectId = "admin",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IIdentityServerConfigurationProvider
|
|
||||||
{
|
|
||||||
IdentityServerConfiguration Get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class IdentityServerConfiguration
|
|
||||||
{
|
{
|
||||||
public IdentityServerConfiguration(
|
public IdentityServerConfiguration(
|
||||||
string identityServerUrl,
|
|
||||||
string apiName,
|
string apiName,
|
||||||
bool requireHttps,
|
bool requireHttps,
|
||||||
SupportedTokens supportedTokens,
|
SupportedTokens supportedTokens,
|
||||||
@ -54,9 +17,8 @@ namespace Ocelot.Configuration.Provider
|
|||||||
IEnumerable<string> grantType,
|
IEnumerable<string> grantType,
|
||||||
AccessTokenType accessTokenType,
|
AccessTokenType accessTokenType,
|
||||||
bool requireClientSecret,
|
bool requireClientSecret,
|
||||||
List<TestUser> users)
|
List<User> users)
|
||||||
{
|
{
|
||||||
IdentityServerUrl = identityServerUrl;
|
|
||||||
ApiName = apiName;
|
ApiName = apiName;
|
||||||
RequireHttps = requireHttps;
|
RequireHttps = requireHttps;
|
||||||
SupportedTokens = supportedTokens;
|
SupportedTokens = supportedTokens;
|
||||||
@ -70,7 +32,6 @@ namespace Ocelot.Configuration.Provider
|
|||||||
Users = users;
|
Users = users;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string IdentityServerUrl { get; private set; }
|
|
||||||
public string ApiName { get; private set; }
|
public string ApiName { get; private set; }
|
||||||
public bool RequireHttps { get; private set; }
|
public bool RequireHttps { get; private set; }
|
||||||
public List<string> AllowedScopes { get; private set; }
|
public List<string> AllowedScopes { get; private set; }
|
||||||
@ -80,7 +41,7 @@ namespace Ocelot.Configuration.Provider
|
|||||||
public bool Enabled {get;private set;}
|
public bool Enabled {get;private set;}
|
||||||
public IEnumerable<string> AllowedGrantTypes {get;private set;}
|
public IEnumerable<string> AllowedGrantTypes {get;private set;}
|
||||||
public AccessTokenType AccessTokenType {get;private set;}
|
public AccessTokenType AccessTokenType {get;private set;}
|
||||||
public bool RequireClientSecret = false;
|
public bool RequireClientSecret {get;private set;}
|
||||||
public List<TestUser> Users {get;private set;}
|
public List<User> Users {get;private set;}
|
||||||
}
|
}
|
||||||
}
|
}
|
17
src/Ocelot/Configuration/Provider/User.cs
Normal file
17
src/Ocelot/Configuration/Provider/User.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace Ocelot.Configuration.Provider
|
||||||
|
{
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public User(string subject, string userName, string hash, string salt)
|
||||||
|
{
|
||||||
|
Subject = subject;
|
||||||
|
UserName = userName;
|
||||||
|
Hash = hash;
|
||||||
|
Salt = salt;
|
||||||
|
}
|
||||||
|
public string Subject { get; private set; }
|
||||||
|
public string UserName { get; private set; }
|
||||||
|
public string Hash { get; private set; }
|
||||||
|
public string Salt { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ using Ocelot.Authentication.Handler.Factory;
|
|||||||
using Ocelot.Authorisation;
|
using Ocelot.Authorisation;
|
||||||
using Ocelot.Cache;
|
using Ocelot.Cache;
|
||||||
using Ocelot.Claims;
|
using Ocelot.Claims;
|
||||||
|
using Ocelot.Configuration.Authentication;
|
||||||
using Ocelot.Configuration.Creator;
|
using Ocelot.Configuration.Creator;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Parser;
|
using Ocelot.Configuration.Parser;
|
||||||
@ -41,7 +42,6 @@ namespace Ocelot.DependencyInjection
|
|||||||
{
|
{
|
||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
|
|
||||||
public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action<ConfigurationBuilderCachePart> settings)
|
public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action<ConfigurationBuilderCachePart> settings)
|
||||||
{
|
{
|
||||||
var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings);
|
var cacheManagerOutputCache = CacheFactory.Build<HttpResponseMessage>("OcelotOutputCache", settings);
|
||||||
@ -51,24 +51,23 @@ namespace Ocelot.DependencyInjection
|
|||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
public static IServiceCollection AddOcelotFileConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot)
|
|
||||||
|
public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot)
|
||||||
|
{
|
||||||
|
return AddOcelot(services, configurationRoot, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot, IIdentityServerConfiguration identityServerConfiguration)
|
||||||
{
|
{
|
||||||
services.Configure<FileConfiguration>(configurationRoot);
|
services.Configure<FileConfiguration>(configurationRoot);
|
||||||
services.AddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
|
services.AddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
|
||||||
services.AddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
|
services.AddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
|
||||||
services.AddSingleton<IConfigurationValidator, FileConfigurationValidator>();
|
services.AddSingleton<IConfigurationValidator, FileConfigurationValidator>();
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddOcelot(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
return AddOcelot(services, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddOcelot(this IServiceCollection services, IdentityServerConfiguration identityServerConfiguration)
|
|
||||||
{
|
|
||||||
if(identityServerConfiguration != null)
|
if(identityServerConfiguration != null)
|
||||||
{
|
{
|
||||||
|
services.AddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
|
||||||
|
services.AddSingleton<IHashMatcher, HashMatcher>();
|
||||||
services.AddIdentityServer()
|
services.AddIdentityServer()
|
||||||
.AddTemporarySigningCredential()
|
.AddTemporarySigningCredential()
|
||||||
.AddInMemoryApiResources(new List<ApiResource>
|
.AddInMemoryApiResources(new List<ApiResource>
|
||||||
@ -101,8 +100,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
Enabled = identityServerConfiguration.Enabled,
|
Enabled = identityServerConfiguration.Enabled,
|
||||||
RequireClientSecret = identityServerConfiguration.RequireClientSecret
|
RequireClientSecret = identityServerConfiguration.RequireClientSecret
|
||||||
}
|
}
|
||||||
})
|
}).AddResourceOwnerValidator<OcelotResourceOwnerPasswordValidator>();
|
||||||
.AddTestUsers(identityServerConfiguration.Users);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
services.AddMvcCore()
|
services.AddMvcCore()
|
||||||
|
@ -37,21 +37,7 @@ namespace Ocelot.Middleware
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
|
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
await builder.UseOcelot(new OcelotMiddlewareConfiguration(), null);
|
await builder.UseOcelot(new OcelotMiddlewareConfiguration());
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder,IdentityServerConfiguration identityServerConfiguration)
|
|
||||||
{
|
|
||||||
await builder.UseOcelot(new OcelotMiddlewareConfiguration(), identityServerConfiguration);
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder,OcelotMiddlewareConfiguration middlewareConfiguration)
|
|
||||||
{
|
|
||||||
await builder.UseOcelot(middlewareConfiguration, null);
|
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
@ -62,9 +48,9 @@ namespace Ocelot.Middleware
|
|||||||
/// <param name="builder"></param>
|
/// <param name="builder"></param>
|
||||||
/// <param name="middlewareConfiguration"></param>
|
/// <param name="middlewareConfiguration"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration, IdentityServerConfiguration identityServerConfiguration)
|
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration)
|
||||||
{
|
{
|
||||||
await CreateAdministrationArea(builder, identityServerConfiguration);
|
await CreateAdministrationArea(builder);
|
||||||
|
|
||||||
// This is registered to catch any global exceptions that are not handled
|
// This is registered to catch any global exceptions that are not handled
|
||||||
builder.UseExceptionHandlerMiddleware();
|
builder.UseExceptionHandlerMiddleware();
|
||||||
@ -168,10 +154,12 @@ namespace Ocelot.Middleware
|
|||||||
return ocelotConfiguration.Data;
|
return ocelotConfiguration.Data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task CreateAdministrationArea(IApplicationBuilder builder, IdentityServerConfiguration identityServerConfiguration)
|
private static async Task CreateAdministrationArea(IApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
var configuration = await CreateConfiguration(builder);
|
var configuration = await CreateConfiguration(builder);
|
||||||
|
|
||||||
|
var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration));
|
||||||
|
|
||||||
if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null)
|
if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null)
|
||||||
{
|
{
|
||||||
var webHostBuilder = (IWebHostBuilder)builder.ApplicationServices.GetService(typeof(IWebHostBuilder));
|
var webHostBuilder = (IWebHostBuilder)builder.ApplicationServices.GetService(typeof(IWebHostBuilder));
|
||||||
|
@ -29,7 +29,8 @@
|
|||||||
"CacheManager.Microsoft.Extensions.Logging": "0.9.2",
|
"CacheManager.Microsoft.Extensions.Logging": "0.9.2",
|
||||||
"Consul": "0.7.2.1",
|
"Consul": "0.7.2.1",
|
||||||
"Polly": "5.0.3",
|
"Polly": "5.0.3",
|
||||||
"IdentityServer4": "1.0.1"
|
"IdentityServer4": "1.0.1",
|
||||||
|
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0"
|
||||||
},
|
},
|
||||||
"runtimes": {
|
"runtimes": {
|
||||||
"win10-x64": {},
|
"win10-x64": {},
|
||||||
|
@ -139,8 +139,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
};
|
};
|
||||||
|
|
||||||
s.AddOcelotOutputCaching(settings);
|
s.AddOcelotOutputCaching(settings);
|
||||||
s.AddOcelotFileConfiguration(configuration);
|
s.AddOcelot(configuration);
|
||||||
s.AddOcelot();
|
|
||||||
})
|
})
|
||||||
.ConfigureLogging(l =>
|
.ConfigureLogging(l =>
|
||||||
{
|
{
|
||||||
|
@ -235,7 +235,7 @@ namespace Ocelot.IntegrationTests
|
|||||||
new KeyValuePair<string, string>("client_secret", "secret"),
|
new KeyValuePair<string, string>("client_secret", "secret"),
|
||||||
new KeyValuePair<string, string>("scope", "admin"),
|
new KeyValuePair<string, string>("scope", "admin"),
|
||||||
new KeyValuePair<string, string>("username", "admin"),
|
new KeyValuePair<string, string>("username", "admin"),
|
||||||
new KeyValuePair<string, string>("password", "admin"),
|
new KeyValuePair<string, string>("password", "secret"),
|
||||||
new KeyValuePair<string, string>("grant_type", "password")
|
new KeyValuePair<string, string>("grant_type", "password")
|
||||||
};
|
};
|
||||||
var content = new FormUrlEncodedContent(formData);
|
var content = new FormUrlEncodedContent(formData);
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CacheManager.Core;
|
using CacheManager.Core;
|
||||||
|
using IdentityServer4.AccessTokenValidation;
|
||||||
|
using IdentityServer4.Models;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@ -15,7 +18,7 @@ namespace Ocelot.ManualTest
|
|||||||
{
|
{
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
private IdentityServerConfiguration _identityServerConfig;
|
private IIdentityServerConfiguration _identityServerConfig;
|
||||||
|
|
||||||
public Startup(IHostingEnvironment env)
|
public Startup(IHostingEnvironment env)
|
||||||
{
|
{
|
||||||
@ -27,9 +30,6 @@ namespace Ocelot.ManualTest
|
|||||||
.AddEnvironmentVariables();
|
.AddEnvironmentVariables();
|
||||||
|
|
||||||
Configuration = builder.Build();
|
Configuration = builder.Build();
|
||||||
|
|
||||||
var identityServerConfigProvider = new HardCodedIdentityServerConfigurationProvider();
|
|
||||||
_identityServerConfig = identityServerConfigProvider.Get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IConfigurationRoot Configuration { get; }
|
public IConfigurationRoot Configuration { get; }
|
||||||
@ -46,15 +46,36 @@ namespace Ocelot.ManualTest
|
|||||||
};
|
};
|
||||||
|
|
||||||
services.AddOcelotOutputCaching(settings);
|
services.AddOcelotOutputCaching(settings);
|
||||||
services.AddOcelotFileConfiguration(Configuration);
|
|
||||||
services.AddOcelot(_identityServerConfig);
|
var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME");
|
||||||
|
var hash = Environment.GetEnvironmentVariable("OCELOT_HASH");
|
||||||
|
var salt = Environment.GetEnvironmentVariable("OCELOT_SALT");
|
||||||
|
|
||||||
|
_identityServerConfig = new IdentityServerConfiguration(
|
||||||
|
"admin",
|
||||||
|
false,
|
||||||
|
SupportedTokens.Both,
|
||||||
|
"secret",
|
||||||
|
new List<string> {"admin", "openid", "offline_access"},
|
||||||
|
"Ocelot Administration",
|
||||||
|
true,
|
||||||
|
GrantTypes.ResourceOwnerPassword,
|
||||||
|
AccessTokenType.Jwt,
|
||||||
|
false,
|
||||||
|
new List<User>
|
||||||
|
{
|
||||||
|
new User("admin", username, hash, salt)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
services.AddOcelot(Configuration, _identityServerConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
||||||
|
|
||||||
await app.UseOcelot(_identityServerConfig);
|
await app.UseOcelot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@
|
|||||||
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
|
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
|
||||||
"Microsoft.NETCore.App": "1.1.0",
|
"Microsoft.NETCore.App": "1.1.0",
|
||||||
"Consul": "0.7.2.1",
|
"Consul": "0.7.2.1",
|
||||||
"Polly": "5.0.3"
|
"Polly": "5.0.3",
|
||||||
|
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0"
|
||||||
},
|
},
|
||||||
|
|
||||||
"tools": {
|
"tools": {
|
||||||
|
33
test/Ocelot.UnitTests/Configuration/HashCreationTests.cs
Normal file
33
test/Ocelot.UnitTests/Configuration/HashCreationTests.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Configuration
|
||||||
|
{
|
||||||
|
public class HashCreationTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void should_create_hash_and_salt()
|
||||||
|
{
|
||||||
|
var password = "secret";
|
||||||
|
|
||||||
|
var salt = new byte[128 / 8];
|
||||||
|
|
||||||
|
using (var rng = RandomNumberGenerator.Create())
|
||||||
|
{
|
||||||
|
rng.GetBytes(salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
var storedSalt = Convert.ToBase64String(salt);
|
||||||
|
|
||||||
|
var storedHash = Convert.ToBase64String(KeyDerivation.Pbkdf2(
|
||||||
|
password: password,
|
||||||
|
salt: salt,
|
||||||
|
prf: KeyDerivationPrf.HMACSHA256,
|
||||||
|
iterationCount: 10000,
|
||||||
|
numBytesRequested: 256 / 8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs
Normal file
76
test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using Ocelot.Configuration.Authentication;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Configuration
|
||||||
|
{
|
||||||
|
public class HashMatcherTests
|
||||||
|
{
|
||||||
|
private string _password;
|
||||||
|
private string _hash;
|
||||||
|
private string _salt;
|
||||||
|
private bool _result;
|
||||||
|
private HashMatcher _hashMatcher;
|
||||||
|
|
||||||
|
public HashMatcherTests()
|
||||||
|
{
|
||||||
|
_hashMatcher = new HashMatcher();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_match_hash()
|
||||||
|
{
|
||||||
|
var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4=";
|
||||||
|
var salt = "zzWITpnDximUNKYLiUam/w==";
|
||||||
|
var password = "secret";
|
||||||
|
|
||||||
|
this.Given(x => GivenThePassword(password))
|
||||||
|
.And(x => GivenTheHash(hash))
|
||||||
|
.And(x => GivenTheSalt(salt))
|
||||||
|
.When(x => WhenIMatch())
|
||||||
|
.Then(x => ThenTheResultIs(true))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_not_match_hash()
|
||||||
|
{
|
||||||
|
var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4=";
|
||||||
|
var salt = "zzWITpnDximUNKYLiUam/w==";
|
||||||
|
var password = "secret1";
|
||||||
|
|
||||||
|
this.Given(x => GivenThePassword(password))
|
||||||
|
.And(x => GivenTheHash(hash))
|
||||||
|
.And(x => GivenTheSalt(salt))
|
||||||
|
.When(x => WhenIMatch())
|
||||||
|
.Then(x => ThenTheResultIs(false))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThePassword(string password)
|
||||||
|
{
|
||||||
|
_password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheHash(string hash)
|
||||||
|
{
|
||||||
|
_hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheSalt(string salt)
|
||||||
|
{
|
||||||
|
_salt = salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenIMatch()
|
||||||
|
{
|
||||||
|
_result = _hashMatcher.Match(_password, _salt, _hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheResultIs(bool expected)
|
||||||
|
{
|
||||||
|
_result.ShouldBe(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
using Ocelot.Configuration.Authentication;
|
||||||
|
using Xunit;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Moq;
|
||||||
|
using IdentityServer4.Validation;
|
||||||
|
using Ocelot.Configuration.Provider;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Configuration
|
||||||
|
{
|
||||||
|
public class OcelotResourceOwnerPasswordValidatorTests
|
||||||
|
{
|
||||||
|
private OcelotResourceOwnerPasswordValidator _validator;
|
||||||
|
private Mock<IHashMatcher> _matcher;
|
||||||
|
private string _userName;
|
||||||
|
private string _password;
|
||||||
|
private ResourceOwnerPasswordValidationContext _context;
|
||||||
|
private Mock<IIdentityServerConfiguration> _config;
|
||||||
|
private User _user;
|
||||||
|
|
||||||
|
public OcelotResourceOwnerPasswordValidatorTests()
|
||||||
|
{
|
||||||
|
_matcher = new Mock<IHashMatcher>();
|
||||||
|
_config = new Mock<IIdentityServerConfiguration>();
|
||||||
|
_validator = new OcelotResourceOwnerPasswordValidator(_matcher.Object, _config.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_success()
|
||||||
|
{
|
||||||
|
this.Given(x => GivenTheUserName("tom"))
|
||||||
|
.And(x => GivenThePassword("password"))
|
||||||
|
.And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx")))
|
||||||
|
.And(x => GivenTheMatcherReturns(true))
|
||||||
|
.When(x => WhenIValidate())
|
||||||
|
.Then(x => ThenTheUserIsValidated())
|
||||||
|
.And(x => ThenTheMatcherIsCalledCorrectly())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_fail_when_no_user()
|
||||||
|
{
|
||||||
|
this.Given(x => GivenTheUserName("bob"))
|
||||||
|
.And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx")))
|
||||||
|
.And(x => GivenTheMatcherReturns(true))
|
||||||
|
.When(x => WhenIValidate())
|
||||||
|
.Then(x => ThenTheUserIsNotValidated())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_fail_when_password_doesnt_match()
|
||||||
|
{
|
||||||
|
this.Given(x => GivenTheUserName("tom"))
|
||||||
|
.And(x => GivenThePassword("password"))
|
||||||
|
.And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx")))
|
||||||
|
.And(x => GivenTheMatcherReturns(false))
|
||||||
|
.When(x => WhenIValidate())
|
||||||
|
.Then(x => ThenTheUserIsNotValidated())
|
||||||
|
.And(x => ThenTheMatcherIsCalledCorrectly())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheMatcherIsCalledCorrectly()
|
||||||
|
{
|
||||||
|
_matcher
|
||||||
|
.Verify(x => x.Match(_password, _user.Salt, _user.Hash), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThePassword(string password)
|
||||||
|
{
|
||||||
|
_password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheUserIs(User user)
|
||||||
|
{
|
||||||
|
_user = user;
|
||||||
|
_config
|
||||||
|
.Setup(x => x.Users)
|
||||||
|
.Returns(new List<User>{_user});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheMatcherReturns(bool expected)
|
||||||
|
{
|
||||||
|
_matcher
|
||||||
|
.Setup(x => x.Match(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
|
||||||
|
.Returns(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheUserName(string userName)
|
||||||
|
{
|
||||||
|
_userName = userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenIValidate()
|
||||||
|
{
|
||||||
|
_context = new ResourceOwnerPasswordValidationContext
|
||||||
|
{
|
||||||
|
UserName = _userName,
|
||||||
|
Password = _password
|
||||||
|
};
|
||||||
|
_validator.ValidateAsync(_context).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheUserIsValidated()
|
||||||
|
{
|
||||||
|
_context.Result.IsError.ShouldBe(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheUserIsNotValidated()
|
||||||
|
{
|
||||||
|
_context.Result.IsError.ShouldBe(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,8 @@
|
|||||||
"Shouldly": "2.8.2",
|
"Shouldly": "2.8.2",
|
||||||
"TestStack.BDDfy": "4.3.2",
|
"TestStack.BDDfy": "4.3.2",
|
||||||
"Microsoft.AspNetCore.Authentication.OAuth": "1.1.0",
|
"Microsoft.AspNetCore.Authentication.OAuth": "1.1.0",
|
||||||
"Microsoft.DotNet.InternalAbstractions": "1.0.0"
|
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
|
||||||
|
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0"
|
||||||
},
|
},
|
||||||
"runtimes": {
|
"runtimes": {
|
||||||
"win10-x64": {},
|
"win10-x64": {},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user