首页 文章

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

提问于
浏览
5

我已阅读文档并按照示例操作,但我无法将用户声明置于访问令牌中 . 我的客户端不是ASP.NET核心,因此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);
    }

Identity Server客户端:

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"})
        };
    }

更新:

这是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

5 回答

  • -2

    您需要修改MVC App中“Notifications”块的代码,如下所述:

    Notifications = new OpenIdConnectAuthenticationNotifications
                    {
                        AuthorizationCodeReceived = async n => {
                            var userInfoClient = new UserInfoClient(UserInfoEndpoint);
                            var userInfoResponse = await userInfoClient.GetAsync(n.ProtocolMessage.AccessToken);
    
                            var identity = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
                            identity.AddClaims(userInfoResponse.Claims);
    
                            var tokenClient = new TokenClient(TokenEndpoint, "portal", "secret");
                            var response = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
    
                            identity.AddClaim(new Claim("access_token", response.AccessToken));
                            identity.AddClaim(new Claim("expires_at", DateTime.UtcNow.AddSeconds(response.ExpiresIn).ToLocalTime().ToString(CultureInfo.InvariantCulture)));
                            identity.AddClaim(new Claim("refresh_token", response.RefreshToken));
                            identity.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
                            n.AuthenticationTicket = new AuthenticationTicket(identity, n.AuthenticationTicket.Properties);
    
                        },
                        RedirectToIdentityProvider = n =>
                        {
                            if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
                            {
                                var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token").Value;
                                n.ProtocolMessage.IdTokenHint = idTokenHint;
                            }
    
                            return Task.FromResult(0);
                        }
                    }
    

    (考虑是否有任何与身份服务器版本相关的更改,因为此代码是为身份服务器3构建的 . )

  • 0

    由于我没有看到 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 . 在你的特定情况下,你关心IdentityResources(但我看到你也有它) .

    那么 - 在调用 UserInfoEndpoint 时,您是否在响应中看到了声明?如果不是 - 那么问题是他们没有被发出 .

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

    祝好运

    EDIT

    我有一个你可能或可能不喜欢的解决方案,但我会建议 .

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

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

    在这种方法中有一个调用 await HttpContext.SignInAsync . 此调用接受参数用户主题,用户名,身份验证属性和 list of claims . 您可以在此处添加自定义声明,然后在 AuthorizationCodeReceived 中调用userinfo endpoints 时显示 . 我刚测试了这个并且它有效 .

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

  • 0

    您可以尝试实现自己的IProfileService并按以下方式覆盖它:

    services.AddIdentityServer()
        .//add clients, scopes,resources here
        .AddProfileService<YourOwnProfileProvider>();
    

    有关更多信息,请在此处查看:

    https://damienbod.com/2016/10/01/identityserver4-webapi-and-angular2-in-a-single-asp-net-core-project/

  • 0

    为什么将“门户”列为身份资源和Api资源?这可能会造成一些混乱 .

    此外,在我切换到IdentityServer4和asp.net核心之前,我的IdentityServer3启动代码看起来与您使用MVC非常相似 . 您可能需要查看IdentityServer3的示例 .

    我可以给出一些建议,在MVC的“ResponseType”字段中,你可以尝试“code id_token token”

    此外,您要在AuthorizationCodeReceived上设置声明,而是使用SecurityTokenValidated .

    但你不应该像人们提到的那样做任何习惯 . IdentityServer4处理您尝试执行的自定义ApiResources .

  • 0
    • portal不是标识资源:您应该删除

    新的IdentityResource(“门户”,新列表{“tenantId”,“userId”,“用户”,“角色”,“名称”})

    • api资源的名称应该是一致的:
    public static IEnumerable GetApiResources()
    {
        return new List
            {
                new ApiResource
                {
                    Name= "portal",
                    UserClaims = { "tenantId","userId","user" }, 
                    Scopes =
                    {
                        new Scope("portal","portal")
                    }
                }, 
    
        };
    }
    
    • 尝试在客户端中设置GrantTypes.Implicit .

相关问题