本篇同步發文在個人Blog: 一袋.NET要扛幾樓?打造容器化的ASP.NET Core網站!系列文章 - (23) 建立購物車系統 - 6
購物車服務Api需要被保護,也就是之前的會員服務的驗證與授權,才能被正確呼叫。而Startup原本加入的Swagger也需要有認證機制,於是也加入OIDC設定。需要啟用Cors,讓外部能呼叫此CartApi。需要另外安裝這兩種套件:
Microsoft.AspNetCore.Authentication.JwtBearer
System.IdentityModel.Tokens.Jwt
using CartApi.Infrastructure.Filters;
using CartApi.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
namespace CartApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
}).AddNewtonsoftJson(options => {
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
ConfigureAuthService(services);
services.Configure<CartSettings>(Configuration);
services.AddSingleton<ConnectionMultiplexer>(sp =>
{
var settings = sp.GetRequiredService<IOptions<CartSettings>>().Value;
var configuration = ConfigurationOptions.Parse(settings.ConnectionString, true);
configuration.ResolveDns = true;
configuration.AbortOnConnectFail = false;
return ConnectionMultiplexer.Connect(configuration);
});
services.AddTransient<ICartRepository, CartRepository>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSwaggerGen(options => {
options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Version = "v1",
Title = "Cart HTTP API",
Description = "The Cart Service HTTP API"
});
options.AddSecurityDefinition("oauth2", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Type = Microsoft.OpenApi.Models.SecuritySchemeType.OAuth2,
Flows = new Microsoft.OpenApi.Models.OpenApiOAuthFlows
{
Implicit = new Microsoft.OpenApi.Models.OpenApiOAuthFlow
{
AuthorizationUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrl")}/connect/authorize"),
TokenUrl = new Uri($"{Configuration.GetValue<string>("IdentityUrl")}/connect/token"),
Scopes = new Dictionary<string, string>()
{
{ "basket", "Basket Api" }
}
}
}
});
options.OperationFilter<AuthorizationCheckOperationFilter>();
});
services.AddCors(builder =>
{
builder.DefaultPolicyName = "CorsPolicy";
builder.AddPolicy("CorsPolicy", options => {
options.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger()
.UseSwaggerUI(c =>
{
c.SwaggerEndpoint(
$"/swagger/v1/swagger.json",
"Cart.API V1");
c.OAuthClientId("basketswaggerui");
c.OAuthAppName("Basket Swagger UI");
});
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
private void ConfigureAuthService(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "basket";
options.BackchannelHttpHandler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
});
}
}
}
在CartApi的Infrastructure/filters新增AuthorizationCheckOperationFilter類別,並實作IOperationFilter,用來判斷在Swagger使用授權的Controller要對應OIDC功能
using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using System.Linq;
namespace CartApi.Infrastructure.Filters
{
public class AuthorizationCheckOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var hasAuthorize =
context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any()
|| context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
if (hasAuthorize)
{
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
operation.Security = new List<OpenApiSecurityRequirement>();
operation.Security.Add(new OpenApiSecurityRequirement
{
[
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
}
] = new[] { "basket" }
});
}
}
}
}
在CartController加上[Authorize]的屬性,代表這控制器的方法都要被保護。