1.Xshell遠程登錄Linux系統 在實際的項目部署工作中,遠程登錄到伺服器上是繞不開的彎。本文遠程登錄Linux系統選用工具的是目前最常用、最好用的Xshell。Xsheel是一個強大的安全終端模擬軟體,它支持SSH1、SSH2以及Windows系統的Telnet協議。它的運行速度流程並且完美 ...
在使用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沒有設置:
那麼為什麼我們一定要設置.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認證哦!