Сохранять сущности в базе данных вместо appsettings.json
У меня есть проект, где я использую IdentityServer4 и PolicyServer.Local. У IdentityServer4 уже есть реализация для хранения необходимых данных в базе данных, но PolicyServer нет.
Поэтому я попытался реализовать это сам, но с успехом, но это не очень хорошо в том смысле, что я думаю, что заменяю большую часть кода PolicyServers.
Как, например, я заменил все классы Entity PolicyServers (Policy, Permission, Roles) и добавил свои собственные, чтобы я мог разрешить свойства List, и все это потому, что Entity Framework не может отобразить List в основном.
Я также добавил свой собственный PolicyServerRuntimeClient, потому что мне нужно было настроить Evaluate-Methods для новых Entity-классов.
Первый из моих Startup.cs:
services.AddDbContext<AuthorizeDbContext>(builder =>
builder.UseSqlite(csAuthorizeContext, sqlOptions =>
sqlOptions.MigrationsAssembly(migrationsAssembly)));
services.AddScoped<IAuthorizeService, AuthorizeService>()
.AddTransient<IPolicyServerRuntimeClient, CustomPolicyServerRuntimeClient>()
.AddScoped(provider => provider.GetRequiredService<IOptionsSnapshot<Policy>>().Value);
new PolicyServerBuilder(services).AddAuthorizationPermissionPolicies();
(AuthorizeService для получения значений из базы данных)
Например, это мои Permission-, Roles- и для разрешения mn-отношения PermissionRoles-classes.
public class Permission
{
[Key]
public string Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
[ForeignKey("Policy")]
public string PolicyId { get; set; }
public IList<PermissionRole> PermissionRoles { get; set; }
}
public class PermissionRole
{
[Key]
public string Id { get; set; }
[Required]
public string PermissionId { get; set; }
public Permission Permission { get; set; }
[Required]
public string RoleId { get; set; }
public Role Role { get; set; }
}
public class Role
{
[Key]
public string Id { get; set; }
[Required]
public string Name { get; set; }
public IList<PermissionRole> PermissionRoles { get; set; }
}
и это были бы мои Evalute-методы в CustomPolicyServerRuntimeClient:
public async Task<PolicyResult> EvaluateAsync(ClaimsPrincipal user)
{
if (user == null)
throw new ArgumentNullException(nameof(user));
var sub = user.FindFirst("sub")?.Value;
if (String.IsNullOrWhiteSpace(sub))
return null;
var roles = _auth.Roles
.ToList()
.Where(x => EvaluateRole(x, user))
.Select(x => x.Name)
.ToArray();
var permissions = _auth.Permissions
.ToList()
.Where(x => EvaluatePermission(x, roles))
.Select(x => x.Name)
.ToArray();
var result = new PolicyResult()
{
Roles = roles.Distinct(),
Permissions = permissions.Distinct()
};
return await Task.FromResult(result);
}
internal bool EvaluateRole(Role role, ClaimsPrincipal user)
{
if (user == null)
throw new ArgumentNullException(nameof(user));
var subClaim = user.FindFirst("sub")?.Value;
var subjectsOfDbRole = _auth.UserDetails
.ToList()
.Where(x => x.RoleId.Equals(role.Id))
.Select(x => x.Subject)
.ToList();
return subjectsOfDbRole.Contains(subClaim);
}
public bool EvaluatePermission(Permission permission, IEnumerable<string> roles)
{
if (roles == null)
throw new ArgumentNullException(nameof(roles));
var permissionRoles = _auth.PermissionRoles
.ToList()
.Where(y => y.PermissionId.Equals(permission.Id))
.ToList();
if (permissionRoles.Any(x => roles.Contains(x.Role.Name)))
return true;
return false;
}
это основные изменения, которые я сделал, чтобы заставить его работать.
Я не хочу делать много работы в Бэкэнде, прежде чем я пойму, как сделать это правильно.
Ожидаемый результат состоял в том, что мне, вероятно, просто нужно было заменить
services.Configure<Policy>(configuration);
но в конце концов я заменил путь больше, чем ожидалось.
1 ответ
Вам не нужно ничего менять в PolicyServer, просто добавьте нового поставщика конфигурации, который возвращает нужные вам параметры. PolicyServer считывает свою конфигурацию из инфраструктуры конфигурации.NET Core. Это не связано с appsettings.json
,
.NET Core может считывать конфигурацию из любого источника через провайдеров. Эти провайдеры не делают ничего сложного, они "просто" читают любой фактический источник и создают пары ключ / значение в виде:
"array:entries:0"= "value0"
"array:entries:1"= "value1"
"array:entries:2"= "value2"
"array:entries:4"= "value4"
"array:entries:5"= "value5"
appsettings.json
не имеет особого значения, это просто файл JSON, из которого поставщик конфигурации JSON.NET Core считывает настройки ключ / значение. Файл может быть назван как угодно. Эти же данные могут быть загружены из словаря, базы данных, службы удаленной настройки и т. Д.
Этот словарь например:
public static Dictionary<string, string> arrayDict = new Dictionary<string, string>
{
{"array:entries:0", "value0"},
{"array:entries:1", "value1"},
{"array:entries:2", "value2"},
{"array:entries:4", "value4"},
{"array:entries:5", "value5"}
};
Предоставляет те же данные конфигурации, что и этот файл JSON:
{
"array" : {
"entries" : [
"value1",
"value2",
"value3",
"value4",
"value5"
]
}
}
Использование словаря
Вы можете загрузить настройки PolicyServer из словаря с помощью поставщика конфигурации памяти. В вашем разделе конфигурации:
public static readonly Dictionary<string, string> _dict =
new Dictionary<string, string>
{
{"Policy:roles:0:name", "doctor"},
{"Policy:roles:0:subjects:0", "1"},
{"Policy:roles:0:subjects:1", "2"},
{"Policy:roles:1:name", "patient"},
{"Policy:roles:1:identityRoles:0", "customer"},
};
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddInMemoryCollection(_dict);
})
.UseStartup<Startup>();
Когда вы звоните AddPolicyServerClient(Configuration.GetSection("Policy"))
в регистрационном коде вашей службы настройки будут исходить из этого словаря.
Использование необработанной таблицы
Вы можете создать свой собственный поставщик конфигурации, как показано в Custom Provider, который извлекал параметры из ID/Value
Таблица. Вы должны хранить полный ключ в ID
поле, которое может быть немного раздражающим, например:
CREATE TABLE MyPolicySettings (ID varchar(200) PRIMARY KEY,value varchar(200))
INSERT INTO TABLE MyPolicySettings (ID,Value)
VALUES
("Policy:roles:0:name", "doctor"},
("Policy:roles:0:subjects:0", "1"),
("Policy:roles:0:subjects:1", "2"),
("Policy:roles:1:name", "patient"),
("Policy:roles:1:identityRoles:0", "customer");
Используя EF
Другой вариант - сохранить настройки в соответствующих таблицах, например, Roles
, Subjects
, IdentityRoles
и использовать ORM для загрузки всей структуры. Как только он у вас появится, вам придется воспроизвести структуру ключа, например, перебирая объекты в итераторе:
public IEnumerable<KeyValuePair<string,string>> FlattenRoles(IEnumerable<MyRole> roles)
{
int iRole=0;
foreach(var role in roles)
{
var rolePart=$"Policy:roles:{i}";
var namePair=new KeyValuePair($"{rolePart}:name",role.Name);
yield return namePair;
int iSubject=0;
foreach(var subjectPair in FlattenSubjects(role.Subject))
{
yield return subjectPair
}
//Same for identity roles etc
iRole++;
}
}
public IEnumerable<KeyValuePair<string,string>> FlattenSubjects(IEnumerable<MySubject> subjects,string rolePart)
{
var pairs=subjects.Select((subject,idx)=>
new KeyValuePair($"{rolePart}:subjects:{idx}",subject.Value);
return pairs;
}
Ваш поставщик пользовательских настроек может использовать это для загрузки строго типизированных классов из базы данных, их выравнивания и преобразования в словарь, например:
public class MyPolicyConfigurationProvider: ConfigurationProvider
{
public MyPolicyConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction)
{
OptionsAction = optionsAction;
}
Action<DbContextOptionsBuilder> OptionsAction { get; }
// Load config data from EF DB.
public override void Load()
{
var builder = new DbContextOptionsBuilder<MyPoliciesContext>();
OptionsAction(builder);
using (var dbContext = new MyPoliciesContext(builder.Options))
{
var keys=FlattenRoles(dbContext.Roles);
Data=new Dictionary<string,string>(keys);
}
}
}