我已阅读文档并按照示例操作,但我无法将用户声明置于访问令牌中。我的客户端不是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应用程序和身份服务器(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
答案 0 :(得分:0)
您需要修改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构建的。)
答案 1 :(得分: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
时,您是否在回复中看到了声明?如果不是 - 那么问题是他们没有被发出。
检查这些,我们可以挖掘更多。
祝你好运修改强>
我有一个你可能或可能不喜欢的解决方案,但我会建议。
在IdentityServer项目中,AccountController.cs
中有一个方法public async Task<IActionResult> Login(LoginInputModel model, string button)
。
这是用户单击登录页面上的登录按钮(或您在那里的任何自定义页面)后的方法。
在此方法中,有一个电话await HttpContext.SignInAsync
。此调用接受用户主题,用户名,身份验证属性和声明列表的参数。您可以在此处添加自定义声明,然后在AuthorizationCodeReceived
中调用userinfo端点时显示。我只是测试了它,它的工作原理。
实际上我发现这是添加自定义声明的方法。否则 - IdentityServer不了解您的自定义声明,也无法使用值填充它们。试一试,看看它是否适合你。
答案 2 :(得分:0)
您可以尝试实现自己的IProfileService并按照以下方式覆盖它:
services.AddIdentityServer()
.//add clients, scopes,resources here
.AddProfileService<YourOwnProfileProvider>();
有关详细信息,请点击此处:
答案 3 :(得分:0)
为什么你有&#34; portal&#34;列为身份资源和Api资源?这可能会造成一些混乱。
此外,在我切换到IdentityServer4和asp.net核心之前,我的IdentityServer3启动代码与您使用MVC的代码非常相似。您可能需要查看IdentityServer3的示例。
我可能会在您的&#34; ResponseType&#34;中给出一些建议。对于MVC,您可以尝试使用#id;代码id_token令牌&#34;
此外,您要在AuthorizationCodeReceived上设置声明,而是使用SecurityTokenValidated。
但你不应该像人们提到的那样做任何习惯。 IdentityServer4处理您尝试执行的自定义ApiResource。
答案 4 :(得分:-2)
新的IdentityResource(&#34; portal&#34;,new List {&#34; tenantId&#34;, &#34; userId&#34;,&#34; user&#34;,&#34; role&#34;,&#34; name&#34;})
api资源的名称应该是一致的:
public static IEnumerable GetApiResources()
{
return new List
{
new ApiResource
{
Name= "portal",
UserClaims = { "tenantId","userId","user" },
Scopes =
{
new Scope("portal","portal")
}
},
};
}