NetCore OpenIdConnect验证为什么要设置Authority?

打印 上一主题 下一主题

主题 557|帖子 557|积分 1671

  在使用Identity Server作Identity Provider的时候,我们在NetCore的ConfigureServices((IServiceCollection services))方法中,常需要指定options的Authority,如下代码所示:
  1. public void ConfigureServices(IServiceCollection services)
  2.         {
  3.             services.AddControllersWithViews();
  4.          
  5.             //
  6.             #region MVC client
  7.             //关闭了 JWT 身份信息类型映射
  8.             //这样就允许 well-known 身份信息(比如,“sub” 和 “idp”)无干扰地流过。
  9.             //这个身份信息类型映射的“清理”必须在调用 AddAuthentication()之前完成
  10.             JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
  11.             services.AddAuthentication(options =>
  12.             {
  13.                 options.DefaultScheme = "Cookies";
  14.                 options.DefaultChallengeScheme = "oidc";
  15.             })
  16.                .AddCookie("Cookies")
  17.               .AddOpenIdConnect("oidc", options =>
  18.               {
  19.                
  20.                   options.Authority = "http://localhost:5001";
  21.                   options.RequireHttpsMetadata = false;
  22.                   options.ClientId = "code_client";
  23.                   //Client secret
  24.                   options.ClientSecret = "511536EF-F270-4058-80CA-1C89C192F69A".ToString();
  25.                   //code方式
  26.                   options.ResponseType = "code";
  27.                   //Scope可以理解为申请何种操作范围
  28.                   options.Scope.Add("code_scope1"); //添加授权资源
  29.    
  30.                   options.SaveTokens = true;
  31.                   options.GetClaimsFromUserInfoEndpoint = true;
  32.                
  33.               });
  34.             #endregion
  35.             services.ConfigureNonBreakingSameSiteCookies();
  36.         }
复制代码
  其中options.Authority = "http://localhost:5001"需要设置,如果把它注释掉,则会报如下的错误,从这个异常看得出来,应该是Authority没有设置:
 
   那么为什么我们一定要设置.Authority 呢?我们一步一步来看看:
  1.在调用services.AddOpenIdConnect方法时,实质调用的是OpenIdConnectExtensions静态类的AddOpenIdConnect静态方法,如下所示:
  1. public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action<OpenIdConnectOptions> configureOptions)
  2.         {
  3.             builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIdConnectOptions>, OpenIdConnectPostConfigureOptions>());
  4.             return builder.AddRemoteScheme<OpenIdConnectOptions, OpenIdConnectHandler>(authenticationScheme, displayName, configureOptions);
  5.         }
复制代码
 2.从上面的代码看得出,实际上调用的是AuthenticationBuilder的AddRemoteScheme扩展方法,如下所示:
  1. /// <summary>
  2.         /// Adds a <see cref="AuthenticationScheme"/> which can be used by <see cref="IAuthenticationService"/>.
  3.         /// </summary>
  4.         /// <typeparam name="TOptions">The <see cref="AuthenticationSchemeOptions"/> type to configure the handler."/>.</typeparam>
  5.         /// <typeparam name="THandler">The <see cref="AuthenticationHandler{TOptions}"/> used to handle this scheme.</typeparam>
  6.         /// <param name="authenticationScheme">The name of this scheme.</param>
  7.         /// <param name="displayName">The display name of this scheme.</param>
  8.         /// <param name="configureOptions">Used to configure the scheme options.</param>
  9.         /// <returns>The builder.</returns>
  10.         public virtual AuthenticationBuilder AddScheme<TOptions, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]THandler>(string authenticationScheme, string? displayName, Action<TOptions>? configureOptions)
  11.             where TOptions : AuthenticationSchemeOptions, new()
  12.             where THandler : AuthenticationHandler<TOptions>
  13.             => AddSchemeHelper<TOptions, THandler>(authenticationScheme, displayName, configureOptions);<br>       <br><br>
复制代码
  1. private AuthenticationBuilder AddSchemeHelper<TOptions, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]THandler>(string authenticationScheme, string? displayName, Action<TOptions>? configureOptions)
  2.             where TOptions : AuthenticationSchemeOptions, new()
  3.             where THandler : class, IAuthenticationHandler
  4.         {
  5.             Services.Configure<AuthenticationOptions>(o =>
  6.             {
  7.                 //注册Scheme对应的HandlerType
  8.                 o.AddScheme(authenticationScheme, scheme => {
  9.                     scheme.HandlerType = typeof(THandler);
  10.                     scheme.DisplayName = displayName;
  11.                 });
  12.             });
  13.             if (configureOptions != null)
  14.             {
  15.                 Services.Configure(authenticationScheme, configureOptions);
  16.             }
  17.             //Options验证
  18.             Services.AddOptions<TOptions>(authenticationScheme).Validate(o => {
  19.                 o.Validate(authenticationScheme);
  20.                 return true;
  21.             });
  22.             Services.AddTransient<THandler>();
  23.             return this;
  24.         }
复制代码
  
  看得出来,实际上调用的是AddSchemeHelper方法,在这个方法里,有一个很重要的Options验证:
  1. Services.AddOptions<TOptions>(authenticationScheme).Validate(o => {
  2.                 o.Validate(authenticationScheme);
  3.                 return true;
  4.             });<br><br>在这里需要对AuthenticationSchemeOptions进行验证<br><br>
复制代码
   3.上一步的Options验证中实际上调用的是OpenIdConnectOptions类的Validate方法,如下所示:
  1. /// <summary>
  2.         /// Check that the options are valid.  Should throw an exception if things are not ok.
  3.         /// </summary>
  4.         public override void Validate()
  5.         {
  6.             base.Validate();
  7.             if (MaxAge.HasValue && MaxAge.Value < TimeSpan.Zero)
  8.             {
  9.                 throw new ArgumentOutOfRangeException(nameof(MaxAge), MaxAge.Value, "The value must not be a negative TimeSpan.");
  10.             }
  11.             if (string.IsNullOrEmpty(ClientId))
  12.             {
  13.                 throw new ArgumentException("Options.ClientId must be provided", nameof(ClientId));
  14.             }
  15.             if (!CallbackPath.HasValue)
  16.             {
  17.                 throw new ArgumentException("Options.CallbackPath must be provided.", nameof(CallbackPath));
  18.             }
  19.             //如果Authority没有设置,则报下面这个异常
  20.             if (ConfigurationManager == null)
  21.             {
  22.                 throw new InvalidOperationException($"Provide {nameof(Authority)}, {nameof(MetadataAddress)}, "
  23.                 + $"{nameof(Configuration)}, or {nameof(ConfigurationManager)} to {nameof(OpenIdConnectOptions)}");
  24.             }
  25.         }
复制代码
  从这里看得出来,如果ConfigurationManager为空,则就会报前面中的异常了,所以异常就是这么来的。
 
4.那么为什么ConfigurationManager为空呢?我们回顾OpenIdConnectExtensions的AddOpenIdConnect方法:
  1.    public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action<OpenIdConnectOptions> configureOptions)
  2.         {
  3.             builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIdConnectOptions>, OpenIdConnectPostConfigureOptions>());
  4.             return builder.AddRemoteScheme<OpenIdConnectOptions, OpenIdConnectHandler>(authenticationScheme, displayName, configureOptions);
  5.         }  
复制代码
看得出AuthenticationBuilder的Services添加了一个名为OpenIdConnectPostConfigureOptions的单例服务: builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
继续看下OpenIdConnectPostConfigureOptions的源码:
  1.       /// <summary>
  2.         /// Invoked to post configure a TOptions instance.
  3.         /// </summary>
  4.         /// <param name="name">The name of the options instance being configured.</param>
  5.         /// <param name="options">The options instance to configure.</param>
  6.         public void PostConfigure(string name, OpenIdConnectOptions options)
  7.         {
  8.             options.DataProtectionProvider = options.DataProtectionProvider ?? _dp;
  9.             if (string.IsNullOrEmpty(options.SignOutScheme))
  10.             {
  11.                 options.SignOutScheme = options.SignInScheme;
  12.             }
  13.             if (options.StateDataFormat == null)
  14.             {
  15.                 var dataProtector = options.DataProtectionProvider.CreateProtector(
  16.                     typeof(OpenIdConnectHandler).FullName!, name, "v1");
  17.                 options.StateDataFormat = new PropertiesDataFormat(dataProtector);
  18.             }
  19.             if (options.StringDataFormat == null)
  20.             {
  21.                 var dataProtector = options.DataProtectionProvider.CreateProtector(
  22.                     typeof(OpenIdConnectHandler).FullName!,
  23.                     typeof(string).FullName!,
  24.                     name,
  25.                     "v1");
  26.                 options.StringDataFormat = new SecureDataFormat<string>(new StringSerializer(), dataProtector);
  27.             }
  28.             if (string.IsNullOrEmpty(options.TokenValidationParameters.ValidAudience) && !string.IsNullOrEmpty(options.ClientId))
  29.             {
  30.                 options.TokenValidationParameters.ValidAudience = options.ClientId;
  31.             }
  32.             if (options.Backchannel == null)
  33.             {
  34.                 options.Backchannel = new HttpClient(options.BackchannelHttpHandler ?? new HttpClientHandler());
  35.                 options.Backchannel.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft ASP.NET Core OpenIdConnect handler");
  36.                 options.Backchannel.Timeout = options.BackchannelTimeout;
  37.                 options.Backchannel.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB
  38.             }
  39.             if (options.ConfigurationManager == null)
  40.             {
  41.                 if (options.Configuration != null)
  42.                 {
  43.                     options.ConfigurationManager = new StaticConfigurationManager<OpenIdConnectConfiguration>(options.Configuration);
  44.                 }
  45.                 else if (!(string.IsNullOrEmpty(options.MetadataAddress) && string.IsNullOrEmpty(options.Authority)))
  46.                 {
  47.                     if (string.IsNullOrEmpty(options.MetadataAddress) && !string.IsNullOrEmpty(options.Authority))
  48.                     {
  49.                         options.MetadataAddress = options.Authority;
  50.                         if (!options.MetadataAddress.EndsWith("/", StringComparison.Ordinal))
  51.                         {
  52.                             options.MetadataAddress += "/";
  53.                         }
  54.                         options.MetadataAddress += ".well-known/openid-configuration";
  55.                     }
  56.                     if (options.RequireHttpsMetadata && !(options.MetadataAddress?.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ?? false))
  57.                     {
  58.                         throw new InvalidOperationException("The MetadataAddress or Authority must use HTTPS unless disabled for development by setting RequireHttpsMetadata=false.");
  59.                     }
  60.                     options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.MetadataAddress, new OpenIdConnectConfigurationRetriever(),
  61.                         new HttpDocumentRetriever(options.Backchannel) { RequireHttps = options.RequireHttpsMetadata })
  62.                     {
  63.                         RefreshInterval = options.RefreshInterval,
  64.                         AutomaticRefreshInterval = options.AutomaticRefreshInterval,
  65.                     };
  66.                 }
  67.             }
  68.         }
复制代码
  
注意看两个标红的if语句,才发现OpenIdConnectOptions的Authority和MetadataAddress在都没有设置的情况下,OpenIdConnectOptions的ConfigurationManager为空!
 
    所以,从上文看得出,如果不设置OpenIdConnectOptions的Authority,那么无法进行OpenIdConnect认证哦!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大号在练葵花宝典

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表