remote service support via Jwt

This commit is contained in:
Shaun Walker
2022-03-30 08:07:03 -04:00
parent b7675a21eb
commit 3194c5b600
20 changed files with 272 additions and 80 deletions

View File

@ -526,16 +526,12 @@ namespace Oqtane.Controllers
public string Token()
{
var token = "";
var user = _users.GetUser(User.Identity.Name);
if (user != null)
var sitesettings = HttpContext.GetSiteSettings();
var secret = sitesettings.GetValue("JwtOptions:Secret", "");
if (!string.IsNullOrEmpty(secret))
{
var sitesettings = HttpContext.GetSiteSettings();
var secret = sitesettings.GetValue("JwtOptions:Secret", "");
if (!string.IsNullOrEmpty(secret))
{
var lifetime = 525600; // long-lived token set to 1 year
token = _jwtManager.GenerateToken(_tenantManager.GetAlias(), user, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), lifetime);
}
var lifetime = 525600; // long-lived token set to 1 year
token = _jwtManager.GenerateToken(_tenantManager.GetAlias(), (ClaimsIdentity)User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), lifetime);
}
return token;
}
@ -548,7 +544,10 @@ namespace Oqtane.Controllers
if (user.IsAuthenticated)
{
user.Username = User.Identity.Name;
user.UserId = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value);
if (User.HasClaim(item => item.Type == ClaimTypes.NameIdentifier))
{
user.UserId = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value);
}
string roles = "";
foreach (var claim in User.Claims.Where(item => item.Type == ClaimTypes.Role))
{

View File

@ -194,7 +194,7 @@ namespace Microsoft.Extensions.DependencyInjection
return services;
}
internal static IServiceCollection TryAddHttpClientWithAuthenticationCookie(this IServiceCollection services)
internal static IServiceCollection AddHttpClients(this IServiceCollection services)
{
if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
{
@ -216,6 +216,8 @@ namespace Microsoft.Extensions.DependencyInjection
});
}
services.AddHttpClient("External");
return services;
}

View File

@ -25,7 +25,7 @@
{
@(Html.AntiForgeryToken())
<component type="typeof(Oqtane.App)" render-mode="@Model.RenderMode" param-AntiForgeryToken="@Model.AntiForgeryToken" param-Runtime="@Model.Runtime" param-RenderMode="@Model.RenderMode.ToString()" param-VisitorId="@Model.VisitorId" param-RemoteIPAddress="@Model.RemoteIPAddress" />
<component type="typeof(Oqtane.App)" render-mode="@Model.RenderMode" param-AntiForgeryToken="@Model.AntiForgeryToken" param-Runtime="@Model.Runtime" param-RenderMode="@Model.RenderMode.ToString()" param-VisitorId="@Model.VisitorId" param-RemoteIPAddress="@Model.RemoteIPAddress" param-AuthorizationToken="@Model.AuthorizationToken" />
<div id="blazor-error-ui">
<environment include="Staging,Production">

View File

@ -20,6 +20,8 @@ using System.Security.Claims;
using System.Net;
using Microsoft.Extensions.Primitives;
using Oqtane.Enums;
using Oqtane.Security;
using Oqtane.Extensions;
namespace Oqtane.Pages
{
@ -30,6 +32,7 @@ namespace Oqtane.Pages
private readonly ILocalizationManager _localizationManager;
private readonly ILanguageRepository _languages;
private readonly IAntiforgery _antiforgery;
private readonly IJwtManager _jwtManager;
private readonly ISiteRepository _sites;
private readonly IPageRepository _pages;
private readonly IUrlMappingRepository _urlMappings;
@ -38,13 +41,14 @@ namespace Oqtane.Pages
private readonly ISettingRepository _settings;
private readonly ILogManager _logger;
public HostModel(IConfigManager configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors, IAliasRepository aliases, ISettingRepository settings, ILogManager logger)
public HostModel(IConfigManager configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, IJwtManager jwtManager, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors, IAliasRepository aliases, ISettingRepository settings, ILogManager logger)
{
_configuration = configuration;
_tenantManager = tenantManager;
_localizationManager = localizationManager;
_languages = languages;
_antiforgery = antiforgery;
_jwtManager = jwtManager;
_sites = sites;
_pages = pages;
_urlMappings = urlMappings;
@ -56,6 +60,7 @@ namespace Oqtane.Pages
public string Language = "en";
public string AntiForgeryToken = "";
public string AuthorizationToken = "";
public string Runtime = "Server";
public RenderMode RenderMode = RenderMode.Server;
public int VisitorId = -1;
@ -133,6 +138,17 @@ namespace Oqtane.Pages
Title = site.Name;
ThemeType = site.DefaultThemeType;
// get jwt token for downstream APIs
if (User.Identity.IsAuthenticated)
{
var sitesettings = HttpContext.GetSiteSettings();
var secret = sitesettings.GetValue("JwtOptions:Secret", "");
if (!string.IsNullOrEmpty(secret))
{
AuthorizationToken = _jwtManager.GenerateToken(alias, (ClaimsIdentity)User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20")));
}
}
if (site.VisitorTracking)
{
TrackVisitor(site.SiteId);
@ -247,9 +263,9 @@ namespace Oqtane.Pages
string url = Request.GetEncodedUrl();
string referrer = (Request.Headers[HeaderNames.Referer] != StringValues.Empty) ? Request.Headers[HeaderNames.Referer] : "";
int? userid = null;
if (User.HasClaim(item => item.Type == ClaimTypes.PrimarySid))
if (User.HasClaim(item => item.Type == ClaimTypes.NameIdentifier))
{
userid = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value);
userid = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value);
}
// check if cookie already exists

View File

@ -10,20 +10,19 @@ namespace Oqtane.Security
{
public interface IJwtManager
{
string GenerateToken(Alias alias, User user, string secret, string issuer, string audience, int lifetime);
string GenerateToken(Alias alias, ClaimsIdentity user, string secret, string issuer, string audience, int lifetime);
User ValidateToken(string token, string secret, string issuer, string audience);
}
public class JwtManager : IJwtManager
{
public string GenerateToken(Alias alias, User user, string secret, string issuer, string audience, int lifetime)
public string GenerateToken(Alias alias, ClaimsIdentity user, string secret, string issuer, string audience, int lifetime)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(secret);
var identity = UserSecurity.CreateClaimsIdentity(alias, user);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(identity),
Subject = new ClaimsIdentity(user),
Issuer = issuer,
Audience = audience,
Expires = DateTime.UtcNow.AddMinutes(lifetime),
@ -56,7 +55,7 @@ namespace Oqtane.Security
var jwtToken = (JwtSecurityToken)validatedToken;
var user = new User
{
UserId = int.Parse(jwtToken.Claims.FirstOrDefault(item => item.Type == "id")?.Value),
UserId = int.Parse(jwtToken.Claims.FirstOrDefault(item => item.Type == "nameid")?.Value),
Username = jwtToken.Claims.FirstOrDefault(item => item.Type == "name")?.Value
};
return user;

View File

@ -28,7 +28,7 @@ namespace Oqtane.Security
var claims = context.Principal.Claims;
// check if principal has roles and matches current site
if (!claims.Any(item => item.Type == ClaimTypes.Role) || claims.FirstOrDefault(item => item.Type == ClaimTypes.GroupSid)?.Value != alias.SiteKey)
if (!claims.Any(item => item.Type == ClaimTypes.Role) || claims.FirstOrDefault(item => item.Type == "sitekey")?.Value != alias.SiteKey)
{
var userRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRepository)) as IUserRepository;
var userRoleRepository = context.HttpContext.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;

View File

@ -49,9 +49,9 @@ namespace Oqtane.Security
if (user.IsAuthenticated)
{
user.Username = principal.Identity.Name;
if (principal.Claims.Any(item => item.Type == ClaimTypes.PrimarySid))
if (principal.Claims.Any(item => item.Type == ClaimTypes.NameIdentifier))
{
user.UserId = int.Parse(principal.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value);
user.UserId = int.Parse(principal.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value);
}
foreach (var claim in principal.Claims.Where(item => item.Type == ClaimTypes.Role))
{

View File

@ -70,7 +70,7 @@ namespace Oqtane
});
// setup HttpClient for server side in a client side compatible fashion ( with auth cookie )
services.TryAddHttpClientWithAuthenticationCookie();
services.AddHttpClients();
// register scoped core services
services.AddScoped<IAuthorizationHandler, PermissionHandler>()

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB