新文章 网摘 文章 随笔 日记

IdentityServer4 授权和使用声明

我们可以使用声明在应用程序中显示与标识相关的信息,但也可以将其用于授权过程。在本文中,我们将学习如何修改我们的声明并添加新的声明。此外,我们还将了解 IdentityServer4 授权过程,以及如何使用角色来保护我们的端点。

要下载客户端应用程序的源代码,可以访问身份服务器 4 授权存储库。

要浏览整个系列,请访问身份服务器4 系列页面。

所以,让我们开始吧。

修改声明

如果我们使用“隐私”页面上的声明检查我们的解码,我们将发现一些命名差异:id_token

不同的声明 - 标识服务器4 授权

 

因此,我们想做的是确保我们的声明与我们定义的声明保持不变,而不是映射到不同的声明。例如,名称标识符声明是从子声明映射的,我们希望它保留子声明。为此,我们必须稍微修改客户端类中的构造函数:Startup

 

 

public Startup(IConfiguration configuration)
{
Configuration = configuration;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
}

 

为此,我们必须添加 using 语句。System.IdentityModel.Tokens.Jwt

现在,我们可以启动应用程序,从客户端注销,再次登录,然后检查隐私页面:

正确的映射声明 - 标识服务器 4 授权

 

我们可以看到我们的声明与我们在 IDP(标识提供者)级别定义的声明相同。

如果令牌中有一些我们不希望包含的声明,我们可以将其删除。为此,我们必须在 OIDC 配置中使用:ClaimActions

 

 

.AddOpenIdConnect("oidc", opt =>
{
opt.SignInScheme = "Cookies";
opt.Authority = "https://localhost:5005";
opt.ClientId = "mvc-client";
opt.ResponseType = "code id_token";
opt.SaveTokens = true;
opt.ClientSecret = "MVCSecret";
opt.GetClaimsFromUserInfoEndpoint = true;
 
opt.ClaimActions.DeleteClaim("sid");
opt.ClaimActions.DeleteClaim("idp");
});

 

该方法存在于命名空间中。作为参数,我们传递要删除的声明。现在,如果我们再次启动客户端并导航到“隐私”页面,则肯定会丢失这些声明(在检查“隐私”页面之前注销并登录)。DeleteClaimMicrosoft.AspNetCore.Authentication

如果不想对要删除的每个声明使用该方法,则始终可以使用该方法:DeleteClaimDeleteClaims

 

opt.ClaimActions.DeleteClaims(new string[] { "sid", "idp" });

让我们继续前进。

添加其他声明

如果我们想向令牌添加其他声明(例如地址),可以通过几个简单的步骤来完成。第一步是在 IDP 项目的类中支持新的标识资源:InMemoryConfig

 

 

public static IEnumerable<IdentityResource> GetIdentityResources() =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Address()
};

 

然后,我们必须将其添加到客户端的允许范围内:

 

 

new Client
{
ClientName = "MVC Client",
ClientId = "mvc-client",
AllowedGrantTypes = GrantTypes.Hybrid,
RedirectUris = new List<string>{ "https://localhost:5010/signin-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Address
},
ClientSecrets = { new Secret("MVCSecret".Sha512()) },
PostLogoutRedirectUris = new List<string> { "https://localhost:5010/signout-callback-oidc" }
}

 

最后,我们必须为用户添加地址声明:

 

 

new TestUser
{
SubjectId = "a9ea0f25-b964-409f-bcce-c923266249b4",
Username = "Mick",
Password = "MickPassword",
Claims = new List<Claim>
{
new Claim("given_name", "Mick"),
new Claim("family_name", "Mining"),
new Claim("address", "Sunny Street 4")
}
},
new TestUser
{
SubjectId = "c95ddb8c-79ec-488a-a485-fe57a1462340",
Username = "Jane",
Password = "JanePassword",
Claims = new List<Claim>
{
new Claim("given_name", "Jane"),
new Claim("family_name", "Downing"),
new Claim("address", "Long Avenue 289")
}
}

 

 

而且,这就是关于班级的全部内容。InMemoryConfig

还有一件事。如果我们要查看特定客户端的同意页面,可以在 OAuth 项目的“客户端配置”中启用该页面:

 

 

ClientSecrets = { new Secret("MVCSecret".Sha512()) },
PostLogoutRedirectUris = new List<string> { "https://localhost:5010/signout-callback-oidc" },
RequireConsent = true

 

现在,我们必须通过将新作用域添加到 OIDC 配置来修改客户端应用程序:

 

 

.AddOpenIdConnect("oidc", opt =>
{
//previous code
 
opt.ClaimActions.DeleteClaim("sid");
opt.ClaimActions.DeleteClaim("idp");
 
opt.Scope.Add("address");
});

 

如果我们注销并重新登录,我们将在屏幕上看到一个新的范围:Consent

同意屏幕中的其他声明

但是,如果我们检查“隐私”页面,我们将无法在那里找到地址声明。这是因为我们没有将其映射到我们的声明中。当然,我们可以检查控制台日志,以确保 IdentityServer 返回了我们的新声明:

 

地址声明

但是,如果我们想包含它,我们可以修改 OIDC 配置:

opt.ClaimActions.MapUniqueJsonKey("address", "address");

再次登录后,我们可以在“隐私”页面上找到地址声明。

我们只想提一下,如果你不需要整个应用程序的所有附加声明,而只需要其中的一部分,最佳做法是不要映射所有声明。始终可以通过将请求发送到终结点来获取它们与包一起。通过这样做,我们确保我们的cookie尺寸小,并且我们始终从用户信息端点获取最新信息。IdentityModel/userinfo

从用户信息终结点手动获取声明

因此,让我们看看如何从终结点中提取地址声明。我们要做的第一件事是从 OIDC 配置中删除该语句。/userinfoMapUniqueJsonKey(„address“, „address“)

然后,让我们安装所需的软件包:

标识模型包

 

之后,让我们修改控制器中的操作:PrivacyHome

 

public async Task<IActionResult> Privacy()
{
var client = new HttpClient();
var metaDataResponse = await client.GetDiscoveryDocumentAsync("https://localhost:5005");
 
var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
 
var response = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = metaDataResponse.UserInfoEndpoint,
Token = accessToken
});
 
if(response.IsError)
{
throw new Exception("Problem while fetching data from the UserInfo endpoint", response.Exception);
}
 
var addressClaim = response.Claims.FirstOrDefault(c => c.Type.Equals("address"));
 
User.AddIdentity(new ClaimsIdentity(new List<Claim> { new Claim(addressClaim.Type.ToString(), addressClaim.Value.ToString()) }));
 
return View();
}

因此,我们创建一个新的客户端对象,并使用该方法从标识服务器获取响应。此响应包含我们所需的终结点的地址。之后,我们提取访问令牌,并使用地址和提取的令牌来获取所需的用户信息。如果响应成功,我们会从声明列表中提取地址声明,然后将其添加到列表中(这是我们在“隐私”视图中迭代的声明列表)。GetDiscoveryDocumentAsync/userinfoUserInfoUser.Claims

现在,如果我们再次登录并导航到“隐私”页面,我们将再次看到地址声明。但这一次,我们手动提取了它。所以基本上,我们只能在应用程序中需要它的时候才可以使用这段代码。

身份服务器4 授权

授权是确定身份验证后允许您执行的操作的过程。帮助我们完成身份验证过程,而 帮助我们完成授权过程,因为它授权 Web 客户端应用程序与 Web api 通信。id_tokenaccess_token

因此,让我们从类修改开始,通过向用户添加角色:InMemoryConfig

 

 

public static List<TestUser> GetUsers() =>
new List<TestUser>
{
new TestUser
{
//previous code
 
Claims = new List<Claim>
{
new Claim("given_name", "Mick"),
new Claim("family_name", "Mining"),
new Claim("address", "Sunny Street 4"),
new Claim("role", "Admin")
}
},
new TestUser
{
//previous code
 
Claims = new List<Claim>
{
new Claim("given_name", "Jane"),
new Claim("family_name", "Downing"),
new Claim("address", "Long Avenue 289"),
new Claim("role", "Visitor")
}
}
};

 

我们必须在方法中创建新的标识范围:GetIdentityResources

 

 

public static IEnumerable<IdentityResource> GetIdentityResources() =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Address(),
new IdentityResource("roles", "User role(s)", new List<string> { "role" })
};

 

 

而且,我们必须将角色作用域添加到 MVC 客户端允许的作用域中:

 

 

AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Address,
"roles"
},

 

这样,我们已经完成了IDP应用程序的修改。让我们通过修改 OIDC 配置以支持角色范围来继续使用客户端应用程序:

 

 

.AddOpenIdConnect("oidc", opt =>
{
//previous code
 
opt.Scope.Add("address");
//opt.ClaimActions.MapUniqueJsonKey("address", "address");
 
opt.Scope.Add("roles");
opt.ClaimActions.MapUniqueJsonKey("role", "role");
});

 

因此,我们希望仅允许具有 Admin 角色的用户执行“创建”、“编辑”、“详细信息”和“删除”操作。为此,我们将修改视图:Index

 

 

@if (User.IsInRole("Admin"))
{
<p>
<a asp-action="Create">Create New</a>
</p>
}
<table class="table">
 
//previous code
 
<tbody>
@foreach (var item in Model)
{
<tr>
//previous code
 
@if (User.IsInRole("Admin"))
{
<td>
@Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
@Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
@Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>
}
</tr>
}
</tbody>
</table>

 

我们使用的方法仅允许管理员用户查看这些链接。IsInRole

最后,我们必须说明我们的框架可以在哪里可以找到用户的角色:

 

 

 

.AddOpenIdConnect("oidc", opt =>
{
//previous code
 
opt.Scope.Add("roles");
opt.ClaimActions.MapUniqueJsonKey("roles", "role");
 
opt.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "role"
};
});

 

该类存在于命名空间中。TokenValidationParametersMicrosoft.IdentityModel.Tokens

现在,我们可以启动应用程序并使用Jane的帐户登录:

已添加角色 - 身份服务器4 授权

我们可以在“同意”屏幕中看到其他范围。一旦我们允许这样做,我们就可以看到“索引”视图,但无需其他操作。这是因为 Jane 处于“访客”角色。如果我们注销并使用Mick登录,我们肯定会看到这些链接。

非常好。

但是,我们是否也可以使用角色来保护我们的端点?当然可以。让我们看看如何做到这一点。

使用角色保护端点

例如,Le说,只有管理员用户才能访问隐私页面。好吧,使用我们在上一部分中执行的相同操作,我们可以在视图中显示“隐私”链接:_Layout

 

 

 

<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
@if (User.IsInRole("Admin"))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
}
@if (User.Identity.IsAuthenticated)
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Authentication" asp-action="Logout">Logout</a>
</li>
}
</ul>

 

如果我们以 Jane 身份登录,我们将无法看到“隐私”链接:

隐藏链接隐私 - 身份服务器4 授权

即使我们看不到“隐私”链接,我们仍然可以通过输入有效的 URI 地址来访问“隐私”页面:

通过 URI 访问的隐私

因此,我们要做的是使用用户的角色来保护我们的隐私端点:

 

 

[Authorize(Roles = "Admin")]
public async Task<IActionResult> Privacy()

 

现在,如果我们注销,以Jane的身份再次登录,并尝试使用URI地址访问隐私页面,我们将无法做到这一点:

 

身份服务器4 授权访问被拒绝

应用程序将我们重定向到 /帐户/AccessDenied 页面,但我们得到 404,因为我们没有该页面。

所以,让我们创建它。

我们要做的第一件事是在帐户控制器中添加一个新操作:

 

public IActionResult AccessDenied()
{
return View();
}

并且,让我们为此操作创建一个视图:

 

@{
ViewData["Title"] = "AccessDenied";
}
 
<h1>AccessDenied</h1>
 
<h3>You are not authorized to view this page.</h3>
 
<p>
You can always <a asp-controller="Account" asp-action="Logout">log in as someone else</a>.
</p>

完成这些更改后,我们可以注销并以 Jane 身份登录。一旦我们导航到/主页/隐私URI,我们将被重定向到访问拒绝页面:

访问被拒绝页面 - 身份服务器4 授权

因此,这按照我们的预期工作。

 

我们还要提到一件事。如果在具有不同名称的控制器中创建此操作,则必须在 Client 应用程序的方法中添加其他映射:AddCookie

 

.AddCookie("Cookies", (opt) =>
{
opt.AccessDeniedPath = "/ControllerName/AccessDenied";
})

使用此配置,我们将为“访问拒绝”操作添加不同的地址。

结论

让我们总结一下。

我们了解到:

  • 如何修改声明并添加其他声明
  • 从终结点手动获取声明的方法/userinfo
  • 如何设置授权
  • 以及如何将角色用于授权目的

身份服务器4 授权和使用声明 - 代码迷宫 (code-maze.com)

posted @ 2022-10-22 08:37  岭南春  阅读(188)  评论(0)    收藏  举报