#1 : Test de Keycloak.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Webzine.Business.Contracts;
|
||||
@@ -32,6 +33,7 @@ public class DashboardController : Controller
|
||||
/// Affiche le tableau de bord de l'administration.
|
||||
/// </summary>
|
||||
/// <returns>La vue Index du tableau de bord.</returns>
|
||||
[Authorize(Roles = "ADMIN")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
DashboardDTO data = this.dashboardService.GetDashboardData();
|
||||
|
||||
49
Webzine.WebApplication/Controllers/AccountController.cs
Normal file
49
Webzine.WebApplication/Controllers/AccountController.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace Webzine.WebApplication.Controllers
|
||||
{
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public class AccountController : Controller
|
||||
{
|
||||
[HttpGet("/account/login")]
|
||||
public IActionResult Login(string? returnUrl = "/")
|
||||
{
|
||||
return this.Challenge(
|
||||
new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl,
|
||||
},
|
||||
OpenIdConnectDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
[HttpGet("/account/logout")]
|
||||
public IActionResult Logout()
|
||||
{
|
||||
return this.SignOut(
|
||||
new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = "/",
|
||||
},
|
||||
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
OpenIdConnectDefaults.AuthenticationScheme);
|
||||
}
|
||||
|
||||
[HttpGet("/account/access-denied")]
|
||||
public IActionResult AccessDenied()
|
||||
{
|
||||
return this.View();
|
||||
}
|
||||
|
||||
[HttpGet("/account/auth-error")]
|
||||
public IActionResult AuthError(string? message = null)
|
||||
{
|
||||
this.ViewData["Message"] = string.IsNullOrWhiteSpace(message)
|
||||
? "Une erreur est survenue pendant la connexion."
|
||||
: message;
|
||||
|
||||
return this.View();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace Webzine.WebApplication.Controllers
|
||||
{
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Webzine.Repository.Contracts;
|
||||
@@ -38,6 +39,7 @@
|
||||
/// </summary>
|
||||
/// <param name="page">Le numéro de page pour la pagination des titres (par défaut à 0).</param>
|
||||
/// <returns>La vue Index avec le ViewModel contenant les listes de titres à afficher.</returns>
|
||||
[Authorize(Roles = "ADMIN")]
|
||||
public IActionResult Index(int page = 0)
|
||||
{
|
||||
this.logger.LogInformation("Arrivée sur la page d'accueil");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace Webzine.WebApplication.Controllers
|
||||
{
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Webzine.Repository.Contracts;
|
||||
@@ -33,6 +34,7 @@
|
||||
/// </summary>
|
||||
/// <param name="nom">Le nom de l'artiste à rechercher, formaté en kebab-case (ex: "fatal-bazooka").</param>
|
||||
/// <returns>La vue de l'artiste avec son ViewModel, ou une redirection vers l'accueil si le nom est vide, ou une erreur 404 si l'artiste n'est pas trouvé.</returns>
|
||||
[Authorize(Roles = "ADMIN")]
|
||||
public IActionResult Index(string nom)
|
||||
{
|
||||
this.logger.LogInformation("Tentative d'accès à l'artiste avec le nom : {NomArtiste}", nom);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace Webzine.WebApplication.Controllers
|
||||
{
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
/// <summary>
|
||||
@@ -25,6 +26,7 @@
|
||||
/// Affiche la page de contact du webzine.
|
||||
/// </summary>
|
||||
/// <returns>La vue Index de la page de contact.</returns>
|
||||
[Authorize(Roles = "ADMIN")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return this.View();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
namespace Webzine.WebApplication.Controllers
|
||||
{
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Webzine.Repository.Contracts;
|
||||
@@ -39,6 +40,7 @@ namespace Webzine.WebApplication.Controllers
|
||||
/// </summary>
|
||||
/// <param name="mot">Nom d'artiste ou de titre.</param>
|
||||
/// <returns>Page de recherche avec les r<>sultats.</returns>
|
||||
[Authorize(Roles = "ADMIN")]
|
||||
public IActionResult Index(string mot)
|
||||
{
|
||||
// Logger la recherche.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
namespace Webzine.WebApplication.Controllers
|
||||
{
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Webzine.Entity;
|
||||
@@ -47,6 +48,7 @@ namespace Webzine.WebApplication.Controllers
|
||||
/// <param name="id">Identifiant du titre.</param>
|
||||
/// <param name="model">Model de donnée pour un commentaire.</param>
|
||||
/// <returns>Vue des details ou 404 si introuvable.</returns>
|
||||
[Authorize(Roles = "ADMIN")]
|
||||
public IActionResult Index(int id)
|
||||
{
|
||||
this.logger.LogInformation("Demande d'affichage du detail pour le titre ID {Id}.", id);
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
// L'erreur SA1200 (ordre des using directives) est desactivee pour Program.cs
|
||||
#pragma warning disable SA1200
|
||||
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
using NLog;
|
||||
using NLog.Web;
|
||||
@@ -23,30 +32,223 @@ using Webzine.WebApplication.Interceptors;
|
||||
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||
logger.Debug("init main");
|
||||
|
||||
static void AddRoleClaim(ClaimsIdentity identity, string? roleValue)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(roleValue) &&
|
||||
!identity.HasClaim(claim => claim.Type == "role" && claim.Value == roleValue))
|
||||
{
|
||||
identity.AddClaim(new Claim("role", roleValue));
|
||||
}
|
||||
}
|
||||
|
||||
static void AddKeycloakRolesFromJson(ClaimsIdentity identity, string? json, string? clientId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var document = JsonDocument.Parse(json);
|
||||
|
||||
if (document.RootElement.TryGetProperty("realm_access", out var realmAccessElement) &&
|
||||
realmAccessElement.TryGetProperty("roles", out var realmRolesElement))
|
||||
{
|
||||
foreach (var role in realmRolesElement.EnumerateArray())
|
||||
{
|
||||
AddRoleClaim(identity, role.GetString());
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(clientId) &&
|
||||
document.RootElement.TryGetProperty("resource_access", out var resourceAccessElement) &&
|
||||
resourceAccessElement.TryGetProperty(clientId, out var clientAccessElement) &&
|
||||
clientAccessElement.TryGetProperty("roles", out var clientRolesElement))
|
||||
{
|
||||
foreach (var role in clientRolesElement.EnumerateArray())
|
||||
{
|
||||
AddRoleClaim(identity, role.GetString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void AddKeycloakRolesFromClaims(ClaimsIdentity identity, ClaimsPrincipal principal, string? clientId)
|
||||
{
|
||||
var realmAccessClaim = principal.FindFirst("realm_access")?.Value;
|
||||
if (!string.IsNullOrWhiteSpace(realmAccessClaim))
|
||||
{
|
||||
AddKeycloakRolesFromJson(identity, $$"""{ "realm_access": {{realmAccessClaim}} }""", clientId);
|
||||
}
|
||||
|
||||
var resourceAccessClaim = principal.FindFirst("resource_access")?.Value;
|
||||
if (!string.IsNullOrWhiteSpace(resourceAccessClaim))
|
||||
{
|
||||
AddKeycloakRolesFromJson(identity, $$"""{ "resource_access": {{resourceAccessClaim}} }""", clientId);
|
||||
}
|
||||
}
|
||||
|
||||
static void AddKeycloakRolesFromAccessToken(ClaimsIdentity identity, string? accessToken, string? clientId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(accessToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var token = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
|
||||
var payload = JsonSerializer.Serialize(token.Payload);
|
||||
|
||||
AddKeycloakRolesFromJson(identity, payload, clientId);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Ajoute les services necessaires pour permettre l'utilisation des
|
||||
// controllers avec des vues.
|
||||
builder.Services.AddControllersWithViews(options =>
|
||||
{
|
||||
options.Filters.Add<ValidationActionFilter>();
|
||||
options.Filters.Add<GlobalExceptionFilter>();
|
||||
})
|
||||
.AddRazorRuntimeCompilation();
|
||||
|
||||
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
|
||||
|
||||
// Important derrière Nginx pour que ASP.NET Core comprenne bien :
|
||||
// - le host public
|
||||
// - le scheme https
|
||||
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders =
|
||||
ForwardedHeaders.XForwardedFor |
|
||||
ForwardedHeaders.XForwardedProto |
|
||||
ForwardedHeaders.XForwardedHost;
|
||||
|
||||
options.KnownNetworks.Clear();
|
||||
options.KnownProxies.Clear();
|
||||
});
|
||||
|
||||
builder.Services
|
||||
.AddAuthentication(options =>
|
||||
{
|
||||
// options.Filters.Add<GlobalExceptionFilter>();
|
||||
options.Filters.Add<ValidationActionFilter>();
|
||||
options.Filters.Add<GlobalExceptionFilter>();
|
||||
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
options.LoginPath = "/account/login";
|
||||
options.LogoutPath = "/account/logout";
|
||||
options.AccessDeniedPath = "/account/access-denied";
|
||||
})
|
||||
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
var publicOrigin = builder.Configuration["Keycloak:PublicOrigin"]?.TrimEnd('/');
|
||||
var callbackPath = builder.Configuration["Keycloak:CallbackPath"] ?? "/signin-oidc";
|
||||
var signedOutCallbackPath = builder.Configuration["Keycloak:SignedOutCallbackPath"] ?? "/signout-callback-oidc";
|
||||
|
||||
options.Authority = builder.Configuration["Keycloak:Authority"];
|
||||
options.ClientId = builder.Configuration["Keycloak:ClientId"];
|
||||
options.ClientSecret = builder.Configuration["Keycloak:ClientSecret"];
|
||||
options.ResponseType = OpenIdConnectResponseType.Code;
|
||||
|
||||
options.CallbackPath = callbackPath;
|
||||
options.SignedOutCallbackPath = signedOutCallbackPath;
|
||||
|
||||
// Reverse proxy + certificat autosigné
|
||||
options.RequireHttpsMetadata = false;
|
||||
|
||||
// Désactive PAR pour éviter l’erreur "Invalid parameter: redirect_uri"
|
||||
options.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable;
|
||||
|
||||
options.SaveTokens = true;
|
||||
options.GetClaimsFromUserInfoEndpoint = false;
|
||||
|
||||
options.Scope.Clear();
|
||||
options.Scope.Add("openid");
|
||||
options.Scope.Add("profile");
|
||||
options.Scope.Add("email");
|
||||
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
NameClaimType = "preferred_username",
|
||||
RoleClaimType = "role",
|
||||
};
|
||||
|
||||
// Temporaire pour environnement de test avec certificat autosigné
|
||||
options.BackchannelHttpHandler = new HttpClientHandler
|
||||
{
|
||||
ServerCertificateCustomValidationCallback =
|
||||
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
|
||||
};
|
||||
|
||||
options.Events = new OpenIdConnectEvents
|
||||
{
|
||||
OnRedirectToIdentityProvider = context =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(publicOrigin))
|
||||
{
|
||||
context.ProtocolMessage.RedirectUri = publicOrigin + context.Options.CallbackPath;
|
||||
}
|
||||
|
||||
logger.Info("RedirectUri Keycloak envoyee : {RedirectUri}", context.ProtocolMessage.RedirectUri);
|
||||
Console.WriteLine("RedirectUri envoyé à Keycloak : " + context.ProtocolMessage.RedirectUri);
|
||||
Console.WriteLine("Authority utilisée : " + context.Options.Authority);
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
|
||||
OnRedirectToIdentityProviderForSignOut = context =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(publicOrigin))
|
||||
{
|
||||
context.ProtocolMessage.PostLogoutRedirectUri = publicOrigin + context.Options.SignedOutCallbackPath;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
|
||||
OnTokenValidated = context =>
|
||||
{
|
||||
var identity = (ClaimsIdentity)context.Principal!.Identity!;
|
||||
var clientId = context.Options.ClientId;
|
||||
|
||||
AddKeycloakRolesFromClaims(identity, context.Principal, clientId);
|
||||
AddKeycloakRolesFromAccessToken(identity, context.TokenEndpointResponse?.AccessToken, clientId);
|
||||
|
||||
logger.Info(
|
||||
"Roles Keycloak ajoutes a l'utilisateur {User}: {Roles}",
|
||||
identity.Name,
|
||||
string.Join(", ", identity.FindAll("role").Select(claim => claim.Value)));
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
|
||||
OnRemoteFailure = context =>
|
||||
{
|
||||
var remoteError = context.Request.Query["error"].ToString();
|
||||
var remoteDescription = context.Request.Query["error_description"].ToString();
|
||||
var failureMessage = context.Failure?.Message;
|
||||
var message = string.Join(
|
||||
Environment.NewLine,
|
||||
new[] { remoteError, remoteDescription, failureMessage }.Where(value => !string.IsNullOrWhiteSpace(value)));
|
||||
|
||||
logger.Error(context.Failure, "Erreur OIDC distante : {Message}", message);
|
||||
Console.WriteLine("Erreur OIDC distante : " + message);
|
||||
context.HandleResponse();
|
||||
context.Response.Redirect("/account/auth-error?message=" + Uri.EscapeDataString(message));
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Ajoute les services necessaires pour permettre l'utilisation des controllers avec des vues.
|
||||
builder.Services.AddControllersWithViews()
|
||||
|
||||
// Ajoute la compilation des vues lors de l'execution de l'application.
|
||||
// Cela nous evite de recompiler l'application a chaque modification de vue.
|
||||
// Necessite le package Nuget Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.
|
||||
.AddRazorRuntimeCompilation();
|
||||
builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("RequireUser", policy => policy.RequireRole("USER", "ADMIN"));
|
||||
options.AddPolicy("RequireAdmin", policy => policy.RequireRole("ADMIN"));
|
||||
});
|
||||
|
||||
// NLog: Setup NLog for Dependency injection
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Host.UseNLog();
|
||||
|
||||
builder.Services.Configure<SpotifySeederOptions>(builder.Configuration.GetSection("SpotifySeeder"));
|
||||
builder.Services.AddHttpClient<SeedDataSpotify>();
|
||||
|
||||
@@ -54,10 +256,10 @@ try
|
||||
{
|
||||
options.SeuilMs = builder.Configuration.GetValue<int>("EfPerformance:SeuilMs");
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<EfSlowQueryInterceptor>();
|
||||
|
||||
// En fonction de la configuration, utilise soit les repositories basés sur une base de données, soit les repositories basés sur des listes locales.
|
||||
// En fonction de la configuration, utilise soit les repositories bases sur une base de donnees, soit les repositories bases sur des listes locales.
|
||||
var repositoryType = builder.Configuration.GetValue<RepositoryType>("Repository");
|
||||
var seederType = builder.Configuration.GetValue<SeederType>("Seeder");
|
||||
var shouldSeed = args.Contains("--seed");
|
||||
@@ -104,12 +306,13 @@ try
|
||||
builder.Services.AddScoped<ValidationActionFilter>();
|
||||
builder.Services.AddScoped<GlobalExceptionFilter>();
|
||||
|
||||
// https://learn.microsoft.com/fr-fr/aspnet/core/performance/response-compression?view=aspnetcore-10.0#configuration
|
||||
// Ajoute le service de compression des reponses HTTP pour reduire la taille des donnees envoyees au client et ameliorer les performances de l'application.
|
||||
builder.Services.AddResponseCompression();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Très important avant tout middleware qui lit le scheme/host de la requête.
|
||||
app.UseForwardedHeaders();
|
||||
|
||||
app.UseMiddleware<Webzine.WebApplication.Middlewares.LogTempsExecutionMiddleware>();
|
||||
|
||||
if (repositoryType == RepositoryType.Db)
|
||||
@@ -168,32 +371,30 @@ try
|
||||
|
||||
app.UseResponseCompression();
|
||||
|
||||
// Active la possibilite de servir des fichiers statiques presents dans le dossier wwwroot.
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
OnPrepareResponse = ctx =>
|
||||
{
|
||||
// https://learn.microsoft.com/fr-fr/aspnet/core/fundamentals/static-files?view=aspnetcore-10.0#set-http-response-headers
|
||||
ctx.Context.Response.Headers.Append("Cache-Control", "public, max-age=31536000");
|
||||
},
|
||||
});
|
||||
|
||||
// Active le middleware permettant le routage des requetes entrantes.
|
||||
app.UseRouting();
|
||||
|
||||
// Appelle les routes definies dans le dossier Extensions.
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapCustomRoutes();
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// NLog: attrape les exceptions non gerees et les logger.
|
||||
logger.Error(exception, "Stopped program because of exception");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Assure que NLog flush tous les messages de log avant de fermer l'application.
|
||||
LogManager.Shutdown();
|
||||
}
|
||||
13
Webzine.WebApplication/Views/Account/AccessDenied.cshtml
Normal file
13
Webzine.WebApplication/Views/Account/AccessDenied.cshtml
Normal file
@@ -0,0 +1,13 @@
|
||||
@{
|
||||
ViewData["Title"] = "Accès refusé";
|
||||
}
|
||||
|
||||
<div class="container mt-5 text-center">
|
||||
<h2>Accès refusé</h2>
|
||||
|
||||
<p>Vous n'avez pas les droits pour accéder à cette page.</p>
|
||||
|
||||
<a href="/" class="btn btn-secondary">
|
||||
Retour à l'accueil
|
||||
</a>
|
||||
</div>
|
||||
15
Webzine.WebApplication/Views/Account/Login.cshtml
Normal file
15
Webzine.WebApplication/Views/Account/Login.cshtml
Normal file
@@ -0,0 +1,15 @@
|
||||
@{
|
||||
ViewData["Title"] = "Connexion";
|
||||
}
|
||||
|
||||
<div class="container mt-5 text-center">
|
||||
<h2>Connexion</h2>
|
||||
|
||||
<p>Vous allez être redirigé vers le serveur d'authentification.</p>
|
||||
|
||||
<a asp-controller="Account"
|
||||
asp-action="Login"
|
||||
class="btn btn-primary">
|
||||
Se connecter avec Keycloak
|
||||
</a>
|
||||
</div>
|
||||
15
Webzine.WebApplication/Views/Account/Logout.cshtml
Normal file
15
Webzine.WebApplication/Views/Account/Logout.cshtml
Normal file
@@ -0,0 +1,15 @@
|
||||
@{
|
||||
ViewData["Title"] = "Déconnexion";
|
||||
}
|
||||
|
||||
<div class="container mt-5 text-center">
|
||||
<h2>Déconnexion</h2>
|
||||
|
||||
<p>Vous êtes sur le point de vous déconnecter.</p>
|
||||
|
||||
<form asp-controller="Account" asp-action="Logout" method="get">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
Se déconnecter
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -14,6 +14,24 @@
|
||||
</head>
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
<partial name="_Header"/>
|
||||
@if (User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
<span class="me-2">Bonjour @User.Identity.Name</span>
|
||||
|
||||
<a asp-controller="Account"
|
||||
asp-action="Logout"
|
||||
class="btn btn-outline-danger btn-sm">
|
||||
Déconnexion
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-controller="Account"
|
||||
asp-action="Login"
|
||||
class="btn btn-outline-primary btn-sm">
|
||||
Connexion
|
||||
</a>
|
||||
}
|
||||
<div class="container-fluid flex-grow-1 py-4">
|
||||
<div class="row">
|
||||
<main class="col mx-3">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
@@ -27,6 +27,10 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.17.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.17.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.17.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.*" />
|
||||
<PackageReference Include="NLog" Version="6.1.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"SpotifySeeder": {
|
||||
"ClientId": "",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
"Keycloak": {
|
||||
"PublicOrigin": "https://localhost:7095"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,16 @@
|
||||
"MaxCommentsPerTrack": 3
|
||||
},
|
||||
"Keycloak": {
|
||||
"Authority": "https://<keycloak-host>/realms/<your-realm>",
|
||||
"ClientId": "my-aspnet-app",
|
||||
"ClientSecret": "your-client-secret",
|
||||
"ResponseType": "code"
|
||||
"Authority": "https://10.4.0.131/keycloak/realms/webzine-realm",
|
||||
"PublicOrigin": "http://10.4.0.131:8080",
|
||||
"ClientId": "webzine-client",
|
||||
"ClientSecret": "Z9JgRucpeZD4jqRhTciiznX3PPoJ9oYp",
|
||||
"ResponseType": "code",
|
||||
"CallbackPath": "/signin-oidc",
|
||||
"SignedOutCallbackPath": "/signout-callback-oidc"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"EfPerformance": {
|
||||
"SeuilMs": 60
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user