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

  • NetCore OpenIdConnect验证为什么要设置Authority?已关闭评论
  • 21 次浏览
  • A+
所属分类:.NET技术
摘要

  在使用Identity Server作Identity Provider的时候,我们在NetCore的ConfigureServices((IServiceCollection services))方法中,常需要指定options的Authority,如下代码所示:

  在使用Identity Server作Identity Provider的时候,我们在NetCore的ConfigureServices((IServiceCollection services))方法中,常需要指定options的Authority,如下代码所示:

 public void ConfigureServices(IServiceCollection services)         {             services.AddControllersWithViews();                        //             #region MVC client             //关闭了 JWT 身份信息类型映射             //这样就允许 well-known 身份信息(比如,“sub” 和 “idp”)无干扰地流过。             //这个身份信息类型映射的“清理”必须在调用 AddAuthentication()之前完成             JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();             services.AddAuthentication(options =>             {                 options.DefaultScheme = "Cookies";                 options.DefaultChallengeScheme = "oidc";             })                .AddCookie("Cookies")                .AddOpenIdConnect("oidc", options =>               {                                    options.Authority = "http://localhost:5001";                   options.RequireHttpsMetadata = false;                   options.ClientId = "code_client";                   //Client secret                   options.ClientSecret = "511536EF-F270-4058-80CA-1C89C192F69A".ToString();                   //code方式                   options.ResponseType = "code";                   //Scope可以理解为申请何种操作范围                   options.Scope.Add("code_scope1"); //添加授权资源                       options.SaveTokens = true;                   options.GetClaimsFromUserInfoEndpoint = true;                                });              #endregion             services.ConfigureNonBreakingSameSiteCookies();         } 

  其中options.Authority = "http://localhost:5001"需要设置,如果把它注释掉,则会报如下的错误,从这个异常看得出来,应该是Authority没有设置:

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

   那么为什么我们一定要设置.Authority 呢?我们一步一步来看看:

  1.在调用services.AddOpenIdConnect方法时,实质调用的是OpenIdConnectExtensions静态类的AddOpenIdConnect静态方法,如下所示:

 public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action<OpenIdConnectOptions> configureOptions)         {             builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIdConnectOptions>, OpenIdConnectPostConfigureOptions>());             return builder.AddRemoteScheme<OpenIdConnectOptions, OpenIdConnectHandler>(authenticationScheme, displayName, configureOptions);         } 

 2.从上面的代码看得出,实际上调用的是AuthenticationBuilder的AddRemoteScheme扩展方法,如下所示:

/// <summary>         /// Adds a <see cref="AuthenticationScheme"/> which can be used by <see cref="IAuthenticationService"/>.         /// </summary>         /// <typeparam name="TOptions">The <see cref="AuthenticationSchemeOptions"/> type to configure the handler."/>.</typeparam>         /// <typeparam name="THandler">The <see cref="AuthenticationHandler{TOptions}"/> used to handle this scheme.</typeparam>         /// <param name="authenticationScheme">The name of this scheme.</param>         /// <param name="displayName">The display name of this scheme.</param>         /// <param name="configureOptions">Used to configure the scheme options.</param>         /// <returns>The builder.</returns>         public virtual AuthenticationBuilder AddScheme<TOptions, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]THandler>(string authenticationScheme, string? displayName, Action<TOptions>? configureOptions)             where TOptions : AuthenticationSchemeOptions, new()             where THandler : AuthenticationHandler<TOptions>             => AddSchemeHelper<TOptions, THandler>(authenticationScheme, displayName, configureOptions);


 private AuthenticationBuilder AddSchemeHelper<TOptions, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]THandler>(string authenticationScheme, string? displayName, Action<TOptions>? configureOptions)             where TOptions : AuthenticationSchemeOptions, new()             where THandler : class, IAuthenticationHandler         {             Services.Configure<AuthenticationOptions>(o =>             {                 //注册Scheme对应的HandlerType                 o.AddScheme(authenticationScheme, scheme => {                     scheme.HandlerType = typeof(THandler);                     scheme.DisplayName = displayName;                 });             });             if (configureOptions != null)             {                 Services.Configure(authenticationScheme, configureOptions);             }             //Options验证             Services.AddOptions<TOptions>(authenticationScheme).Validate(o => {                 o.Validate(authenticationScheme);                 return true;             });             Services.AddTransient<THandler>();             return this;         } 

  

  看得出来,实际上调用的是AddSchemeHelper方法,在这个方法里,有一个很重要的Options验证:

 Services.AddOptions<TOptions>(authenticationScheme).Validate(o => {                 o.Validate(authenticationScheme);                 return true;             });

在这里需要对AuthenticationSchemeOptions进行验证

   3.上一步的Options验证中实际上调用的是OpenIdConnectOptions类的Validate方法,如下所示:

 /// <summary>         /// Check that the options are valid.  Should throw an exception if things are not ok.         /// </summary>         public override void Validate()         {             base.Validate();              if (MaxAge.HasValue && MaxAge.Value < TimeSpan.Zero)             {                 throw new ArgumentOutOfRangeException(nameof(MaxAge), MaxAge.Value, "The value must not be a negative TimeSpan.");             }              if (string.IsNullOrEmpty(ClientId))             {                 throw new ArgumentException("Options.ClientId must be provided", nameof(ClientId));             }              if (!CallbackPath.HasValue)             {                 throw new ArgumentException("Options.CallbackPath must be provided.", nameof(CallbackPath));             }              //如果Authority没有设置,则报下面这个异常             if (ConfigurationManager == null)             {                 throw new InvalidOperationException($"Provide {nameof(Authority)}, {nameof(MetadataAddress)}, "                 + $"{nameof(Configuration)}, or {nameof(ConfigurationManager)} to {nameof(OpenIdConnectOptions)}");             }         } 

  从这里看得出来,如果ConfigurationManager为空,则就会报前面中的异常了,所以异常就是这么来的。

 

4.那么为什么ConfigurationManager为空呢?我们回顾OpenIdConnectExtensions的AddOpenIdConnect方法:

   public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action<OpenIdConnectOptions> configureOptions)         {             builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIdConnectOptions>, OpenIdConnectPostConfigureOptions>());             return builder.AddRemoteScheme<OpenIdConnectOptions, OpenIdConnectHandler>(authenticationScheme, displayName, configureOptions);         }  

看得出AuthenticationBuilder的Services添加了一个名为OpenIdConnectPostConfigureOptions的单例服务: builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIdConnectOptions>, OpenIdConnectPostConfigureOptions>());

继续看下OpenIdConnectPostConfigureOptions的源码:

      /// <summary>         /// Invoked to post configure a TOptions instance.         /// </summary>         /// <param name="name">The name of the options instance being configured.</param>         /// <param name="options">The options instance to configure.</param>         public void PostConfigure(string name, OpenIdConnectOptions options)         {             options.DataProtectionProvider = options.DataProtectionProvider ?? _dp;              if (string.IsNullOrEmpty(options.SignOutScheme))             {                 options.SignOutScheme = options.SignInScheme;             }              if (options.StateDataFormat == null)             {                 var dataProtector = options.DataProtectionProvider.CreateProtector(                     typeof(OpenIdConnectHandler).FullName!, name, "v1");                 options.StateDataFormat = new PropertiesDataFormat(dataProtector);             }              if (options.StringDataFormat == null)             {                 var dataProtector = options.DataProtectionProvider.CreateProtector(                     typeof(OpenIdConnectHandler).FullName!,                     typeof(string).FullName!,                     name,                     "v1");                  options.StringDataFormat = new SecureDataFormat<string>(new StringSerializer(), dataProtector);             }              if (string.IsNullOrEmpty(options.TokenValidationParameters.ValidAudience) && !string.IsNullOrEmpty(options.ClientId))             {                 options.TokenValidationParameters.ValidAudience = options.ClientId;             }              if (options.Backchannel == null)             {                 options.Backchannel = new HttpClient(options.BackchannelHttpHandler ?? new HttpClientHandler());                 options.Backchannel.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft ASP.NET Core OpenIdConnect handler");                 options.Backchannel.Timeout = options.BackchannelTimeout;                 options.Backchannel.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB             }              if (options.ConfigurationManager == null)             {                 if (options.Configuration != null)                 {                     options.ConfigurationManager = new StaticConfigurationManager<OpenIdConnectConfiguration>(options.Configuration);                 }                 else if (!(string.IsNullOrEmpty(options.MetadataAddress) && string.IsNullOrEmpty(options.Authority)))                 {                     if (string.IsNullOrEmpty(options.MetadataAddress) && !string.IsNullOrEmpty(options.Authority))                     {                         options.MetadataAddress = options.Authority;                         if (!options.MetadataAddress.EndsWith("/", StringComparison.Ordinal))                         {                             options.MetadataAddress += "/";                         }                          options.MetadataAddress += ".well-known/openid-configuration";                     }                      if (options.RequireHttpsMetadata && !(options.MetadataAddress?.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ?? false))                     {                         throw new InvalidOperationException("The MetadataAddress or Authority must use HTTPS unless disabled for development by setting RequireHttpsMetadata=false.");                     }                      options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.MetadataAddress, new OpenIdConnectConfigurationRetriever(),                         new HttpDocumentRetriever(options.Backchannel) { RequireHttps = options.RequireHttpsMetadata })                     {                         RefreshInterval = options.RefreshInterval,                         AutomaticRefreshInterval = options.AutomaticRefreshInterval,                     };                 }             }         } 

  

注意看两个标红的if语句,才发现OpenIdConnectOptions的Authority和MetadataAddress在都没有设置的情况下,OpenIdConnectOptions的ConfigurationManager为空!

 

    所以,从上文看得出,如果不设置OpenIdConnectOptions的Authority,那么无法进行OpenIdConnect认证哦!