refactor: Projektdateien migriert. Cloud-NuGet-Pakete durch lokale NuGet-Projekte ersetzt.
This commit is contained in:
21
HRD.LDAPService/JWT/HttpErrorDetails.cs
Normal file
21
HRD.LDAPService/JWT/HttpErrorDetails.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace HRD.LDAPService.JWT
|
||||
{
|
||||
public class HttpErrorDetails
|
||||
{
|
||||
public HttpErrorDetails(int statusCode, string message)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public string Message { get; set; }
|
||||
public int StatusCode { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return JsonConvert.SerializeObject(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
HRD.LDAPService/JWT/JWTAuthorizeAttribute.cs
Normal file
40
HRD.LDAPService/JWT/JWTAuthorizeAttribute.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using System;
|
||||
|
||||
namespace HRD.LDAPService.JWT
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class JWTAuthorizeAttribute : Attribute, IAuthorizationFilter
|
||||
{
|
||||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
{
|
||||
if (JwtTokenConfig.AktivateAuthorizationFilter)
|
||||
{
|
||||
bool isInWhiteList = false;
|
||||
|
||||
//allow access with logn & pwd and without Authorization token
|
||||
var path = context?.HttpContext.Request.Path.Value;
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
if (JwtTokenConfig.IsInBlackList(path))
|
||||
{
|
||||
context.Result = new JsonResult(new { message = $"Unauthorized access. Path is in a blacklist: '${path}'" }) { StatusCode = StatusCodes.Status403Forbidden };
|
||||
}
|
||||
|
||||
isInWhiteList = JwtTokenConfig.IsInWhiteList(path);
|
||||
|
||||
if (!isInWhiteList)
|
||||
{ //need jwt check
|
||||
var check = (string)context.HttpContext.Items[JwtGlobals.HttpContextItem_IsValidHenselToken];
|
||||
if (check == null)
|
||||
{
|
||||
context.Result = new JsonResult(new { message = $"Unauthorized access. Path: '${path}'" }) { StatusCode = StatusCodes.Status401Unauthorized };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
125
HRD.LDAPService/JWT/JWTAuthorizeVendorId.cs
Normal file
125
HRD.LDAPService/JWT/JWTAuthorizeVendorId.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace HRD.LDAPService.JWT
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class JWTAuthorizeVendorId : Attribute, IAuthorizationFilter
|
||||
{
|
||||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
{
|
||||
if (JwtTokenConfig.AktivateAuthorizationFilter)
|
||||
{
|
||||
//allow access with logn & pwd and without Authorization token
|
||||
var path = context?.HttpContext.Request.Path.Value;
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
if (JwtTokenConfig.IsInBlackList(path))
|
||||
{
|
||||
context.Result = new JsonResult(new { message = $"Unauthorized access. Path is in a blacklist: '${path}'" }) { StatusCode = StatusCodes.Status403Forbidden };
|
||||
return;
|
||||
}
|
||||
|
||||
if (JwtTokenConfig.IsInWhiteList(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else //need jwt check
|
||||
{
|
||||
var check = (string)context.HttpContext.Items[JwtGlobals.HttpContextItem_IsValidHenselToken];
|
||||
if (check == null)
|
||||
{
|
||||
context.Result = new JsonResult(new { message = $"Unauthorized access. Path: '${path}'" }) { StatusCode = StatusCodes.Status401Unauthorized };
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var headerAuthorization = context.HttpContext.Request.Headers["Authorization"];
|
||||
|
||||
var authorizationType = headerAuthorization.FirstOrDefault()?.Split(" ").First();
|
||||
if (authorizationType == null)
|
||||
{
|
||||
context.Result = new JsonResult(new { message = $"Eror. Path: '${path}'" }) { StatusCode = StatusCodes.Status401Unauthorized };
|
||||
return;
|
||||
}
|
||||
var jwt = headerAuthorization.FirstOrDefault();
|
||||
|
||||
if (!JwtManager.IsValidatJwtTokenSubject(jwt))
|
||||
{
|
||||
throw new UnauthorizedAccessException($"Not valid JWT");
|
||||
}
|
||||
|
||||
LdapUser ldapUser = JwtManager.DecryptTokenAsLdapUser(jwt);
|
||||
string ldapUserVendorId = ldapUser.GetExtendedAttributeValue("VendorId");
|
||||
if (string.IsNullOrEmpty(ldapUserVendorId))
|
||||
{
|
||||
context.Result = new JsonResult(new { message = $"Vendor Id is empty. Path: '${path}'" }) { StatusCode = StatusCodes.Status401Unauthorized };
|
||||
return;
|
||||
}
|
||||
var syncIOFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
|
||||
if (syncIOFeature != null)
|
||||
{
|
||||
syncIOFeature.AllowSynchronousIO = true;
|
||||
|
||||
var req = context.HttpContext.Request;
|
||||
|
||||
req.EnableBuffering();
|
||||
|
||||
// read the body here as a workarond for the JSON parser disposing the stream
|
||||
if (req.Body.CanSeek)
|
||||
{
|
||||
req.Body.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// if body (stream) can seek, we can read the body to a string for logging purposes
|
||||
using (var reader = new StreamReader(
|
||||
req.Body,
|
||||
encoding: Encoding.UTF8,
|
||||
detectEncodingFromByteOrderMarks: false,
|
||||
bufferSize: 8192,
|
||||
leaveOpen: true))
|
||||
{
|
||||
var jsonString = reader.ReadToEnd();
|
||||
var content = JsonConvert.DeserializeObject<dynamic>(jsonString);
|
||||
if (content == null)
|
||||
{
|
||||
context.Result = new JsonResult(new { message = $"Unauthorized access. Can not deserialize the the Request: '${path}'" }) { StatusCode = StatusCodes.Status403Forbidden };
|
||||
}
|
||||
|
||||
if (content?.Root != null && string.Equals(content?.Root?.Type.ToString(), "Array", StringComparison.InvariantCulture))
|
||||
{
|
||||
List<dynamic> contentList = JsonConvert.DeserializeObject<List<dynamic>>(jsonString);
|
||||
string jwtVendorId = contentList?.FirstOrDefault()?.vendorId;
|
||||
if (string.IsNullOrEmpty(jwtVendorId) || ldapUserVendorId != jwtVendorId)
|
||||
{
|
||||
context.Result = new JsonResult(new { message = $"Vendor {ldapUserVendorId} is not equal to Vendor from Token {jwtVendorId} List. Path: '${path}'" }) { StatusCode = StatusCodes.Status401Unauthorized };
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string jwtVendorId = content.vendorId;
|
||||
if (string.IsNullOrEmpty(jwtVendorId) || ldapUserVendorId != jwtVendorId)
|
||||
{
|
||||
context.Result = new JsonResult(new { message = $"Vendor {ldapUserVendorId} is not equal to Vendor from Token {jwtVendorId}. Path: '${path}'" }) { StatusCode = StatusCodes.Status401Unauthorized };
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Important! go back to beginning so json reader get's the whole thing
|
||||
req.Body.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
HRD.LDAPService/JWT/JWTCrypt.cs
Normal file
31
HRD.LDAPService/JWT/JWTCrypt.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace HRD.LDAPService.JWT
|
||||
{
|
||||
public static class JWTCrypt
|
||||
{
|
||||
public static string GenerateHashPassword(string inputKey)
|
||||
{
|
||||
return BCrypt.Net.BCrypt.EnhancedHashPassword(inputKey, 11, BCrypt.Net.HashType.SHA512);
|
||||
}
|
||||
|
||||
public static bool VerifyHashPassword(string hashedPassword, string providedPassword)
|
||||
{
|
||||
return BCrypt.Net.BCrypt.Verify(providedPassword, hashedPassword, true, BCrypt.Net.HashType.SHA512);
|
||||
}
|
||||
|
||||
public static string SHA512(string input)
|
||||
{
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(input);
|
||||
using (var hash = System.Security.Cryptography.SHA512.Create())
|
||||
{
|
||||
var hashedInputBytes = hash.ComputeHash(bytes);
|
||||
|
||||
// Convert to text
|
||||
// StringBuilder Capacity is 128, because 512 bits / 8 bits in byte * 2 symbols for byte
|
||||
var hashedInputStringBuilder = new System.Text.StringBuilder(128);
|
||||
foreach (var b in hashedInputBytes)
|
||||
hashedInputStringBuilder.Append(b.ToString("X2"));
|
||||
return hashedInputStringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
HRD.LDAPService/JWT/JwtGlobals.cs
Normal file
19
HRD.LDAPService/JWT/JwtGlobals.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace HRD.LDAPService.JWT
|
||||
{
|
||||
public static class JwtGlobals
|
||||
{
|
||||
public const string HttpContextItem_LdapUser = "ldapuser";
|
||||
public const string HttpContextItem_IsValidHenselToken = "IsValidHenselToken";
|
||||
|
||||
public const string CLAIM_DEPARTNENTID = "departmentid";
|
||||
public const string CLAIM_EXTENDETDEPARTNENTIDLIST = "extendetdepartmentidlist";
|
||||
|
||||
public const string CLAIM_ROLE = "role";
|
||||
|
||||
public const string ROLE_USER = "User";
|
||||
public const string ROLE_DEPARTMENTUSER = "DepartmentUser";
|
||||
public const string ROLE_DEPARTMENTMASTER = "DepartmentMaster";
|
||||
public const string ROLE_MASTER = "Master";
|
||||
public const string ROLE_ADMIN = "Admin";
|
||||
}
|
||||
}
|
||||
355
HRD.LDAPService/JWT/JwtManager.cs
Normal file
355
HRD.LDAPService/JWT/JwtManager.cs
Normal file
@@ -0,0 +1,355 @@
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace HRD.LDAPService.JWT
|
||||
{
|
||||
public static class JwtManager
|
||||
|
||||
{
|
||||
private const string GlbExtendedAttributes = "ExtendedAttributes_";
|
||||
|
||||
public static LdapUser DecryptTokenAsLdapUser(string token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token)) { return default; }
|
||||
//Check token with "Bearer" prefix
|
||||
if (token.StartsWith("Bearer", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
token = token.Split(" ").Last();
|
||||
}
|
||||
if (string.IsNullOrEmpty(token)) { return default; }
|
||||
|
||||
JwtSecurityToken jwtSecurityToken = DecryptToken(token);
|
||||
if (jwtSecurityToken == null) { return default; }
|
||||
|
||||
LdapUser ldapUser = ClaimsIdentityToLdapUser(jwtSecurityToken.Claims.ToList());
|
||||
if (ldapUser == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
ldapUser.Token = token;
|
||||
|
||||
return ldapUser;
|
||||
}
|
||||
|
||||
public static JwtSecurityToken DecryptToken(string token)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(JwtTokenConfig.Secret);
|
||||
try
|
||||
{
|
||||
tokenHandler.ValidateToken(token, new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(key),
|
||||
ValidateIssuer = false,
|
||||
ValidateAudience = false,
|
||||
ClockSkew = TimeSpan.FromSeconds(60) // set clockskew to zero so tokens expire exactly at token expiration time
|
||||
}, out SecurityToken validatedToken);
|
||||
|
||||
var jwtToken = (JwtSecurityToken)validatedToken;
|
||||
return jwtToken;
|
||||
}
|
||||
//IDX10223: Lifetime validation failed. The token is expired. ValidTo: 'System.DateTime', Current time: 'System.DateTime'.
|
||||
catch (SecurityTokenExpiredException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// return null if validation fails
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public static LdapUser RenewLdapUserWithJwtToken(string token)
|
||||
{
|
||||
LdapUser renewLdapUser = null;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
throw new ArgumentNullException($"Token is missing!");
|
||||
}
|
||||
|
||||
renewLdapUser = LdapAuthenticationService.RenewIdentity(token);
|
||||
if (renewLdapUser is null)
|
||||
{
|
||||
throw new Exception($"Can't renew from token!");
|
||||
}
|
||||
|
||||
if (!renewLdapUser.IsValidatCredentials)
|
||||
{
|
||||
throw new Exception($"Invalid credentials!");
|
||||
}
|
||||
|
||||
if (!renewLdapUser.Enabled)
|
||||
{
|
||||
throw new Exception($"Ldap-User is disabled!");
|
||||
}
|
||||
|
||||
(string newtoken, DateTime newExpiredOn) = CreateToken(renewLdapUser);
|
||||
renewLdapUser.Token = newtoken;
|
||||
renewLdapUser.JwtExpiredOn = newExpiredOn;
|
||||
|
||||
if (!renewLdapUser.IsValid())
|
||||
{
|
||||
throw new Exception($"Ldapuser is not valid!");
|
||||
}
|
||||
|
||||
return renewLdapUser;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public static LdapUser RenewLdapUserWithJwtToken(LdapUser ldapUser)
|
||||
{
|
||||
LdapUser renewLdapUser = null;
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(ldapUser?.Token))
|
||||
{
|
||||
throw new Exception($"Token is missing (Login:{ldapUser.LoginName})");
|
||||
}
|
||||
|
||||
renewLdapUser = LdapAuthenticationService.RenewIdentity(ldapUser);
|
||||
if (renewLdapUser is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
//if (!ldapUser.IsValidatCredentials)
|
||||
//{
|
||||
// ldapUser.Token = string.Empty;
|
||||
// return false;
|
||||
//}
|
||||
|
||||
if (!renewLdapUser.Enabled)
|
||||
{
|
||||
renewLdapUser.Token = string.Empty;
|
||||
return renewLdapUser;
|
||||
}
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(JwtTokenConfig.Secret);
|
||||
var claims = CreateClaimsIdentity(renewLdapUser);
|
||||
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = claims,
|
||||
Expires = DateTime.UtcNow.AddMinutes(JwtTokenConfig.ExpirationInMin),
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
ldapUser.Token = tokenHandler.WriteToken(token);
|
||||
ldapUser.JwtExpiredOn = token.ValidTo;
|
||||
if (renewLdapUser.IsValid())
|
||||
{
|
||||
return renewLdapUser;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool GenerateLdapUserWithJwtToken(LdapUser ldapUser)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!LdapAuthenticationService.CheckAndUpdateIdentityWithPassword(ldapUser))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ldapUser.IsValidatCredentials)
|
||||
{
|
||||
ldapUser.Token = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ldapUser.Enabled)
|
||||
{
|
||||
ldapUser.Token = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
(string token, DateTime jwtExpiredOn) = CreateToken(ldapUser);
|
||||
ldapUser.Token = token;
|
||||
ldapUser.JwtExpiredOn = jwtExpiredOn;
|
||||
return ldapUser.IsValid();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GenerateHash(string password)
|
||||
{
|
||||
return JWTCrypt.SHA512(password);
|
||||
}
|
||||
|
||||
private static (string, DateTime) CreateToken(LdapUser ldapUser)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(JwtTokenConfig.Secret);
|
||||
var claims = CreateClaimsIdentity(ldapUser);
|
||||
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = claims,
|
||||
Expires = DateTime.UtcNow.AddMinutes(JwtTokenConfig.ExpirationInMin),
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
return (tokenHandler.WriteToken(token), token.ValidTo);
|
||||
}
|
||||
|
||||
public static bool IsValidatJwtTokenSubject(string token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
token = token.Trim();
|
||||
|
||||
if (token.IndexOf(" ", StringComparison.InvariantCultureIgnoreCase) > 0)
|
||||
{
|
||||
if (token.StartsWith("Bearer", StringComparison.InvariantCultureIgnoreCase)) //token with "Bearer" prefix
|
||||
{
|
||||
token = token.Split(" ").Last();
|
||||
}
|
||||
else
|
||||
{
|
||||
token = token.Split(" ").First();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var jwtToken = DecryptToken(token);
|
||||
return !String.IsNullOrEmpty(jwtToken?.Subject); //Loginname
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private static LdapUser ClaimsIdentityToLdapUser(List<Claim> claims)
|
||||
{
|
||||
LdapUser user = new LdapUser("");
|
||||
foreach (var claim in claims)
|
||||
{
|
||||
switch (claim.Type)
|
||||
{
|
||||
case JwtRegisteredClaimNames.Sub:
|
||||
user.LoginName = claim.Value;
|
||||
break;
|
||||
|
||||
case JwtRegisteredClaimNames.Email:
|
||||
user.Email = claim.Value;
|
||||
break;
|
||||
|
||||
case JwtRegisteredClaimNames.NameId:
|
||||
{
|
||||
if (int.TryParse(claim.Value, out int id))
|
||||
{
|
||||
user.UserId = id;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case JwtRegisteredClaimNames.Jti:
|
||||
{
|
||||
if (Guid.TryParse(claim.Value, out Guid g))
|
||||
{
|
||||
user.LdapGuid = g;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case JwtGlobals.CLAIM_DEPARTNENTID:
|
||||
user.DepartmentId = int.Parse(claim.Value);
|
||||
break;
|
||||
|
||||
case JwtGlobals.CLAIM_EXTENDETDEPARTNENTIDLIST:
|
||||
user.ExtendedDepartmentIdList = claim.Value;
|
||||
break;
|
||||
|
||||
case JwtGlobals.CLAIM_ROLE:
|
||||
user.AddRole(claim.Value);
|
||||
break;
|
||||
|
||||
case JwtRegisteredClaimNames.Exp:
|
||||
{
|
||||
//#pragma warning disable CA1305 // Specify IFormatProvider
|
||||
var expValue = Convert.ToInt32(claim.Value);
|
||||
//#pragma warning restore CA1305 // Specify IFormatProvider
|
||||
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(expValue);
|
||||
user.JwtExpiredOn = dateTimeOffset.UtcDateTime;
|
||||
}
|
||||
break;
|
||||
|
||||
case var s when claim.Type.StartsWith(GlbExtendedAttributes):
|
||||
var strKey = claim.Type.Substring(GlbExtendedAttributes.Length, claim.Type.Length - GlbExtendedAttributes.Length);
|
||||
user.ExtendedAttributesList.Add(new KeyValuePair<string, string>(strKey, claim.Value));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private static ClaimsIdentity CreateClaimsIdentity(LdapUser user)
|
||||
{
|
||||
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
|
||||
|
||||
List<Claim> claims = new List<Claim>
|
||||
{
|
||||
CreateClaim(JwtRegisteredClaimNames.Sub, user.LoginName),
|
||||
CreateClaim(JwtRegisteredClaimNames.NameId, user.UserId),
|
||||
CreateClaim(JwtRegisteredClaimNames.Email, user.Email),
|
||||
CreateClaim(JwtGlobals.CLAIM_DEPARTNENTID, user.DepartmentId),
|
||||
CreateClaim(JwtGlobals.CLAIM_EXTENDETDEPARTNENTIDLIST, user.ExtendedDepartmentIdList)
|
||||
};
|
||||
|
||||
user.RoleList.ForEach(role => claims.Add(
|
||||
CreateClaim(ClaimTypes.Role, role.Role)
|
||||
));
|
||||
|
||||
user.ExtendedAttributesList.ForEach(item => claims.Add(
|
||||
CreateClaim($"{GlbExtendedAttributes}{item.Key}", item.Value)
|
||||
));
|
||||
|
||||
claimsIdentity.AddClaims(claims);
|
||||
return claimsIdentity;
|
||||
}
|
||||
|
||||
private static Claim CreateClaim(string claimName, int claimValue)
|
||||
{
|
||||
return new Claim(claimName, string.IsNullOrEmpty($"{claimValue}") ? string.Empty : $"{claimValue}");
|
||||
}
|
||||
|
||||
private static Claim CreateClaim(string claimName, string claimValue)
|
||||
{
|
||||
return new Claim(claimName, string.IsNullOrEmpty(claimValue) ? string.Empty : claimValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
HRD.LDAPService/JWT/JwtMiddleware.cs
Normal file
97
HRD.LDAPService/JWT/JwtMiddleware.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HRD.LDAPService.JWT
|
||||
{
|
||||
public class JwtMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public JwtMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
#pragma warning disable AMNF0001 // Asynchronous method name is not ending with 'Async'
|
||||
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
#pragma warning restore AMNF0001 // Asynchronous method name is not ending with 'Async'
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException($"Jwt {httpContext} is null");
|
||||
}
|
||||
|
||||
if (JwtTokenConfig.AktivateAuthorizationFilter)
|
||||
{
|
||||
var path = httpContext.Request.Path.Value;
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
if (JwtTokenConfig.IsInBlackList(path))
|
||||
{
|
||||
httpContext.Response.ContentType = MediaTypeNames.Application.Json;
|
||||
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
var responseJson = JsonConvert.SerializeObject($"Path is in a blacklist: '${path}'");
|
||||
await httpContext.Response.WriteAsync(responseJson).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (JwtTokenConfig.IsInWhiteList(path))
|
||||
{
|
||||
await _next(httpContext).ConfigureAwait(false); // calling next middleware
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
var headerAuthorization = httpContext.Request.Headers["Authorization"];
|
||||
|
||||
var authorizationType = headerAuthorization.FirstOrDefault()?.Split(" ").First();
|
||||
if (authorizationType == null)
|
||||
{
|
||||
await _next(httpContext).ConfigureAwait(false); // calling next middleware
|
||||
return;
|
||||
}
|
||||
var jwt = headerAuthorization.FirstOrDefault();
|
||||
|
||||
//Check token
|
||||
if (JwtManager.IsValidatJwtTokenSubject(jwt))
|
||||
{
|
||||
var user = JwtManager.DecryptTokenAsLdapUser(jwt);
|
||||
if (user == default)
|
||||
{
|
||||
httpContext.Response.ContentType = MediaTypeNames.Application.Json;
|
||||
httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
var responseJson = JsonConvert.SerializeObject($"Failed to decode JWT. The User was not valid");
|
||||
await httpContext.Response.WriteAsync(responseJson).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
httpContext.Items[JwtGlobals.HttpContextItem_IsValidHenselToken] = "true";
|
||||
httpContext.Items[JwtGlobals.HttpContextItem_LdapUser] = user;
|
||||
await _next(httpContext).ConfigureAwait(false); // calling next middleware
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
httpContext.Response.ContentType = MediaTypeNames.Application.Json;
|
||||
httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
var responseJson = JsonConvert.SerializeObject($"The JWT was not valid.");
|
||||
await httpContext.Response.WriteAsync(responseJson).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetAction(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext.Request.Headers.ContainsKey("action"))
|
||||
{
|
||||
return httpContext.Request.Headers["action"].ToString();
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
HRD.LDAPService/JWT/JwtMiddlewareExtensions.cs
Normal file
69
HRD.LDAPService/JWT/JwtMiddlewareExtensions.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace HRD.LDAPService.JWT
|
||||
{
|
||||
public static class JwtMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseJwtMiddleware(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<JwtMiddleware>();
|
||||
}
|
||||
|
||||
public static void ConfigureJWT(this IServiceCollection services, JwtMiddlewareOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
JwtTokenConfig.DeaktivateLDAP = options.DeaktivateLDAP; //if true => use login + pwd only
|
||||
|
||||
JwtTokenConfig.Secret = options.Secret;
|
||||
JwtTokenConfig.Issuer = options.Issuer;
|
||||
JwtTokenConfig.Audience = options.Audience;
|
||||
JwtTokenConfig.JwtRoleList = options.JwtRoleList;
|
||||
JwtTokenConfig.ExpirationInMin = options.ExpirationInMin;
|
||||
JwtTokenConfig.AktivateAuthorizationFilter = options.AktivateAuthorizationFilter;
|
||||
JwtTokenConfig.AuthorizationFilterWhitelistPath = options.AuthorizationFilterWhitelistPath;
|
||||
JwtTokenConfig.AuthorizationFilterBlacklistPath = options.AuthorizationFilterBlacklistPath;
|
||||
|
||||
//Authentication
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
//JwtBearer
|
||||
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
options.RequireHttpsMetadata = true;
|
||||
options.SaveToken = true;
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = JwtTokenConfig.Issuer, //JWT-Site
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtTokenConfig.Secret)),
|
||||
ValidAudience = JwtTokenConfig.Audience,
|
||||
ValidateAudience = true, //App-Site
|
||||
ValidateLifetime = true,
|
||||
ClockSkew = TimeSpan.FromMinutes(1),
|
||||
NameClaimType = ClaimTypes.NameIdentifier
|
||||
};
|
||||
});
|
||||
|
||||
//Authorization
|
||||
services.AddAuthorization(authopt =>
|
||||
{
|
||||
authopt.AddPolicy("UserMustHaveRole", polBuilder => polBuilder.RequireClaim(ClaimTypes.Role));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
18
HRD.LDAPService/JWT/JwtMiddlewareOptions.cs
Normal file
18
HRD.LDAPService/JWT/JwtMiddlewareOptions.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HRD.LDAPService.JWT
|
||||
{
|
||||
public class JwtMiddlewareOptions
|
||||
{
|
||||
public string Secret { get; set; }
|
||||
|
||||
public int ExpirationInMin { get; set; } = 60 * 24 * 28; //28 Tage
|
||||
public List<JwtRole> JwtRoleList { get; set; }
|
||||
public List<string> AuthorizationFilterWhitelistPath { get; set; }
|
||||
public List<string> AuthorizationFilterBlacklistPath { get; set; }
|
||||
public bool AktivateAuthorizationFilter { get; set; } = true;
|
||||
public string Issuer { get; set; }
|
||||
public string Audience { get; set; }
|
||||
public bool DeaktivateLDAP { get; set; }
|
||||
}
|
||||
}
|
||||
32
HRD.LDAPService/JWT/JwtRole.cs
Normal file
32
HRD.LDAPService/JWT/JwtRole.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace HRD.LDAPService
|
||||
{
|
||||
public class JwtRole
|
||||
{
|
||||
private string role;
|
||||
private string _group;
|
||||
|
||||
public JwtRole(string role) : this(role, string.Empty)
|
||||
{ }
|
||||
|
||||
public JwtRole(string role, string group)
|
||||
{
|
||||
Role = role;
|
||||
Group = group;
|
||||
}
|
||||
|
||||
[Required]
|
||||
public string Role { get => role; set => role = value; }
|
||||
|
||||
[Required]
|
||||
public string Group
|
||||
{
|
||||
get => _group;
|
||||
|
||||
//"dhr/" prefix should be removed
|
||||
set => _group = string.IsNullOrEmpty(value) ? string.Empty : value.Replace(@"DHR\", "", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
HRD.LDAPService/JWT/JwtTokenConfig.cs
Normal file
49
HRD.LDAPService/JWT/JwtTokenConfig.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HRD.LDAPService.JWT
|
||||
{
|
||||
public static class JwtTokenConfig
|
||||
{
|
||||
private static string secret;
|
||||
|
||||
public static int ExpirationInMin { get; set; }
|
||||
public static string Secret { get; set; }
|
||||
public static string Issuer { get; internal set; }
|
||||
public static string Audience { get; internal set; }
|
||||
|
||||
#warning use internal setter
|
||||
public static List<JwtRole> JwtRoleList { get; set; } = new List<JwtRole>();
|
||||
public static List<string> AuthorizationFilterWhitelistPath { get; set; }
|
||||
public static List<string> AuthorizationFilterBlacklistPath { get; set; }
|
||||
public static bool AktivateAuthorizationFilter { get; set; }
|
||||
public static bool DeaktivateLDAP { get; set; }
|
||||
|
||||
public static bool IsInWhiteList(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path)) { return false; }
|
||||
foreach (var item in JwtTokenConfig.AuthorizationFilterWhitelistPath)
|
||||
{
|
||||
if (path.Contains(item, System.StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsInBlackList(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path)) { return false; }
|
||||
foreach (var item in JwtTokenConfig.AuthorizationFilterBlacklistPath)
|
||||
{
|
||||
if (path.Contains(item, System.StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user