在另一个域中使用 IdentityServer 进行身份验证 IdentityManager
Authentication IdentityManager with IdentityServer in another domain
IdentityManager经过IdentityServer认证后,发出一个内部token是这样的。 (请运行the example检查请求/api或api/Users后发送的不记名令牌)
authorization:Bearer UQgpIqqyn_lgUukES3PqHFEuf0_2sz26Jsh848K_4DYdiYeQLkSazg43MT2BdWSC-EY--iUYAPKk4rD9-8sq0_nbf2Z7XDzPlcDL0LdAP8oNyKUDCOLeap9zCEaB4ve1VE1Q_e5JGYsx_jTvs-yYlUI5fMn-6OBxunlNcTwPq-xv6hOXZhh-PUGIE9Ndhkptd0zt5r1A3UAvvTk72yI6yD40yRnl1KhNEQw33UNVMIeV4vWqwiXHtyoxi87e3r4_x3IyzZeEqxtwPIPH1l6o1s7HfZozspaTbaq9gPLvuaXa0dQjf5lA2CIGs5z8Fa3W
其实我需要IdentityManager在登录过程中保存我自己的IdentityServer发出的JWT并使用那个token来调用api而不是使用上面那种token.为什么?因为我想从 IdentityManager 本身调用外部 API,它需要我自己的 IdentityServer 服务器发出的令牌。
使用 HostSecurityConfiguration 或 LocalSecurityConfiguration 对我不起作用,因为它们在内部使用 OAuthAuthorizationServerProvider 并且该提供者发出一个令牌(上面那个),它对于最终 IdentityManager 内部将要调用的 API 无效.保存的令牌 必须 由我自己的 IdentityServer 颁发,因为外部 API 需要它的令牌。
我尝试使用 ExternalBearerTokenConfiguration 但没有成功。每次我尝试用这个 class 做一些事情时,我都会被重定向到
https://localhost:44337/idm/connect/authorize?state=8030030589322682&nonce=7778993666881238&client_id=idmgr&response_type=id_token%20token
并且这个 url 显然不存在,因为权限以 https://localhost:44337/ids 开头并且 ExternalBearerTokenConfiguration 假设我的提供者在同一域下。
这是 ExternalBearerTokenConfiguration 的配置
idm.UseIdentityManager(new IdentityManagerOptions
{
Factory = factory,
SecurityConfiguration = new ExternalBearerTokenConfiguration()
{
RequireSsl = false,
SigningCert = Cert.Load(),
Issuer = "https://localhost:44337/ids",
Scope = "idmgr",
Audience = $"https://localhost:44337/ids/resources",
BearerAuthenticationType = "Cookies"
}
});
从另一个方向来看,我发现在 IdentityManager.Assets.EmbeddedHtmlResult 上修改方法 GetResponseMessage() 我可以转到我的 IdentityServer 并请求身份验证,这非常好。如您所见,我可以获得我的 id_token 和访问令牌,其中包含所有内容。这种方法的好处是内部保存的令牌是我从我的 IdentityServer 获得的令牌。
{
"client_id": "idmgr_client",
"scope": [
"openid",
"idmgr",
"WebUserAccountsApi"
],
"sub": "951a965f-1f84-4360-90e4-3f6deac7b9bc",
"amr": [
"password"
],
"auth_time": 1505323819,
"idp": "idsrv",
"name": "Admin",
"role": "IdentityManagerAdministrator",
"iss": "https://localhost:44336/ids",
"aud": "https://localhost:44336/ids/resources",
"exp": 1505327419,
"nbf": 1505323819
}
所以现在我几乎已经得到了我需要的一切,当 IdentityServer 将我发送回我的 /idm 端点(这是 IdentityManager 的端点)时,UseIdentityServerBearerTokenAuthentication 验证了我的令牌,所以我得到了授权,我确信这是因为我可以在下面的代码中看到这一行中的所有内容 => context.Authentication.User.Identity.IsAuthenticated。问题是 UseIdentityManager 没有接受我的授权,即使 UseIdentityServerBearerTokenAuthentication 已经做了。
当我删除 IdentityManager 项目中的安全性并在 api/Users 中放置一个断点时,我可以看到 Principal 有一些值,但它们都是空的。声明为空,身份本身有一个对象但未经过身份验证。可能我在这段代码中遗漏了一些东西,它是我的 UseIdentityServerBearerTokenAuthentication 身份验证和 UseIdentityManager 之间的粘合剂。
app.Map("/idm", idm =>
{
var factory = new IdentityManagerServiceFactory();
var rand = new System.Random();
var users = Users.Get(rand.Next(5000, 20000));
var roles = Roles.Get(rand.Next(15));
factory.Register(new Registration<ICollection<InMemoryUser>>(users));
factory.Register(new Registration<ICollection<InMemoryRole>>(roles));
factory.IdentityManagerService = new Registration<IIdentityManagerService, InMemoryIdentityManagerService>();
idm.Use(async (context, next) =>
{
await next.Invoke();
});
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
idm.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = Constants.authority,
RequiredScopes = new[] { "idmgr" }
});
idm.Use(async (context, next) =>
{
if (context.Authentication.User != null &&
context.Authentication.User.Identity != null &&
context.Authentication.User.Identity.IsAuthenticated)
{
/*var xxx = "";
}
await next.Invoke();
});
idm.UseIdentityManager(new IdentityManagerOptions
{
Factory = factory
});
idm.Use(async (context, next) =>
{
await next.Invoke();
});
});
如果你知道我遗漏了什么,请发表评论。欢迎所有的想法。
如果您知道如何使用您自己的 IdentityServer 在 IdentityManager 中进行身份验证,请告诉我该怎么做。
提前致谢。
丹尼尔
IdentityManager 存储库 an example 使用 IdentityServer3 作为 IdentityManager 的 Idp。
也可以找到一些相关的讨论in this thread...
编辑:
我没有研究过 IdentityManager 在内部使用您描述的令牌做什么。但是,对于您的外部 API 调用,您是否也可以请求访问令牌(而不是仅 id_token),然后保存该访问令牌并使用它来调用您的外部 API?这将是 Identity Server 颁发的令牌,默认情况下它将是 JWT。
以下是示例代码的更改方式;请查看标记为“--EDIT-- ...”的两条注释下的代码:“--EDIT-- ...”
本质上,我只是请求和访问令牌,然后保存它....
app.UseOpenIdConnectAuthentication(new Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
Authority = "https://localhost:44337/ids",
ClientId = "idmgr_client",
RedirectUri = "https://localhost:44337",
// ---EDIT--- request id_token AND access_token
ResponseType = "id_token token",
UseTokenLifetime = false,
Scope = "openid idmgr",
SignInAsAuthenticationType = "Cookies",
Notifications = new Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
// --EDIT-- save access_token
n.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
return Task.FromResult(0);
},
RedirectToIdentityProvider = async n =>
{
if (n.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnectRequestType.LogoutRequest)
{
var result = await n.OwinContext.Authentication.AuthenticateAsync("Cookies");
if (result != null)
{
var id_token = result.Identity.Claims.GetValue("id_token");
if (id_token != null)
{
n.ProtocolMessage.IdTokenHint = id_token;
n.ProtocolMessage.PostLogoutRedirectUri = "https://localhost:44337/idm";
}
}
}
}
}
});
我找到了解决方案。
我创建了一个名为 OAuthSettings 的 class 和另一个名为 EmptySecurityConfiguration 的。我是这样使用它们的:
idm.UseIdentityManager(new IdentityManagerOptions
{
Factory = factory,
SecurityConfiguration = new EmptySecurityConfiguration
{
OAuthSettings = new OAuthSettings()
{
authorization_endpoint = authority + "/connect/authorize",
client_id = "idmgr_client",
authority = authority,
response_type = "id_token token",
redirect_uri = idmUrl + "/#/callback/",
//scope = "openid",
scope = "openid idmgr MyApi",
//response_mode = ""
acr_values = "tenant:anything",
load_user_profile = true
}
}
});
我必须修改 SecurityConfiguration class 才能添加我的 OAuthSettings 属性。
然后我在 EmbeddedHtmlResult 中像这样使用它
OAuthSettings OAuthSettings = null;
if (this.securityConfiguration.OAuthSettings == null)
{
OAuthSettings = new OAuthSettings
{
authorization_endpoint = this.authorization_endpoint,
client_id = Constants.IdMgrClientId
};
}
else
{
OAuthSettings = this.securityConfiguration.OAuthSettings;
}
var html = AssetManager.LoadResourceString(this.file,
new {
pathBase = this.path,
model = Newtonsoft.Json.JsonConvert.SerializeObject(new
{
PathBase = this.path,
ShowLoginButton = this.securityConfiguration.ShowLoginButton,
oauthSettings = OAuthSettings
})
});
之后,您必须 运行 代码,仅此而已。您已获得 IdentityServer 颁发的令牌并保存它以便在 javascript 位中使用。
现在 Bearer Token 看起来像这样:
authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJjbGllbnRfaWQiOiJpZG1ncl9jbGllbnQiLCJzY29wZSI6WyJvcGVuaWQiLCJpZG1nciIsIk15QXBpIl0sInN1YiI6Ijk1MWE5NjVmLTFmODQtNDM2MC05MGU0LTNmNmRlYWM3YjliYyIsImFtciI6WyJwYXNzd29yZCJdLCJhdXRoX3RpbWUiOjE1MDU1NzYzNTAsImlkcCI6Imlkc3J2IiwibmFtZSI6IkFkbWluIiwicm9sZSI6IklkZW50aXR5TWFuYWdlckFkbWluaXN0cmF0b3IiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDMzOC9pZHMiLCJhdWQiOiJodHRwczovL2xvY2FsaG9zdDo0NDMzOC9pZHMvcmVzb3VyY2VzIiwiZXhwIjoxNTA1NTgwMzU4LCJuYmYiOjE1MDU1NzY3NTh9.iVsEuswGDdMGo-x-NdPxMEln6or9e7p8G-8iSK746_Wapcwi_-N7EcY3G8GKj0YvExO4i605kfNjsTDAd14zQvT6UyU8_gcGO84DhQRM_MWpirfhlPWu6flXT4dRzYberjgHhDEOzROsrHofVAAZD_51BEE1FgAQrqCCWar2POSi9AsLFJ_AxFRnMlbZbZy8adJiMGOUFhtBXzhJVYzuolAMJ08NBTzmaK5vLsEn9Ok-09ZGX3MOpq2aBfES1hRJKEP-LDhMNo4dQn0mQ9Y-gGvkpXMmZQ6tC8yUs2PokJ5eGsFqevK6zpvJDiKPPjoN01QJtEqZ2UU_oGzMEKwyUA
我使用 code
创建了一个 github 存储库
IdentityManager经过IdentityServer认证后,发出一个内部token是这样的。 (请运行the example检查请求/api或api/Users后发送的不记名令牌)
authorization:Bearer UQgpIqqyn_lgUukES3PqHFEuf0_2sz26Jsh848K_4DYdiYeQLkSazg43MT2BdWSC-EY--iUYAPKk4rD9-8sq0_nbf2Z7XDzPlcDL0LdAP8oNyKUDCOLeap9zCEaB4ve1VE1Q_e5JGYsx_jTvs-yYlUI5fMn-6OBxunlNcTwPq-xv6hOXZhh-PUGIE9Ndhkptd0zt5r1A3UAvvTk72yI6yD40yRnl1KhNEQw33UNVMIeV4vWqwiXHtyoxi87e3r4_x3IyzZeEqxtwPIPH1l6o1s7HfZozspaTbaq9gPLvuaXa0dQjf5lA2CIGs5z8Fa3W
其实我需要IdentityManager在登录过程中保存我自己的IdentityServer发出的JWT并使用那个token来调用api而不是使用上面那种token.为什么?因为我想从 IdentityManager 本身调用外部 API,它需要我自己的 IdentityServer 服务器发出的令牌。
使用 HostSecurityConfiguration 或 LocalSecurityConfiguration 对我不起作用,因为它们在内部使用 OAuthAuthorizationServerProvider 并且该提供者发出一个令牌(上面那个),它对于最终 IdentityManager 内部将要调用的 API 无效.保存的令牌 必须 由我自己的 IdentityServer 颁发,因为外部 API 需要它的令牌。
我尝试使用 ExternalBearerTokenConfiguration 但没有成功。每次我尝试用这个 class 做一些事情时,我都会被重定向到 https://localhost:44337/idm/connect/authorize?state=8030030589322682&nonce=7778993666881238&client_id=idmgr&response_type=id_token%20token 并且这个 url 显然不存在,因为权限以 https://localhost:44337/ids 开头并且 ExternalBearerTokenConfiguration 假设我的提供者在同一域下。
这是 ExternalBearerTokenConfiguration 的配置
idm.UseIdentityManager(new IdentityManagerOptions
{
Factory = factory,
SecurityConfiguration = new ExternalBearerTokenConfiguration()
{
RequireSsl = false,
SigningCert = Cert.Load(),
Issuer = "https://localhost:44337/ids",
Scope = "idmgr",
Audience = $"https://localhost:44337/ids/resources",
BearerAuthenticationType = "Cookies"
}
});
从另一个方向来看,我发现在 IdentityManager.Assets.EmbeddedHtmlResult 上修改方法 GetResponseMessage() 我可以转到我的 IdentityServer 并请求身份验证,这非常好。如您所见,我可以获得我的 id_token 和访问令牌,其中包含所有内容。这种方法的好处是内部保存的令牌是我从我的 IdentityServer 获得的令牌。
{
"client_id": "idmgr_client",
"scope": [
"openid",
"idmgr",
"WebUserAccountsApi"
],
"sub": "951a965f-1f84-4360-90e4-3f6deac7b9bc",
"amr": [
"password"
],
"auth_time": 1505323819,
"idp": "idsrv",
"name": "Admin",
"role": "IdentityManagerAdministrator",
"iss": "https://localhost:44336/ids",
"aud": "https://localhost:44336/ids/resources",
"exp": 1505327419,
"nbf": 1505323819
}
所以现在我几乎已经得到了我需要的一切,当 IdentityServer 将我发送回我的 /idm 端点(这是 IdentityManager 的端点)时,UseIdentityServerBearerTokenAuthentication 验证了我的令牌,所以我得到了授权,我确信这是因为我可以在下面的代码中看到这一行中的所有内容 => context.Authentication.User.Identity.IsAuthenticated。问题是 UseIdentityManager 没有接受我的授权,即使 UseIdentityServerBearerTokenAuthentication 已经做了。
当我删除 IdentityManager 项目中的安全性并在 api/Users 中放置一个断点时,我可以看到 Principal 有一些值,但它们都是空的。声明为空,身份本身有一个对象但未经过身份验证。可能我在这段代码中遗漏了一些东西,它是我的 UseIdentityServerBearerTokenAuthentication 身份验证和 UseIdentityManager 之间的粘合剂。
app.Map("/idm", idm =>
{
var factory = new IdentityManagerServiceFactory();
var rand = new System.Random();
var users = Users.Get(rand.Next(5000, 20000));
var roles = Roles.Get(rand.Next(15));
factory.Register(new Registration<ICollection<InMemoryUser>>(users));
factory.Register(new Registration<ICollection<InMemoryRole>>(roles));
factory.IdentityManagerService = new Registration<IIdentityManagerService, InMemoryIdentityManagerService>();
idm.Use(async (context, next) =>
{
await next.Invoke();
});
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
idm.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = Constants.authority,
RequiredScopes = new[] { "idmgr" }
});
idm.Use(async (context, next) =>
{
if (context.Authentication.User != null &&
context.Authentication.User.Identity != null &&
context.Authentication.User.Identity.IsAuthenticated)
{
/*var xxx = "";
}
await next.Invoke();
});
idm.UseIdentityManager(new IdentityManagerOptions
{
Factory = factory
});
idm.Use(async (context, next) =>
{
await next.Invoke();
});
});
如果你知道我遗漏了什么,请发表评论。欢迎所有的想法。 如果您知道如何使用您自己的 IdentityServer 在 IdentityManager 中进行身份验证,请告诉我该怎么做。
提前致谢。 丹尼尔
IdentityManager 存储库 an example 使用 IdentityServer3 作为 IdentityManager 的 Idp。
也可以找到一些相关的讨论in this thread...
编辑:
我没有研究过 IdentityManager 在内部使用您描述的令牌做什么。但是,对于您的外部 API 调用,您是否也可以请求访问令牌(而不是仅 id_token),然后保存该访问令牌并使用它来调用您的外部 API?这将是 Identity Server 颁发的令牌,默认情况下它将是 JWT。
以下是示例代码的更改方式;请查看标记为“--EDIT-- ...”的两条注释下的代码:“--EDIT-- ...”
本质上,我只是请求和访问令牌,然后保存它....
app.UseOpenIdConnectAuthentication(new Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
Authority = "https://localhost:44337/ids",
ClientId = "idmgr_client",
RedirectUri = "https://localhost:44337",
// ---EDIT--- request id_token AND access_token
ResponseType = "id_token token",
UseTokenLifetime = false,
Scope = "openid idmgr",
SignInAsAuthenticationType = "Cookies",
Notifications = new Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
// --EDIT-- save access_token
n.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
return Task.FromResult(0);
},
RedirectToIdentityProvider = async n =>
{
if (n.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnectRequestType.LogoutRequest)
{
var result = await n.OwinContext.Authentication.AuthenticateAsync("Cookies");
if (result != null)
{
var id_token = result.Identity.Claims.GetValue("id_token");
if (id_token != null)
{
n.ProtocolMessage.IdTokenHint = id_token;
n.ProtocolMessage.PostLogoutRedirectUri = "https://localhost:44337/idm";
}
}
}
}
}
});
我找到了解决方案。
我创建了一个名为 OAuthSettings 的 class 和另一个名为 EmptySecurityConfiguration 的。我是这样使用它们的:
idm.UseIdentityManager(new IdentityManagerOptions
{
Factory = factory,
SecurityConfiguration = new EmptySecurityConfiguration
{
OAuthSettings = new OAuthSettings()
{
authorization_endpoint = authority + "/connect/authorize",
client_id = "idmgr_client",
authority = authority,
response_type = "id_token token",
redirect_uri = idmUrl + "/#/callback/",
//scope = "openid",
scope = "openid idmgr MyApi",
//response_mode = ""
acr_values = "tenant:anything",
load_user_profile = true
}
}
});
我必须修改 SecurityConfiguration class 才能添加我的 OAuthSettings 属性。
然后我在 EmbeddedHtmlResult 中像这样使用它
OAuthSettings OAuthSettings = null;
if (this.securityConfiguration.OAuthSettings == null)
{
OAuthSettings = new OAuthSettings
{
authorization_endpoint = this.authorization_endpoint,
client_id = Constants.IdMgrClientId
};
}
else
{
OAuthSettings = this.securityConfiguration.OAuthSettings;
}
var html = AssetManager.LoadResourceString(this.file,
new {
pathBase = this.path,
model = Newtonsoft.Json.JsonConvert.SerializeObject(new
{
PathBase = this.path,
ShowLoginButton = this.securityConfiguration.ShowLoginButton,
oauthSettings = OAuthSettings
})
});
之后,您必须 运行 代码,仅此而已。您已获得 IdentityServer 颁发的令牌并保存它以便在 javascript 位中使用。
现在 Bearer Token 看起来像这样:
authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJjbGllbnRfaWQiOiJpZG1ncl9jbGllbnQiLCJzY29wZSI6WyJvcGVuaWQiLCJpZG1nciIsIk15QXBpIl0sInN1YiI6Ijk1MWE5NjVmLTFmODQtNDM2MC05MGU0LTNmNmRlYWM3YjliYyIsImFtciI6WyJwYXNzd29yZCJdLCJhdXRoX3RpbWUiOjE1MDU1NzYzNTAsImlkcCI6Imlkc3J2IiwibmFtZSI6IkFkbWluIiwicm9sZSI6IklkZW50aXR5TWFuYWdlckFkbWluaXN0cmF0b3IiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDMzOC9pZHMiLCJhdWQiOiJodHRwczovL2xvY2FsaG9zdDo0NDMzOC9pZHMvcmVzb3VyY2VzIiwiZXhwIjoxNTA1NTgwMzU4LCJuYmYiOjE1MDU1NzY3NTh9.iVsEuswGDdMGo-x-NdPxMEln6or9e7p8G-8iSK746_Wapcwi_-N7EcY3G8GKj0YvExO4i605kfNjsTDAd14zQvT6UyU8_gcGO84DhQRM_MWpirfhlPWu6flXT4dRzYberjgHhDEOzROsrHofVAAZD_51BEE1FgAQrqCCWar2POSi9AsLFJ_AxFRnMlbZbZy8adJiMGOUFhtBXzhJVYzuolAMJ08NBTzmaK5vLsEn9Ok-09ZGX3MOpq2aBfES1hRJKEP-LDhMNo4dQn0mQ9Y-gGvkpXMmZQ6tC8yUs2PokJ5eGsFqevK6zpvJDiKPPjoN01QJtEqZ2UU_oGzMEKwyUA
我使用 code
创建了一个 github 存储库