mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 14:02:49 +08:00
started adding authentication stack, thing that decides if we should be authenticated is in
This commit is contained in:
parent
1fddcf0836
commit
58393f07ec
@ -0,0 +1,11 @@
|
||||
namespace Ocelot.Library.Infrastructure.Authentication
|
||||
{
|
||||
using Responses;
|
||||
public class CouldNotFindConfigurationError : Error
|
||||
{
|
||||
public CouldNotFindConfigurationError(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
namespace Ocelot.Library.Infrastructure.Authentication
|
||||
{
|
||||
using DownstreamRouteFinder;
|
||||
using Responses;
|
||||
|
||||
public interface IRouteRequiresAuthentication
|
||||
{
|
||||
Response<bool> IsAuthenticated(DownstreamRoute downstreamRoute, string httpMethod);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
namespace Ocelot.Library.Infrastructure.Authentication
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Configuration;
|
||||
using DownstreamRouteFinder;
|
||||
using Responses;
|
||||
|
||||
public class RouteRequiresAuthentication : IRouteRequiresAuthentication
|
||||
{
|
||||
private readonly IOcelotConfiguration _configuration;
|
||||
|
||||
public RouteRequiresAuthentication(IOcelotConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public Response<bool> IsAuthenticated(DownstreamRoute downstreamRoute, string httpMethod)
|
||||
{
|
||||
var reRoute =
|
||||
_configuration.ReRoutes.FirstOrDefault(
|
||||
x =>
|
||||
x.DownstreamTemplate == downstreamRoute.DownstreamUrlTemplate &&
|
||||
string.Equals(x.UpstreamHttpMethod, httpMethod, StringComparison.CurrentCultureIgnoreCase));
|
||||
|
||||
if (reRoute == null)
|
||||
{
|
||||
return new ErrorResponse<bool>(new List<Error> {new CouldNotFindConfigurationError($"Could not find configuration for {downstreamRoute.DownstreamUrlTemplate} using method {httpMethod}")});
|
||||
}
|
||||
|
||||
return new OkResponse<bool>(reRoute.IsAuthenticated);
|
||||
}
|
||||
}
|
||||
}
|
@ -43,7 +43,9 @@ namespace Ocelot.Library.Infrastructure.Configuration
|
||||
|
||||
upstreamTemplate = $"{upstreamTemplate}{RegExMatchEndString}";
|
||||
|
||||
_reRoutes.Add(new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate));
|
||||
var isAuthenticated = !string.IsNullOrEmpty(reRoute.Authentication);
|
||||
|
||||
_reRoutes.Add(new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,17 +2,19 @@
|
||||
{
|
||||
public class ReRoute
|
||||
{
|
||||
public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern)
|
||||
public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated)
|
||||
{
|
||||
DownstreamTemplate = downstreamTemplate;
|
||||
UpstreamTemplate = upstreamTemplate;
|
||||
UpstreamHttpMethod = upstreamHttpMethod;
|
||||
UpstreamTemplatePattern = upstreamTemplatePattern;
|
||||
IsAuthenticated = isAuthenticated;
|
||||
}
|
||||
|
||||
public string DownstreamTemplate { get; private set; }
|
||||
public string UpstreamTemplate { get; private set; }
|
||||
public string UpstreamTemplatePattern { get; private set; }
|
||||
public string UpstreamHttpMethod { get; private set; }
|
||||
public bool IsAuthenticated { get; private set; }
|
||||
}
|
||||
}
|
@ -5,5 +5,6 @@
|
||||
public string DownstreamTemplate { get; set; }
|
||||
public string UpstreamTemplate { get; set; }
|
||||
public string UpstreamHttpMethod { get; set; }
|
||||
public string Authentication { get; set; }
|
||||
}
|
||||
}
|
54
src/Ocelot.Library/Middleware/AuthenticationMiddleware.cs
Normal file
54
src/Ocelot.Library/Middleware/AuthenticationMiddleware.cs
Normal file
@ -0,0 +1,54 @@
|
||||
namespace Ocelot.Library.Middleware
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Infrastructure.Authentication;
|
||||
using Infrastructure.DownstreamRouteFinder;
|
||||
using Infrastructure.Repository;
|
||||
using Infrastructure.Responses;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
public class AuthenticationMiddleware : OcelotMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IScopedRequestDataRepository _scopedRequestDataRepository;
|
||||
private readonly IRouteRequiresAuthentication _requiresAuthentication;
|
||||
public AuthenticationMiddleware(RequestDelegate next,
|
||||
IScopedRequestDataRepository scopedRequestDataRepository,
|
||||
IRouteRequiresAuthentication requiresAuthentication)
|
||||
: base(scopedRequestDataRepository)
|
||||
{
|
||||
_next = next;
|
||||
_scopedRequestDataRepository = scopedRequestDataRepository;
|
||||
_requiresAuthentication = requiresAuthentication;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
var downstreamRoute = _scopedRequestDataRepository.Get<DownstreamRoute>("DownstreamRoute");
|
||||
|
||||
var isAuthenticated = _requiresAuthentication.IsAuthenticated(downstreamRoute.Data, context.Request.Method);
|
||||
|
||||
if (isAuthenticated.IsError)
|
||||
{
|
||||
SetPipelineError(downstreamRoute.Errors);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsAuthenticatedRoute(isAuthenticated))
|
||||
{
|
||||
//todo - build auth pipeline and then call normal pipeline if all good?
|
||||
await _next.Invoke(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _next.Invoke(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static bool IsAuthenticatedRoute(Response<bool> isAuthenticated)
|
||||
{
|
||||
return isAuthenticated.Data;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace Ocelot.Library.Middleware
|
||||
{
|
||||
public static class AuthenticationMiddlewareMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseAuthenticationMiddleware(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<AuthenticationMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Ocelot.Library.Infrastructure.DownstreamRouteFinder;
|
||||
using Ocelot.Library.Infrastructure.Repository;
|
||||
using Ocelot.Library.Infrastructure.Responder;
|
||||
using Ocelot.Library.Infrastructure.UrlTemplateReplacer;
|
||||
|
||||
namespace Ocelot.Library.Middleware
|
||||
|
@ -4,7 +4,7 @@ namespace Ocelot.Library.Middleware
|
||||
{
|
||||
public static class DownstreamUrlCreatorMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UserDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder)
|
||||
public static IApplicationBuilder UseDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<DownstreamUrlCreatorMiddleware>();
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ using Ocelot.Library.Middleware;
|
||||
|
||||
namespace Ocelot
|
||||
{
|
||||
using Library.Infrastructure.Authentication;
|
||||
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IHostingEnvironment env)
|
||||
@ -52,6 +54,7 @@ namespace Ocelot
|
||||
services.AddSingleton<IHttpRequester, HttpClientHttpRequester>();
|
||||
services.AddSingleton<IHttpResponder, HttpContextResponder>();
|
||||
services.AddSingleton<IRequestBuilder, HttpRequestBuilder>();
|
||||
services.AddSingleton<IRouteRequiresAuthentication, RouteRequiresAuthentication>();
|
||||
|
||||
// see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc
|
||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
@ -69,7 +72,9 @@ namespace Ocelot
|
||||
|
||||
app.UseDownstreamRouteFinderMiddleware();
|
||||
|
||||
app.UserDownstreamUrlCreatorMiddleware();
|
||||
app.UseAuthenticationMiddleware();
|
||||
|
||||
app.UseDownstreamUrlCreatorMiddleware();
|
||||
|
||||
app.UseHttpRequestBuilderMiddleware();
|
||||
|
||||
|
@ -2,3 +2,4 @@ ReRoutes:
|
||||
- DownstreamTemplate: http://localhost:51879/
|
||||
UpstreamTemplate: /
|
||||
UpstreamHttpMethod: Get
|
||||
Authentication: IdentityServer
|
||||
|
@ -0,0 +1,96 @@
|
||||
namespace Ocelot.UnitTests.Authentication
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Library.Infrastructure.Authentication;
|
||||
using Library.Infrastructure.Configuration;
|
||||
using Library.Infrastructure.DownstreamRouteFinder;
|
||||
using Library.Infrastructure.Responses;
|
||||
using Library.Infrastructure.UrlMatcher;
|
||||
using Moq;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
|
||||
public class RequiresAuthenticationTests
|
||||
{
|
||||
private readonly RouteRequiresAuthentication _routeRequiresAuthentication;
|
||||
private string _url;
|
||||
private readonly Mock<IOcelotConfiguration> _config;
|
||||
private Response<bool> _result;
|
||||
private string _httpMethod;
|
||||
|
||||
public RequiresAuthenticationTests()
|
||||
{
|
||||
_config = new Mock<IOcelotConfiguration>();
|
||||
_routeRequiresAuthentication = new RouteRequiresAuthentication(_config.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_true_if_route_requires_authentication()
|
||||
{
|
||||
this.Given(x => x.GivenIHaveADownstreamUrl("http://www.bbc.co.uk"))
|
||||
.And(
|
||||
x =>
|
||||
x.GivenTheConfigurationForTheRouteIs(new ReRoute("http://www.bbc.co.uk", "/api/poo", "get",
|
||||
"/api/poo$", true)))
|
||||
.When(x => x.WhenICheckToSeeIfTheRouteShouldBeAuthenticated())
|
||||
.Then(x => x.ThenTheResultIs(true))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_false_if_route_requires_authentication()
|
||||
{
|
||||
this.Given(x => x.GivenIHaveADownstreamUrl("http://www.bbc.co.uk"))
|
||||
.And(
|
||||
x =>
|
||||
x.GivenTheConfigurationForTheRouteIs(new ReRoute("http://www.bbc.co.uk", "/api/poo", "get",
|
||||
"/api/poo$", false)))
|
||||
.When(x => x.WhenICheckToSeeIfTheRouteShouldBeAuthenticated())
|
||||
.Then(x => x.ThenTheResultIs(false))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_error_if_no_matching_config()
|
||||
{
|
||||
this.Given(x => x.GivenIHaveADownstreamUrl("http://www.bbc.co.uk"))
|
||||
.And(x => x.GivenTheConfigurationForTheRouteIs(new ReRoute(string.Empty, string.Empty, string.Empty, string.Empty,false)))
|
||||
.When(x => x.WhenICheckToSeeIfTheRouteShouldBeAuthenticated())
|
||||
.Then(x => x.ThenAnErrorIsReturned())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenAnErrorIsReturned()
|
||||
{
|
||||
_result.IsError.ShouldBeTrue();
|
||||
}
|
||||
|
||||
public void GivenIHaveADownstreamUrl(string url)
|
||||
{
|
||||
_url = url;
|
||||
}
|
||||
|
||||
private void GivenTheConfigurationForTheRouteIs(ReRoute reRoute)
|
||||
{
|
||||
_httpMethod = reRoute.UpstreamHttpMethod;
|
||||
|
||||
_config
|
||||
.Setup(x => x.ReRoutes)
|
||||
.Returns(new List<ReRoute>
|
||||
{
|
||||
reRoute
|
||||
});
|
||||
}
|
||||
|
||||
private void WhenICheckToSeeIfTheRouteShouldBeAuthenticated()
|
||||
{
|
||||
_result = _routeRequiresAuthentication.IsAuthenticated(new DownstreamRoute(new List<TemplateVariableNameAndValue>(), _url), _httpMethod);
|
||||
}
|
||||
|
||||
private void ThenTheResultIs(bool expected)
|
||||
{
|
||||
_result.Data.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
.When(x => x.WhenIInstanciateTheOcelotConfig())
|
||||
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
||||
{
|
||||
new ReRoute("/products/{productId}","/api/products/{productId}", "Get", "/api/products/.*$")
|
||||
new ReRoute("/products/{productId}","/api/products/{productId}", "Get", "/api/products/.*$", false)
|
||||
}))
|
||||
.BDDfy();
|
||||
}
|
||||
@ -61,7 +61,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
.When(x => x.WhenIInstanciateTheOcelotConfig())
|
||||
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
||||
{
|
||||
new ReRoute("/products/{productId}","/api/products/{productId}/variants/{variantId}", "Get", "/api/products/.*/variants/.*$")
|
||||
new ReRoute("/products/{productId}","/api/products/{productId}/variants/{variantId}", "Get", "/api/products/.*/variants/.*$", false)
|
||||
}))
|
||||
.BDDfy();
|
||||
}
|
||||
@ -84,7 +84,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
.When(x => x.WhenIInstanciateTheOcelotConfig())
|
||||
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
||||
{
|
||||
new ReRoute("/products/{productId}","/api/products/{productId}/variants/{variantId}/", "Get", "/api/products/.*/variants/.*/$")
|
||||
new ReRoute("/products/{productId}","/api/products/{productId}/variants/{variantId}/", "Get", "/api/products/.*/variants/.*/$", false)
|
||||
}))
|
||||
.BDDfy();
|
||||
}
|
||||
@ -107,7 +107,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
.When(x => x.WhenIInstanciateTheOcelotConfig())
|
||||
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
||||
{
|
||||
new ReRoute("/api/products/","/", "Get", "/$")
|
||||
new ReRoute("/api/products/","/", "Get", "/$", false)
|
||||
}))
|
||||
.BDDfy();
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
||||
.And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse<List<TemplateVariableNameAndValue>>(new List<TemplateVariableNameAndValue>())))
|
||||
.And(x => x.GivenTheConfigurationIs(new List<ReRoute>
|
||||
{
|
||||
new ReRoute("someDownstreamPath","someUpstreamPath", "Get", "someUpstreamPath")
|
||||
new ReRoute("someDownstreamPath","someUpstreamPath", "Get", "someUpstreamPath", false)
|
||||
}
|
||||
))
|
||||
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
|
||||
@ -57,8 +57,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
||||
.And(x => x.GivenTheTemplateVariableAndNameFinderReturns(new OkResponse<List<TemplateVariableNameAndValue>>(new List<TemplateVariableNameAndValue>())))
|
||||
.And(x => x.GivenTheConfigurationIs(new List<ReRoute>
|
||||
{
|
||||
new ReRoute("someDownstreamPath", "someUpstreamPath", "Get", string.Empty),
|
||||
new ReRoute("someDownstreamPathForAPost", "someUpstreamPath", "Post", string.Empty)
|
||||
new ReRoute("someDownstreamPath", "someUpstreamPath", "Get", string.Empty, false),
|
||||
new ReRoute("someDownstreamPathForAPost", "someUpstreamPath", "Post", string.Empty, false)
|
||||
}
|
||||
))
|
||||
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(true))))
|
||||
@ -75,7 +75,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder
|
||||
this.Given(x => x.GivenThereIsAnUpstreamUrlPath("somePath"))
|
||||
.And(x => x.GivenTheConfigurationIs(new List<ReRoute>
|
||||
{
|
||||
new ReRoute("somPath", "somePath", "Get", "somePath")
|
||||
new ReRoute("somPath", "somePath", "Get", "somePath", false)
|
||||
}
|
||||
))
|
||||
.And(x => x.GivenTheUrlMatcherReturns(new OkResponse<UrlMatch>(new UrlMatch(false))))
|
||||
|
Loading…
x
Reference in New Issue
Block a user