我已经在具有多个微服务的Linux容器中配置了ocelot。为了限制我正在使用的一些微服务
RouteClaimsRequirement
。我有管理员角色作为声明,但是当我使用角色管理员发送令牌时,Ocelot 返回 403 Forbidden,这是 not 满足 RouteClaimsRequirement
中的标准的 HttpCode。如果我从 RouteClaimsRequirment
删除 ocelot.json
一切正常。
{
"DownstreamPathTemplate": "/api/v1/product/{everything}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "product",
"Port": 443
}
],
"UpstreamPathTemplate": "/product/{everything}",
"UpstreamHttpMethod": [ "Get", "Post", "Delete" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer",
"AllowedScopes": []
},
"RouteClaimsRequirement": { <---- Problem Part
"Role": "Administrator"
},
"DangerousAcceptAnyServerCertificateValidator": true,
"RateLimitOptions": {
"ClientWhitelist": [],
"EnableRateLimiting": true,
"Period": "5s",
"PeriodTimespan": 6,
"Limit": 8
}
}
ocelot 项目启动类如下所示:
public void ConfigureServices(IServiceCollection services)
=> services
.AddCors()
.AddTokenAuthentication(Configuration)
.AddOcelot();
public static IServiceCollection AddTokenAuthentication(
this IServiceCollection services,
IConfiguration
configuration,
JwtBearerEvents events = null)
{
var secret = configuration
.GetSection(nameof(ApplicationSettings))
.GetValue<string>(nameof(ApplicationSettings.Secret));
var key = Encoding.ASCII.GetBytes(secret);
services
.AddAuthentication(authentication =>
{
authentication.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
authentication.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(bearer =>
{
bearer.RequireHttpsMetadata = false;
bearer.SaveToken = true;
bearer.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
if (events != null)
{
bearer.Events = events;
}
});
services.AddHttpContextAccessor();
services.AddScoped<ICurrentUserService, CurrentUserService>();
return services;
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app
.UseCors(options => options
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod())
.UseAuthentication()
.UseAuthorization()
.UseOcelot().Wait();
}
代币生成如下所示:
public string GenerateToken(User user, IEnumerable<string> roles = null)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(this.applicationSettings.Secret);
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Name, user.Email)
};
if (roles != null)
{
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
}
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var encryptedToken = tokenHandler.WriteToken(token);
return encryptedToken;
}
解密后的令牌:
{
"nameid": "e18d5f1f-a315-435c-9e38-df9f2c77ad20",
"unique_name": "[email protected]",
"role": "Administrator",
"nbf": 1595460189,
"exp": 1596064989,
"iat": 1595460189
}
经过大量的代码研究和调试,我终于找到了问题所在。它来自索赔名称,不是
role
,而是http://schemas.microsoft.com/ws/2008/06/identity/claims/role
,但是当你把它写在ocelot.json
中时,它会因为冒号(:
)而被错误识别。
研究中途,我在Github上找到了一个解决同样问题的人的答案,我复制了他的解决方案。这是解决方案的链接。
问题如何处理:我们使用特殊符号而不是
:
编写URL,然后将其重写为正确的URL,因此ocelot.json配置不会抛出问题。
首先您需要创建
IClaimsAuthoriser
public class ClaimAuthorizerDecorator : IClaimsAuthoriser
{
private readonly ClaimsAuthoriser _authoriser;
public ClaimAuthorizerDecorator(ClaimsAuthoriser authoriser)
{
_authoriser = authoriser;
}
public Response<bool> Authorise(ClaimsPrincipal claimsPrincipal, Dictionary<string, string> routeClaimsRequirement, List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
{
var newRouteClaimsRequirement = new Dictionary<string, string>();
foreach (var kvp in routeClaimsRequirement)
{
if (kvp.Key.StartsWith("http$//"))
{
var key = kvp.Key.Replace("http$//", "http://");
newRouteClaimsRequirement.Add(key, kvp.Value);
}
else
{
newRouteClaimsRequirement.Add(kvp.Key, kvp.Value);
}
}
return _authoriser.Authorise(claimsPrincipal, newRouteClaimsRequirement, urlPathPlaceholderNameAndValues);
}
}
之后需要服务收集扩展:
public static class ServiceCollectionExtensions
{
public static IServiceCollection DecorateClaimAuthoriser(this IServiceCollection services)
{
var serviceDescriptor = services.First(x => x.ServiceType == typeof(IClaimsAuthoriser));
services.Remove(serviceDescriptor);
var newServiceDescriptor = new ServiceDescriptor(serviceDescriptor.ImplementationType, serviceDescriptor.ImplementationType, serviceDescriptor.Lifetime);
services.Add(newServiceDescriptor);
services.AddTransient<IClaimsAuthoriser, ClaimAuthorizerDecorator>();
return services;
}
}
在启动时,添加 ocelot 后定义扩展
public void ConfigureServices(IServiceCollection services)
{
services
.AddCors()
.AddTokenAuthentication(Configuration)
.AddOcelot().AddSingletonDefinedAggregator<DashboardAggregator>();
services.DecorateClaimAuthoriser();
}
最后你需要将配置 JSON 更改为:
"RouteClaimsRequirement": {
"http$//schemas.microsoft.com/ws/2008/06/identity/claims/role": "Administrator"
}