Identity Server:在 MVC 客户端的混合流中添加访问令牌的声明

2024-04-27

我已阅读文档并遵循示例,但无法将用户声明放入访问令牌中。我的客户端不是 ASP.NET core,因此 MVC 客户端的配置与 v4 示例不同。

除非我误解了文档,否则在创建访问令牌时,ApiResources 用于填充配置文件服务中的 RequestedClaimTypes。客户端应将 api 资源添加到其范围列表中,以包含关联的用户声明。就我而言,它们没有连接。

当使用“ClaimsProviderAccessToken”调用者调用 ProfileService.GetProfileDataAsync 时,请求的声明类型为空。即使我在此处设置了 context.IssuedClaims,当再次调用“AccessTokenValidation”时,也不会设置上下文上的声明。

在 MVC 应用程序中:

    app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                UseTokenLifetime = false, 
                ClientId = "portal",
                ClientSecret = "secret",
                Authority = authority,
                RequireHttpsMetadata = false,
                RedirectUri = redirectUri,
                PostLogoutRedirectUri = postLogoutRedirectUri,
                ResponseType = "code id_token",
                Scope = "openid offline_access portal",
                SignInAsAuthenticationType = "Cookies",
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthorizationCodeReceived = async n =>
                    {
                        await AssembleUserClaims(n);
                    },
                    RedirectToIdentityProvider = n =>
                    {
                        // if signing out, add the id_token_hint
                        if (n.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectRequestType.Logout)
                        {
                            var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");

                            if (idTokenHint != null)
                            {
                                n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                            }

                        }

                        return Task.FromResult(0);
                    }
                }
            });

    private static async Task AssembleUserClaims(AuthorizationCodeReceivedNotification notification)
    {

        string authCode = notification.ProtocolMessage.Code;

        string redirectUri = "https://myuri.com";

        var tokenClient = new TokenClient(tokenendpoint, "portal", "secret");

        var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(authCode, redirectUri);

        if (tokenResponse.IsError)
        {
            throw new Exception(tokenResponse.Error);
        }

        // use the access token to retrieve claims from userinfo
        var userInfoClient = new UserInfoClient(new Uri(userinfoendpoint), tokenResponse.AccessToken);

        var userInfoResponse = await userInfoClient.GetAsync();

        // create new identity
        var id = new ClaimsIdentity(notification.AuthenticationTicket.Identity.AuthenticationType);
        id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
        id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
        id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
        id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
        id.AddClaim(new Claim("id_token", notification.ProtocolMessage.IdToken));
        id.AddClaim(new Claim("sid", notification.AuthenticationTicket.Identity.FindFirst("sid").Value));
        notification.AuthenticationTicket = new AuthenticationTicket(id, notification.AuthenticationTicket.Properties);
    }

身份服务器客户端:

    private Client CreatePortalClient(Guid tenantId)
    {
        Client portal = new Client();
        portal.ClientName = "Portal MVC";
        portal.ClientId = "portal";
        portal.ClientSecrets = new List<Secret> { new Secret("secret".Sha256()) };
        portal.AllowedGrantTypes = GrantTypes.HybridAndClientCredentials;
        portal.RequireConsent = false; 
        portal.RedirectUris = new List<string> {
            "https://myuri.com",
        };
        portal.AllowedScopes = new List<string>
        {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                "portal"
        };
        portal.Enabled = true;
        portal.AllowOfflineAccess = true;
        portal.AlwaysSendClientClaims = true;
        portal.AllowAccessTokensViaBrowser = true;

        return portal;
    }

API资源:

public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource
            {
                Name= "portalresource",
                UserClaims = { "tenantId","userId","user" }, 
                Scopes =
                {
                    new Scope()
                    {
                        Name = "portalscope",
                        UserClaims = { "tenantId","userId","user",ClaimTypes.Role, ClaimTypes.Name),

                    },

                }
            },

        };
    }

身份资源:

    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new IdentityResource[]
        {
            // some standard scopes from the OIDC spec
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Email(),
            new IdentityResource("portal", new List<string>{ "tenantId", "userId", "user", "role", "name"})
        };
    }

UPDATE:

以下是 MVC 应用程序和 Identity Server (IS) 之间的交互:

MVC: 
    Owin Authentication Challenge
IS:
    AccountController.LoginAsync - assemble user claims and call HttpContext.SignInAsync with username and claims)
    ProfileService.IsActiveAsync - Context = "AuthorizeEndpoint", context.Subject.Claims = all userclaims
    ClaimsService.GetIdentityTokenClaimsAsync - Subject.Claims (all userclaims), resources = 1 IdentityResource (OpenId), GrantType = Hybrid
MVC:
    SecurityTokenValidated (Notification Callback)
    AuthorizationCodeReceived - Protocol.Message has Code and IdToken call to TokenClient.RequestAuthorizationCodeAsync()
IS: 
    ProfileService.IsActiveAsync - Context = "AuthorizationCodeValidation", context.Subject.Claims = all userclaims
    ClaimsService.GetAccessTokenClaimsAsync - Subject.Claims (all userclaims), resources = 2 IdentityResource (openId,profile), GrantType = Hybrid
    ProfileService.GetProfileDataAsync - Context = "ClaimsProviderAccessToken", context.Subject.Claims = all userclaims, context.RequestedClaimTypes = empty, context.IssuedClaims = name,role,user,userid,tenantid
    ClaimsService.GetIdentityTokenClaimsAsync - Subject.Claims (all userclaims), resources = 2 IdentityResource (openId,profile), GrantType = authorization_code

MVC:
    call to UserInfoClient with tokenResponse.AccessToken
IS:
    ProfileService.IsActiveAsync - Context = "AccessTokenValidation", context.Subject.Claims = sub,client_id,aud,scope etc (expecting user and tenantId here)
    ProfileService.IsActiveAsync - Context = "UserInfoRequestValidation", context.Subject.Claims = sub,auth_time,idp, amr
    ProfileService.GetProfileDataAsync - Context = "UserInfoEndpoint", context.Subject.Claims = sub,auth_time,idp,amp, context.RequestedClaimTypes = sub

因为我没有看到你发生了什么await AssembleUserClaims(context);我建议检查它是否正在执行以下操作:

基于您从以下任一方获得的访问令牌context.ProtoclMessage.AccessToken或从呼叫TokenEndpoint你应该创建一个新的ClaimsIdentity。你这样做是因为你没有提到它吗?

像这样的事情:

var tokenClient = new TokenClient(
                      IdentityServerTokenEndpoint,
                      "clientId",
                      "clientSecret");


var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
                        n.Code, n.RedirectUri);

if (tokenResponse.IsError)
{
    throw new Exception(tokenResponse.Error);
}

// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);

id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaims(n.AuthenticationTicket.Identity.Claims);

// get user info claims and add them to the identity
var userInfoClient = new UserInfoClient(IdentityServerUserInfoEndpoint);
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
var userInfoEndpointClaims = userInfoResponse.Claims;

// this line prevents claims duplication and also depends on the IdentityModel library version. It is a bit different for >v2.0
id.AddClaims(userInfoEndpointClaims.Where(c => id.Claims.Any(idc => idc.Type == c.Type && idc.Value == c.Value) == false));

// create the authentication ticket
n.AuthenticationTicket = new AuthenticationTicket(
                        new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
                        n.AuthenticationTicket.Properties);

还有一件事——阅读this https://leastprivilege.com/2016/12/01/new-in-identityserver4-resource-based-configuration/关于资源。在您的特定情况下,您关心 IdentityResources (但我发现您也有它)。

所以 - 当打电话时UserInfoEndpoint您看到回复中的主张了吗?如果没有 - 那么问题是它们没有被发行。

检查这些,我们可以挖掘更多。

祝你好运

EDIT

我有一个您可能喜欢也可能不喜欢的解决方案,但我会建议它。

在 IdentityServer 项目中,AccountController.cs有一个方法public async Task<IActionResult> Login(LoginInputModel model, string button).

这是用户单击登录页面(或您在那里的任何自定义页面)上的登录按钮后的方法。

在这个方法中有一个调用await HttpContext.SignInAsync。此调用接受参数用户主题、用户名、身份验证属性和索赔清单。您可以在此处添加自定义声明,然后当您调用中的 userinfo 端点时,它将出现AuthorizationCodeReceived。我刚刚测试了这个并且它有效。

实际上我发现这是添加自定义声明的方法。否则 - IdentityServer 不知道您的自定义声明,并且无法用值填充它们。尝试一下,看看它是否适合您。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Identity Server:在 MVC 客户端的混合流中添加访问令牌的声明 的相关文章

随机推荐

  • 单元测试“混合”WPF/Silverlight 控件

    我正在启动一个新的 WPF Silverlight 自定义控件项目 并希望对此进行单元测试 但是我对如何解决这个问题有点困惑 此控件将基于 WPF 和 Silverlight 的相同代码库 并使用 ifs 和部分类进行少量分叉以消除差异 我
  • Android 13 中文件未删除?

    String path storage emulated 0 Recordings Call Two mp3 File file new File path if file exists File file2 new File file g
  • 如何将输入 type="date" 转换为时间戳?

    我需要转换一个
  • 可以用结构化数据来审查一个人吗?

    我的审查数据围绕people https schema org Person 我想用结构化数据来表示这些评论 像这样的东西 从这里 http moz com ugc getting the most out of schemaorg mic
  • 在 SQL 中合并具有重叠日期范围的记录

    编辑 我们当前的服务器是 SQL 2008 R2 因此 LAG LEAD 功能将不起作用 我正在尝试获取表中的多个数据流并将它们组合成 1 个数据流 鉴于下面的 3 个数据流 我希望最终结果是 1 个优先考虑状态 on 的流 递归似乎是最好
  • 如何使用 YouTube javascript API 获取特定“秒”的 YouTube 视频的缩略图或快照?

    我想拍摄 YouTube 视频的缩略图http www youtube com watch v ffaTjpA1scI http www youtube com watch v ffaTjpA1scI30秒或120秒 我如何使用 youtu
  • SHA 足以检查文件重复吗? (PHP 中的 sha1_file)

    假设您想创建一个文件托管站点 供人们上传文件并向他们的朋友发送链接以供稍后检索 并且您想确保文件在我们存储文件的位置重复 那么 PHP 的 sha1 file 是否足以完成该任务 有什么理由不使用 md5 file 来代替吗 对于前端 它将
  • 即席查询/更新是否开始影响您使用 MongoDB 的工作效率?

    我开发一个 ASP MVC 网站已经快一年了 现在完全在 mongodb 上 我大部分时间都喜欢它 使用 C mongodb 驱动程序和 mongovue 等工具 开发效率非常高 然而 我已经开始达到这样一个地步 有些事情我真的希望我有一个
  • 在 SecureField 的 SwiftUI 中切换 isSecureTextEntry

    我想实现在 SecureField 中显示和隐藏密码的功能 以下是 SecureField 的代码 我添加了按钮 该按钮将被检测到显示和隐藏 SecureField 中的文本 但 swiftUI 没有类似的功能是安全文本条目除了切换之外还有
  • 无法找到模块提供者

    我在使用 PowerShell 时遇到问题 几乎就像没有完全安装一样 这很奇怪 因为它是 Windows 10 并且是附带的 在没有锁定的情况下 我尝试使用另一台正在运行的 Windows 10 计算机上的全新副本替换以下目录 C User
  • 如何使用调查包的 svyby 函数对多个列进行循环?

    我已经尝试了很多方法 但我无法解决问题 我发现here https stackoverflow com questions 13402829 r looping through in survey package here https st
  • 每个命令重置外部差异工具

    我最近安装了一个新的差异工具 差异性的 https github com Wilfred difftastic 这扰乱了我将差异复制到剪贴板 或文件 以发送这些内容的习惯 diff external difft As per https d
  • 无法使用 git 克隆任何存储库

    我尝试克隆一些存储库 但总是遇到相同的错误 我在哪里可以找到有关此错误的更多信息 错误日志文件或类似的文件 或者也许有人知道可能出了什么问题 git clone http github com creationix nvm git nvm
  • Visual Studio 2012 生成后事件 - 错误代码 255

    这是我尝试将应用程序可执行文件复制到另一个文件夹并更改其名称 IF ConfigurationName Release SET DESTINATION ProjectDir Output Distribution IF NOT EXIST
  • 多重绑定多重选择ListView

    好吧 我这里有一个奇怪的 我想要弄清楚的是如何拥有一个由 ObservableCollection 填充的列表视图 根据第一个列表视图的选择更新另一个由另一个 ObservableCollection 填充的 ListView 然后使用 v
  • URLConnection 和 setUseCaches(true)

    我正在使用当前代码在线程中下载图像 Log d TAG Fetching image BASE URL mURL URLConnection connection InputStream input connection new URL B
  • Hstore 和 Rails

    我正在尝试使用最新版本的 activerecord postgres hstore gem 在 Rails 3 2 9 项目中使用 Hstore 但在使用 ActiveRecord 提供的 store accessor 来获取 Hstore
  • 获取浏览器中正在查看的当前页面(可能吗?)

    我想知道 作为一名 Android 开发人员 我是否可以知道浏览器应用程序当前是否打开 如果是 正在查看哪个页面 网址 如果是这样 我将如何去做这件事 如果没有 我也很想知道 所以我不会再搜索了 谢谢 奥姆里 我不知道是否有可能知道浏览器应
  • solr索引嵌套文档

    solr支持嵌套文档吗 有没有更好的方法来实现这种文档
  • Identity Server:在 MVC 客户端的混合流中添加访问令牌的声明

    我已阅读文档并遵循示例 但无法将用户声明放入访问令牌中 我的客户端不是 ASP NET core 因此 MVC 客户端的配置与 v4 示例不同 除非我误解了文档 否则在创建访问令牌时 ApiResources 用于填充配置文件服务中的 Re