我有一个使用IdentityServer4和PolicyServer.Local的项目。 IdentityServer4已经有一个用于在数据库中存储必要数据的实现,但是PolicyServer没有。
所以我尝试自己成功实现它,但是从我认为我要替换许多PolicyServers代码的意义上来说,这感觉并不好。
例如,我替换了所有PolicyServers实体类(策略,权限,角色),并添加了自己的类,因此我可以解析List属性,这是因为Entity Framework基本上无法映射List。
我还添加了自己的PolicyServerRuntimeClient,因为我需要将Evaluate-Methods调整为新的Entity-class。
首先是我的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-,并通过PermissionRoles-classs解决m-n关系。
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; }
}
这将是我在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);
但是最后我确实做了比预期更多的替换。
答案 0 :(得分:1)
您不必在PolicyServer中进行任何更改,只需添加一个新的配置提供程序即可返回所需的设置。 PolicyServer从.NET Core的配置基础结构中读取其配置。它与appsettings.json
无关。
.NET Core can read configuration,可通过providers从任何来源获取。这些提供程序没有做任何复杂的事情,他们“只是”读取其实际来源,并以以下形式生成键/值字符串对:
"array:entries:0"= "value0"
"array:entries:1"= "value1"
"array:entries:2"= "value2"
"array:entries:4"= "value4"
"array:entries:5"= "value5"
appsettings.json
没有特殊含义,它只是.NET Core JSON configuration provider从中读取键/值设置的JSON文件。该文件可以被命名为任何东西。可以从字典,数据库,远程配置服务等中加载相同的数据。
例如这个字典:
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"
]
}
}
使用字典
您可以使用Memory configuration provider从字典中加载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 Configuration 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);
}
}
}