IdentityServer4环境部署失败分析贴(一)

Stella981
• 阅读 677

前言:

在部署Idv4站点和其客户端在外网时,发现了许多问题,折腾了许久,翻看了许多代码,写个MD记录一下。

1.受保护站点提示错误: Unable to obtain configuration from: '[PII is hidden]'.

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HLL3MB34N0G5", Request id "0HLL3MB34N0G5:00000009": An unhandled exception was thrown by the application.
System.InvalidOperationException: IDX20803: Unable to obtain configuration from: '[PII is hidden]'.
   在 Microsoft.IdentityModel.Protocols.ConfigurationManager`1.<GetConfigurationAsync>d__24.MoveNext()
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
   在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.<HandleChallengeAsync>d__18.MoveNext()
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
   在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.<ChallengeAsync>d__54.MoveNext()
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
1.1 受保护站点的代码执行流程
graph TB
User--1.进入-->A(Home/Index)
A--2.Authorize-->AA{是否授权}
AA--YES-->完事
AA--NO-->B(返回ChallengeResult)
B--3.ExecuteResult-->C(进入AuthenticationMiddleware中间件)
C--4.根据authenticationScheme找到相应Handler-->D(OpenIDConenctHandler)
D--5.调用HandleChallengeAsync-->F(调用OpenIdConnectConfigurationRetriever.IConfigurationRetriever)
F--6.通过HTTP调用Idv4站点的/.well-known/openid-configuration-->F1(Idv4Config)
F1--7.通过上步骤取到的config.jwks_uri获取加密key信息-->F2(获得公钥私钥等信息)
F2--8.设置JsonWebKeySet-->F3(完成openIdConnectConfiguration.SigningKeysKeys的添加)
F3-->F4(完成)

IdentityServer4环境部署失败分析贴(一)

1.1 受保护站点的部分源码

Idv4部分源码

//注入Idev相关,配置authenticationScheme为oidc
public static IIdentityServerBuilder AddIdentityServer(this IServiceCollection services, Action<IdentityServerOptions> setupAction)
{
    services.Configure(setupAction);
    return services.AddIdentityServer();
}

PS: 这里要额外提一下,在Abp的模板项目中,如下配置使用Idv4可能无法正常跳转IDV4授权站完成单点登录

services.AddOpenIdConnect("oidc", options =>
{
   ...
}

需要额外添加下面这段,原因是Abp引入Microsoft.Identity,会重复设置schme导致错误,需如下代码添加设置。

services.AddAuthentication(options =>
{
        options.DefaultAuthenticateScheme = "oidc";
        options.DefaultChallengeScheme = "oidc";
        options.DefaultSignOutScheme = "oidc";
})

步骤5部分源码

 protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
        {
            Logger.EnteringOpenIdAuthenticationHandlerHandleUnauthorizedAsync(GetType().FullName);

            // order for local RedirectUri
            // 1. challenge.Properties.RedirectUri
            // 2. CurrentUri if RedirectUri is not set)
            if (string.IsNullOrEmpty(properties.RedirectUri))
            {
                properties.RedirectUri = CurrentUri;
            }
            Logger.PostAuthenticationLocalRedirect(properties.RedirectUri);

            if (_configuration == null && Options.ConfigurationManager != null)
            {
                //问题在这里
                _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
            }
            ...

步骤6部分源码

 public static async Task<OpenIdConnectConfiguration> GetAsync(string address, IDocumentRetriever retriever, CancellationToken cancel)
        {
            if (string.IsNullOrWhiteSpace(address))
                throw LogHelper.LogArgumentNullException(nameof(address));

            if (retriever == null)
            {
                throw LogHelper.LogArgumentNullException(nameof(retriever));
            }
            //当站点没启动的时候,这里可能出现问题
            string doc = await retriever.GetDocumentAsync(address, cancel).ConfigureAwait(false);

            LogHelper.LogVerbose(LogMessages.IDX21811, doc);
            OpenIdConnectConfiguration openIdConnectConfiguration = JsonConvert.DeserializeObject<OpenIdConnectConfiguration>(doc);
            if (!string.IsNullOrEmpty(openIdConnectConfiguration.JwksUri))
            {
                LogHelper.LogVerbose(LogMessages.IDX21812, openIdConnectConfiguration.JwksUri);
                //当证书配置或读取错误的时候,这里会出现问题
                string keys = await retriever.GetDocumentAsync(openIdConnectConfiguration.JwksUri, cancel).ConfigureAwait(false);

                LogHelper.LogVerbose(LogMessages.IDX21813, openIdConnectConfiguration.JwksUri);
                openIdConnectConfiguration.JsonWebKeySet = JsonConvert.DeserializeObject<JsonWebKeySet>(keys);
                foreach (SecurityKey key in openIdConnectConfiguration.JsonWebKeySet.GetSigningKeys())
                {
                    openIdConnectConfiguration.SigningKeys.Add(key);
                }
            }

            return openIdConnectConfiguration;
        }

通过流程进行问题解析:

通过查看日志可发现在流程5后开始报错

步骤一

访问:http://{ssoHost}/.well-known/openid-configuration

如果访问成功,则排除单点登录站点部署失败问题,前往步骤二

如果访问失败,则去解决单点登录站点部署问题。

步骤二

http://{ssoHost}/.well-known/openid-configuration/jwks

如果访问成功,则重启客户端

如果访问失败,那么有意思了...继续往下看

此时单点登录站点的日志
[10:25:24 Error] Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware
An unhandled exception has occurred while executing the request.
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: 出现了内部错误。
   at Internal.Cryptography.Helpers.OpenStorageProvider(CngProvider provider)
   at System.Security.Cryptography.CngKey.Import(Byte[] keyBlob, String curveName, CngKeyBlobFormat format, CngProvider provider)
   at System.Security.Cryptography.CngKey.Import(Byte[] keyBlob, CngKeyBlobFormat format)
   at Internal.Cryptography.Pal.X509Pal.DecodePublicKey(Oid oid, Byte[] encodedKeyValue, Byte[] encodedParameters, ICertificatePal certificatePal)
   at System.Security.Cryptography.X509Certificates.PublicKey.get_Key()
   at Microsoft.IdentityModel.Tokens.X509SecurityKey.get_PublicKey()
   at IdentityServer4.ResponseHandling.DiscoveryResponseGenerator.CreateJwkDocumentAsync()
   at IdentityServer4.Endpoints.DiscoveryKeyEndpoint.ProcessAsync(HttpContext context)
   at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events)
   at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
单点登录站点在此时的工作流程
graph TB

A(通过Http获取发现服务的配置信息)--1.进入SSO站点-->B(IdentityServerMiddleware)
B--2.调用Process-->C(进入DiscoveryKeyEndpoint)
C--3.遍历注入的加密Key-->D(返回JsonBWebKey)
部分源码解析:

IdentityServer4环境部署失败分析贴(一)

   public async Task Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events)
        {
            // this will check the authentication session and from it emit the check session
            // cookie needed from JS-based signout clients.
            await session.EnsureSessionIdCookieAsync();

            try
            {
                //根据请求路径,进入相应endpoint处理器
                var endpoint = router.Find(context);
                if (endpoint != null)
                {
                    _logger.LogInformation("Invoking IdentityServer endpoint: {endpointType} for {url}", endpoint.GetType().FullName, context.Request.Path.ToString());

                    var result = await endpoint.ProcessAsync(context);

                ...


  //创建JwtDocument
  public virtual async Task<IEnumerable<Models.JsonWebKey>> CreateJwkDocumentAsync()
        {
            var webKeys = new List<Models.JsonWebKey>();
            var signingCredentials = await Keys.GetSigningCredentialsAsync();
            var algorithm = signingCredentials?.Algorithm ?? Constants.SigningAlgorithms.RSA_SHA_256;

            foreach (var key in await Keys.GetValidationKeysAsync())
            {
                if (key is X509SecurityKey x509Key)
                {
                    var cert64 = Convert.ToBase64String(x509Key.Certificate.RawData);
                    var thumbprint = Base64Url.Encode(x509Key.Certificate.GetCertHash());
                    //当证书读取不正常的时候,PublicKey的属性构造器中会报错。
                    var pubKey = x509Key.PublicKey as RSA;
                    var parameters = pubKey.ExportParameters(false);
                    var exponent = Base64Url.Encode(parameters.Exponent);
                    var modulus = Base64Url.Encode(parameters.Modulus);

                    var webKey = new Models.JsonWebKey
                    {
                        kty = "RSA",
                        use = "sig",
                        kid = x509Key.KeyId,
                        x5t = thumbprint,
                        e = exponent,
                        n = modulus,
                        x5c = new[] { cert64 },
                        alg = algorithm
                    };

                    webKeys.Add(webKey);
                    continue;
                }
问题解析

通过日志可发现,问题出现在PublicKey的获取上,即证书的读取失败了。

解决方案!

终于到解决问题的时候了...

步骤一:

检查证书是否是临时证书

//在正式环境中,这可能会报错
builder.AddDeveloperSigningCredential(true, "tempkey.rsa");

//正确方式
builder.AddSigningCredential(new X509Certificate2(path,
                         Configuration["Certificates:Password"]))
这里可以参见郭的随笔:
https://www.cnblogs.com/guolianyu/p/9872661.html                        
步骤二:

如果已经添加了证书,且证书路径正确读取,且证书密码正确;

但是!在Windows Server2008服务器上部署仍失败时请检查 CNG Key Isolation 服务是否启动!

如果 该服务未启动,则启动后即可! IdentityServer4环境部署失败分析贴(一)

如果该服务已启动,则尴尬的很...如果您仍排除不了问题,可以评论告诉我

点赞
收藏
评论区
推荐文章
冴羽 冴羽
2年前
VuePress 博客之 SEO 优化(六)站长工具
前言在中,我们使用VuePress搭建了一个博客,最终的效果查看:。本篇接着讲讲SEO优化会用到的站长平台和工具等。1.百度统计地址:网站流量分析工具,能够告诉用户访客是如何找到并浏览用户的网站,在网站上做了些什么2.百度搜索资源平台地址:在添加站点后,可以看到自己站点在百度搜索结果中的一些表现:百度搜索中心也提供了一些教程如:1.《平
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Easter79 Easter79
3年前
tidb集群某个节点报错之:node_exporter
今天启动集群tidb时出现一个错误,是某个tikv节点报错:node\_exporter9100.service failed一个节点的问题会导致整个集群启动失败。去此节点下的日志文件中查找,发现没有什么报错原因。无奈此时只能去系统日志中查看发生了什么问题果然发现了问题Jan1615:35:05ip1723126133
代码还原的技术: Unidbg hook_add_new实现条件断点(二)
一、目标在做代码还原的时候,有时候会分析一组结果,希望在中途下个条件断点,比如在代码行0x1234,R00x5678的时候触发断点。今天我们就来试着搞一下。TIP:Unidbg代码同步到官方最新版,最新版已经支持浮点寄存器的显示了。二、步骤先写个floatdemotwo把祖传算法升个级extern"C"JNIEXPORTjstringJNIC
Wesley13 Wesley13
3年前
JAVA拦截器,JAVA返回结果跨域问题解决
遇到的问题:通过拦截器做权限控制,没有权限时返回了json值,结果前端请求时提示跨域了备注:我的前端站点和后端站点不是一个地址报错1:AccesstoXMLHttpRequestat'http://localhost:8089/appcicd/appinfo/getappinfos'fromorigi
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
可莉 可莉
3年前
21.Shiro在springboot与vue前后端分离项目里的session管理
1.前言当决定前端与后端代码分开部署时,发现shiro自带的session不起作用了。然后通过对请求head的分析,然后在网上查找一部分解决方案。最终就是,登录成功之后,前端接收到后端传回来的sessionId,存入cookie当中。之后,前端向后端发送请求时,请求Head中都会带上这个sessionid。后端代码通过对这个se
Stella981 Stella981
3年前
CmsEasy_v5.7 漏洞测试
01前言    下载了一套CMS,看了一下,部分代码加密,也懒得去解密了,费事。直接登录后台进行黑盒结合白盒进行测试,也发现了一些问题,汇总一下,不做具体代码分析。02环境准备CmsEasy官网:http://www.cmseasy.cn网站源码版本:CmsEasy\_v5.7\_UTF80208程序源码下载:
Wesley13 Wesley13
3年前
Java多线程导致的的一个事物性问题
业务场景我们现在有一个类似于文件上传的功能,各个子站点接受业务,业务上传文件,各个子站点的文件需要提交到总站点保存,文件是按批次提交到总站点的,也就是说,一个批次下面约有几百个文件。      考虑到白天提交这么多文件会影响到子站点其他系统带宽,我们将分站点的文件提交到总站点这个操作过程独立出来,放到晚上来做,具体时间是晚上7:00到早上7:00。
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这