使用 IdentityServer4 进行身份验证和授权的正确架构
Right architecture for Authentication and Authorization with IdentityServer4
我正在尝试在 IdentityServer4 (IS4) 上实施带有身份验证的正确架构。
因此,我将有一个服务器作为身份提供者,使用用于 SSO 的 oidc 和 oauth2 令牌以及用于保护网络的访问令牌 api。用户配置文件将存储在 IS4 数据库中。
我有很多应用程序会引用 IS4,但让我们考虑一个简单的应用程序。
我读了一些关于身份验证和授权之间区别的文章,如果我理解正确的话,将授权信息存储在声明中或在 IS4 中使用其他技巧并不是一个好主意。它应该只管理用户的身份及其属性,而不是它在其他应用程序中的权限。
所以,我的疑问是关于权限的管理……关于授权。
每个应用程序是否都知道所有用户,通过 id 与 IS4 表示相匹配,以管理特定权限?这意味着我必须将每个应用程序数据库与 IS4 数据库同步!
是否最好实施一种授权管理服务,以存储无法从声明中检索的规则?
这是问题的一个例子:
- 用户 John 是“标准用户”。我在索赔中看到了它。我可以得到关于他的通用角色的信息。
- 因为约翰不是“管理员”,他无法访问应用程序中的打印和设置菜单。
- 我想将访问打印菜单的权限动态添加到约翰,而不是设置菜单。
由于 John 将维护“标准用户”角色,我想我必须在特定应用程序中存储“显示打印菜单”权限的信息。
我对架构的看法是否正确,或者是否有更好的方法来实现该场景?
我会在 IdentityServer 中放置真正全局的声明,例如姓名、电子邮件,然后在每个应用程序中放置特定于应用程序的声明。只是为了遵循关注点分离。如果你把所有的声明都放在 IdentityServer 中,否则会很乱,最好遵循关注点分离。
您不需要在数据库之间开发一些自定义同步,而是在遇到新用户时在每个应用程序中创建一个本地条目。
在每个应用程序的登录过程中添加本地声明很容易使用声明转换来完成,就像这个例子:
public class BonusLevelClaimTransformation : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (!principal.HasClaim(c => c.Type == "bonuslevel"))
{
//Lookup bonus level.....
principal.Identities.First().AddClaim(new Claim("bonuslevel", "12345"));
}
return Task.FromResult(principal);
}
}
您应该尝试减少代币中的声明,否则它们会变得很大。此外,您添加到本地 ClaimsPrincipal 对象的声明越多,会话 cookie 就越大。
当会话 cookie 增长时,ASP.NET Core 会将其吐出到多个 cookie 中,例如:
您希望使您的令牌保持较小,并且并非每个声明都需要包含在令牌中才能满足基本的授权和身份验证需求。像“CanPrint”、“Address”或“UserWebPage”这样的声明可能不应该出现在它自己的令牌中。一种可能性是通过询问 UserInfo 端点 (documentation) 根据需要加载不常用的声明。
您还可以使用 GetClaimsFromUserInfoEndpoint 选项来发挥自己的优势。有关详细信息,请参阅 this 文章。
当您登录 IdentityServer 时,您同意您请求的范围。然后在接收回 ID 令牌的客户端应用程序中,它会获取其中一些声明并将它们放入会话 cookie 中。然后通常会丢弃 ID 令牌。然后,各个页面(从 UserInfo 端点)可以请求其他信息,这需要比在会话 cookie 中找到的声明更多的数据。例如,“UserWebPage”可能只在少数几个页面上需要,而“CanPrint”在其他一些页面上可能需要。或者,您可以在会话期间“以某种方式”下载并缓存它。但这取决于你如何做到这一点。您还确实希望保持会话 cookie 较小,因为它包含在每个请求中。
经过上面的讨论和观看 video 的结论是:
- 身份验证可以用IdentityServer4解决
- 对于授权,我们只有一些最佳实践,例如使用特定于应用程序的规则引擎将声明映射到权限。
- 新的应用程序可以应用授权逻辑:“Authorization Provider”(web api + web UI) 具有系统中所有应用程序的授权业务逻辑。
每个人都应该根据自己的场景写自己的授权引擎
我正在尝试在 IdentityServer4 (IS4) 上实施带有身份验证的正确架构。 因此,我将有一个服务器作为身份提供者,使用用于 SSO 的 oidc 和 oauth2 令牌以及用于保护网络的访问令牌 api。用户配置文件将存储在 IS4 数据库中。 我有很多应用程序会引用 IS4,但让我们考虑一个简单的应用程序。
我读了一些关于身份验证和授权之间区别的文章,如果我理解正确的话,将授权信息存储在声明中或在 IS4 中使用其他技巧并不是一个好主意。它应该只管理用户的身份及其属性,而不是它在其他应用程序中的权限。
所以,我的疑问是关于权限的管理……关于授权。 每个应用程序是否都知道所有用户,通过 id 与 IS4 表示相匹配,以管理特定权限?这意味着我必须将每个应用程序数据库与 IS4 数据库同步! 是否最好实施一种授权管理服务,以存储无法从声明中检索的规则?
这是问题的一个例子:
- 用户 John 是“标准用户”。我在索赔中看到了它。我可以得到关于他的通用角色的信息。
- 因为约翰不是“管理员”,他无法访问应用程序中的打印和设置菜单。
- 我想将访问打印菜单的权限动态添加到约翰,而不是设置菜单。
由于 John 将维护“标准用户”角色,我想我必须在特定应用程序中存储“显示打印菜单”权限的信息。
我对架构的看法是否正确,或者是否有更好的方法来实现该场景?
我会在 IdentityServer 中放置真正全局的声明,例如姓名、电子邮件,然后在每个应用程序中放置特定于应用程序的声明。只是为了遵循关注点分离。如果你把所有的声明都放在 IdentityServer 中,否则会很乱,最好遵循关注点分离。
您不需要在数据库之间开发一些自定义同步,而是在遇到新用户时在每个应用程序中创建一个本地条目。
在每个应用程序的登录过程中添加本地声明很容易使用声明转换来完成,就像这个例子:
public class BonusLevelClaimTransformation : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (!principal.HasClaim(c => c.Type == "bonuslevel"))
{
//Lookup bonus level.....
principal.Identities.First().AddClaim(new Claim("bonuslevel", "12345"));
}
return Task.FromResult(principal);
}
}
您应该尝试减少代币中的声明,否则它们会变得很大。此外,您添加到本地 ClaimsPrincipal 对象的声明越多,会话 cookie 就越大。
当会话 cookie 增长时,ASP.NET Core 会将其吐出到多个 cookie 中,例如:
您希望使您的令牌保持较小,并且并非每个声明都需要包含在令牌中才能满足基本的授权和身份验证需求。像“CanPrint”、“Address”或“UserWebPage”这样的声明可能不应该出现在它自己的令牌中。一种可能性是通过询问 UserInfo 端点 (documentation) 根据需要加载不常用的声明。
您还可以使用 GetClaimsFromUserInfoEndpoint 选项来发挥自己的优势。有关详细信息,请参阅 this 文章。
当您登录 IdentityServer 时,您同意您请求的范围。然后在接收回 ID 令牌的客户端应用程序中,它会获取其中一些声明并将它们放入会话 cookie 中。然后通常会丢弃 ID 令牌。然后,各个页面(从 UserInfo 端点)可以请求其他信息,这需要比在会话 cookie 中找到的声明更多的数据。例如,“UserWebPage”可能只在少数几个页面上需要,而“CanPrint”在其他一些页面上可能需要。或者,您可以在会话期间“以某种方式”下载并缓存它。但这取决于你如何做到这一点。您还确实希望保持会话 cookie 较小,因为它包含在每个请求中。
经过上面的讨论和观看 video 的结论是:
- 身份验证可以用IdentityServer4解决
- 对于授权,我们只有一些最佳实践,例如使用特定于应用程序的规则引擎将声明映射到权限。
- 新的应用程序可以应用授权逻辑:“Authorization Provider”(web api + web UI) 具有系统中所有应用程序的授权业务逻辑。
每个人都应该根据自己的场景写自己的授权引擎