本文介绍了Identity Server:在MVC客户端中添加声明以访问混合流中的令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've read the docs and followed the examples but I am unable to get user claims into the access token. My client is not ASP.NET core, so the configuration of the MVC client is not the same as the v4 samples.

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

Unless I have misunderstood the docs, the ApiResources are used to populate the RequestedClaimTypes in the profile service when creating the access token. The client should add the api resource to it's list of scopes to include associated userclaims. In my case they are not being connected.

当使用"ClaimsProviderAccessToken"的调用方调用ProfileService.GetProfileDataAsync时,请求的声明类型为空.即使在这里设置了context.IssuedClaims,当再次为"AccessTokenValidation"调用它时,也不会设置对上下文的声明.

When ProfileService.GetProfileDataAsync is called with a caller of "ClaimsProviderAccessToken", the requested claim types are empty. Even if I set the context.IssuedClaims in here, when it is called again for "AccessTokenValidation" the claims on the context are not set.

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

更新:

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

Here is the interaction between the MVC app and the 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); 中会发生什么,我建议检查它是否正在做以下:

As I'm not seeing what happens in your await AssembleUserClaims(context); I would suggest to check if it is doing the following:

基于您从 context.ProtoclMessage.AccessToken 或从对 TokenEndpoint 的调用中获得的访问令牌,应创建一个新的 ClaimsIdentity.您这样做是因为您没有提及它?

Based on the the access token that you have from either the context.ProtoclMessage.AccessToken or from the call to the TokenEndpoint you should create a new ClaimsIdentity. Are you doing this, because you are not mentioning it?

类似这样的东西:

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

还有一件事-阅读关于资源.在您的特定情况下,您会关心IdentityResources(但我发现您也有该身份).

And one more thing - read this regarding the resources. In your particular case, you care about IdentityResources (but I see that you also have it there).

所以-调用 UserInfoEndpoint 时,您是否在响应中看到声明?如果否,那么问题在于它们没有发出.

So - when calling the UserInfoEndpoint do you see the claims in the response? If no - then the problem is that they are not issued.

检查这些,我们可以进一步进行挖掘.

Check these, and we can dig in more.

祝你好运

编辑

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

I have a solution that you may, or may not like, but I'll suggest it.

在IdentityServer项目中,在 AccountController.cs 中,有一个方法 public async Task< IActionResult>.登录(LoginInputModel模型,字符串按钮).

In the IdentityServer project, in the AccountController.cs there is a method public async Task<IActionResult> Login(LoginInputModel model, string button).

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

This is the method after the user has clicked the login button on the login page (or whatever custom page you have there).

在此方法中,有一个调用 await HttpContext.SignInAsync .该调用接受用户主题,用户名,身份验证属性和声明列表的参数.您可以在此处添加自定义声明,然后当您在 AuthorizationCodeReceived 中调用userinfo端点时,该声明就会出现.我刚刚测试了它,它起作用了.

In this method there is a call await HttpContext.SignInAsync. This call accept parameters the user subject, username, authentication properties and list of claims. Here you can add your custom claim, and then it will appear when you call the userinfo endpoint in the AuthorizationCodeReceived. I just tested this and it works.

实际上,我发现这是添加自定义声明的方式.否则-IdentityServer不了解您的自定义声明,因此无法使用值填充它们.试试看,看看它是否适合您.

Actually I figured out that this is the way to add custom claims. Otherwise - IdentityServer doesn't know about your custom claims, and is not able to populate them with values. Try it out and see if it works for you.

这篇关于Identity Server:在MVC客户端中添加声明以访问混合流中的令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-21 03:06