hangfire.httpjob支持oath2认证
hangfire.httpjob用的是basic认证,现在需要对接sso,支持oath2认证,没有找到相关的文章。只有自己找源码修改。
下载hangfire.httpjob源码
找到类HttpJob =》 方法PrepareHttpRequestMessage
修改类似如下的代码
if (!string.IsNullOrEmpty(item.BasicUserName) && !string.IsNullOrEmpty(item.BasicPassword)) { var byteArray = Encoding.ASCII.GetBytes(item.BasicUserName + ":" + item.BasicPassword); request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); RunWithTry(() => context.WriteLine($"【Header】【Authorization】Add True")); }
为
if (!string.IsNullOrEmpty(item.BasicUserName) && !string.IsNullOrEmpty(item.BasicPassword)) { var byteArray = Encoding.ASCII.GetBytes(item.BasicUserName + ":" + item.BasicPassword); request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); RunWithTry(() => context.WriteLine($"【Header】【Authorization】Add True")); } else if(item.BearerAuth != null) { var tokenMgr=TokenManagers.GetManager(item.BearerAuth); request.Headers.Authorization = new AuthenticationHeaderValue("bearer", tokenMgr.GetToken().ConfigureAwait(false).GetAwaiter().GetResult()); }
增加类TokenManagers
using IdentityModel.Client; using System; using System.Collections.Generic; using System.Net.Http; using System.Text; using System.Threading.Tasks; namespace Hangfire.HttpJob.Server { public static class TokenManagers { private static volatile Dictionary<int,TokenManager> _mgrs=new Dictionary<int,TokenManager>(); private static object _lockObj=new object(); public static TokenManager GetManager(BearerAuth config) { var key=GetKey(config); if(!_mgrs.ContainsKey(key)) { lock(_lockObj) { if(!_mgrs.ContainsKey(key)) { var mgr=new TokenManager(config); _mgrs.Add(key, mgr); } } } return _mgrs[key]; } private static int GetKey(BearerAuth config) { return $"{config.Address}_{config.ClientId}_{config.ClientSecret}_{config.Scope}".GetHashCode(); } } public class TokenManager { private string _accessToken=string.Empty; private string _refreshToken=string.Empty; private DateTime _expiredTime=DateTime.MinValue; HttpClient _httpClient = new HttpClient(); BearerAuth _authConfig=null; DiscoveryDocumentResponse _disco; public TokenManager(BearerAuth authConfig) { _authConfig = authConfig; _disco = _httpClient.GetDiscoveryDocumentAsync(authConfig.Address).GetAwaiter().GetResult(); if(_disco.IsError) { throw _disco.Exception; } } public async Task<string> GetToken() { if(_expiredTime <= DateTime.Now) { if(string.IsNullOrWhiteSpace(_refreshToken)) { await RequestTokenAsync(); } else { await RequestRefreshTokenAsync(); } } return _accessToken; } internal async Task RequestTokenAsync() { var tokenResponse = await _httpClient.RequestClientCredentialsTokenAsync( new ClientCredentialsTokenRequest { Address=_disco.TokenEndpoint, ClientId= _authConfig.ClientId, ClientSecret= _authConfig.ClientSecret, Scope=_authConfig.Scope, }); if(tokenResponse.IsError) { throw new Exception($"获取Token失败。error:{tokenResponse.Error}。description:{tokenResponse.ErrorDescription}"); } _accessToken = tokenResponse.AccessToken; _refreshToken = tokenResponse.RefreshToken; _expiredTime = DateTime.Now.AddSeconds(tokenResponse.ExpiresIn); } protected async Task RequestRefreshTokenAsync() { var tokenResponse = await _httpClient.RequestRefreshTokenAsync(new RefreshTokenRequest(){ Address=_disco.TokenEndpoint, RefreshToken=_refreshToken }); if(tokenResponse.IsError) { throw new Exception($"刷新Token失败。error:{tokenResponse.Error}。description:{tokenResponse.ErrorDescription}"); } _accessToken = tokenResponse.AccessToken; _refreshToken = tokenResponse.RefreshToken; _expiredTime = DateTime.Now.AddSeconds(tokenResponse.ExpiresIn); } } }
新建一个项目zac.job并引用项目hangfire.httpjob
appsettings
{ "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Trace", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "Hangfire": { "HangfireSettings": { "ServerName": "Zac.Job", "StartUpPath": "/job", "ReadOnlyPath": "", "JobQueues": [ "default", "apis", "recurring" ], "WorkerCount": 50, "DisplayStorageConnectionString": false, "HttpAuthInfo": { "SslRedirect": false, "RequireSsl": false, "LoginCaseSensitive": true, "IsOpenLogin": true, "Users": [ { "Login": "admin", "PasswordClear": "test" } ] }, "ConnectionString": "server=xx;database=xxxx;uid=sa;pwd=xxx" }, "HttpJobOptions": { "Lang": "zh", "DefaultTimeZone": "", "CurrentDomain": "//", "EnableDingTalk": true, "DefaultRecurringQueueName": "recurring", "GlobalSettingJsonFilePath": "", "Proxy": "", "JobExpirationTimeoutDay": 7, "GlobalHttpTimeOut": 5000, "MailOption": { "Server": "xxx", "Port": 25, "User": "xxx", "Password": "xxx", "UseSsl": false, "AlertMailList": [] }, "DingTalkOption": { "Token": "", "AtPhones": "", "IsAtAll": false } } }, "SsoOptions": { "Enabled": true, "Authority": "https://localhost:3010/", "RequireHttpsMetadata": false, "ClientId": "xxx", "ClientSecret": "xxx", "ResponseType": "code", "Scopes": [ "openid" ] } }
startup.cs
public class Startup { public Startup(IConfiguration configuration) { JsonConfig = configuration; JsonConfig.GetSection("SsoOptions").Bind(SsoOptions); } public IConfiguration JsonConfig { get; } public SsoOptions SsoOptions { get; set; } = new SsoOptions(); // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddSelfHangfire(JsonConfig); if(SsoOptions.Enabled) { services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = "oidc"; }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, (opt) => { opt.SlidingExpiration = true; opt.ExpireTimeSpan = TimeSpan.FromHours(1); }) .AddOpenIdConnect("oidc", options => { options.Authority = SsoOptions.Authority; options.RequireHttpsMetadata = SsoOptions.RequireHttpsMetadata; options.ClientId = SsoOptions.ClientId; options.ClientSecret = SsoOptions.ClientSecret; options.ResponseType = SsoOptions.ResponseType; //代表 options.Scope.Clear(); if(SsoOptions.Scopes != null) { foreach(var scope in SsoOptions.Scopes) { options.Scope.Add(scope); } } options.SaveTokens = true; }); services.AddAuthorization(); } services.AddControllersWithViews(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory logging) { app.UseRouting(); //app.UseMiddleware<AuthMiddleware>(); if(SsoOptions.Enabled) { app.UseAuthentication(); app.UseAuthorization(); } //app.Map(new PathString("/job"), tt => tt.UseMiddleware<AuthMiddleware>()); app.ConfigureSelfHangfire(JsonConfig,SsoOptions); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } }
HangfireCollectionExtensions.cs
public static class HangfireCollectionExtensions { private const string HangfireSettingsKey = "Hangfire:HangfireSettings"; private const string HttpJobOptionsKey = "Hangfire:HttpJobOptions"; private const string HangfireConnectStringKey = "Hangfire:HangfireSettings:ConnectionString"; private const string HangfireLangKey = "Hangfire:HttpJobOptions:Lang"; public static IServiceCollection AddSelfHangfire(this IServiceCollection services, IConfiguration configuration) { var hangfireSettings = configuration.GetSection(HangfireSettingsKey); var httpJobOptions = configuration.GetSection(HttpJobOptionsKey); services.Configure<HangfireSettings>(hangfireSettings); services.Configure<HangfireHttpJobOptions>(httpJobOptions); services.AddTransient<IBackgroundProcess, ProcessMonitor>(); services.AddHangfire(globalConfiguration => { services.ConfigurationHangfire(configuration, globalConfiguration); }); services.AddHangfireServer((provider, config) => { var settings = provider.GetService<IOptions<HangfireSettings>>().Value; ConfigFromEnv(settings); var queues = settings.JobQueues.Select(m => m.ToLower()).Distinct().ToList(); var workerCount = Math.Max(Environment.ProcessorCount, settings.WorkerCount); //工作线程数,当前允许的最大线程,默认20 config.ServerName = settings.ServerName; config.ServerTimeout = TimeSpan.FromMinutes(4); config.SchedulePollingInterval = TimeSpan.FromSeconds(5);//秒级任务需要配置短点,一般任务可以配置默认时间,默认15秒 config.ShutdownTimeout = TimeSpan.FromMinutes(30); //超时时间 config.Queues = queues.ToArray(); //队列 config.WorkerCount = workerCount; }); return services; } public static void ConfigurationHangfire(this IServiceCollection services, IConfiguration configuration, IGlobalConfiguration globalConfiguration) { var serverProvider = services.BuildServiceProvider(); var langStr = configuration.GetSection(HangfireLangKey).Get<string>(); var envLangStr = GetEnvConfig<string>("Lang"); if (!string.IsNullOrEmpty(envLangStr)) langStr = envLangStr; if (!string.IsNullOrEmpty(langStr)) { System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(langStr); } var hangfireSettings = serverProvider.GetService<IOptions<HangfireSettings>>().Value; ConfigFromEnv(hangfireSettings); var httpJobOptions = serverProvider.GetService<IOptions<HangfireHttpJobOptions>>().Value; ConfigFromEnv(httpJobOptions); httpJobOptions.GlobalSettingJsonFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory!, "hangfire", "hangfire_global.json"); var sqlConnectStr = configuration.GetSection(HangfireConnectStringKey).Get<string>(); var envSqlConnectStr = GetEnvConfig<string>("HangfireSqlserverConnectionString"); if (!string.IsNullOrEmpty(envSqlConnectStr)) sqlConnectStr = envSqlConnectStr; var mssqlOption = new SqlServerStorageOptions { CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), QueuePollInterval = TimeSpan.Zero, UseRecommendedIsolationLevel = true, UsePageLocksOnDequeue = true, DisableGlobalLocks = true }; globalConfiguration.UseSqlServerStorage(sqlConnectStr, mssqlOption) .UseConsole(new ConsoleOptions { BackgroundColor = "#000079" }) .UseTagsWithSql(new TagsOptions() { TagsListStyle = TagsListStyle.Dropdown }) .UseHangfireHttpJob(httpJobOptions) .UseHeartbeatPage(); } public static IApplicationBuilder ConfigureSelfHangfire(this IApplicationBuilder app, IConfiguration configuration,SsoOptions ssoOptions) { var langStr = configuration.GetSection(HangfireLangKey).Get<string>(); var envLangStr = GetEnvConfig<string>("Lang"); if (!string.IsNullOrEmpty(envLangStr)) langStr = envLangStr; if (!string.IsNullOrEmpty(langStr)) { var options = new RequestLocalizationOptions { DefaultRequestCulture = new RequestCulture(langStr) }; app.UseRequestLocalization(options); } var services = app.ApplicationServices; var hangfireSettings = services.GetService<IOptions<HangfireSettings>>().Value; var dashbordConfig = new DashboardOptions { AppPath = "#", IgnoreAntiforgeryToken = true, DisplayStorageConnectionString = hangfireSettings.DisplayStorageConnectionString, IsReadOnlyFunc = Context => false }; if(ssoOptions.Enabled) { dashbordConfig.Authorization = new[] { new BearerAuthAuthorizationFilter() }; } app.UseHangfireHttpJob().UseHangfireDashboard(hangfireSettings.StartUpPath, dashbordConfig); if (!string.IsNullOrEmpty(hangfireSettings.ReadOnlyPath)) //只读面板,只能读取不能操作 app.UseHangfireDashboard(hangfireSettings.ReadOnlyPath, new DashboardOptions { IgnoreAntiforgeryToken = true, AppPath = hangfireSettings.StartUpPath, //返回时跳转的地址 DisplayStorageConnectionString = false, //是否显示数据库连接信息 IsReadOnlyFunc = Context => true }); return app; } #region Docker运行的参数配置https://github.com/yuzd/Hangfire.HttpJob/wiki/000.Docker-Quick-Start private static void ConfigFromEnv(HangfireSettings settings) { var hangfireQueues = GetEnvConfig<string>("HangfireQueues"); if (!string.IsNullOrEmpty(hangfireQueues)) { settings.JobQueues = hangfireQueues.Split(',').ToList(); } var serverName = GetEnvConfig<string>("ServerName"); if (!string.IsNullOrEmpty(serverName)) { settings.ServerName = serverName; } var workerCount = GetEnvConfig<string>("WorkerCount"); if (!string.IsNullOrEmpty(workerCount)) { settings.WorkerCount = int.Parse(workerCount); } var tablePrefix = GetEnvConfig<string>("TablePrefix"); if (!string.IsNullOrEmpty(tablePrefix)) { settings.TablePrefix = tablePrefix; } var hangfireUserName = GetEnvConfig<string>("HangfireUserName"); var hangfirePwd = GetEnvConfig<string>("HangfirePwd"); if (!string.IsNullOrEmpty(hangfireUserName) && !string.IsNullOrEmpty(hangfirePwd)) { settings.HttpAuthInfo = new HttpAuthInfo { Users = new List<UserInfo>() }; settings.HttpAuthInfo.Users.Add(new UserInfo { Login = hangfireUserName, PasswordClear = hangfirePwd }); } } private static void ConfigFromEnv(HangfireHttpJobOptions settings) { var defaultRecurringQueueName = GetEnvConfig<string>("DefaultRecurringQueueName"); if (!string.IsNullOrEmpty(defaultRecurringQueueName)) { settings.DefaultRecurringQueueName = defaultRecurringQueueName; } if (settings.MailOption == null) settings.MailOption = new MailOption(); var hangfireMailServer = GetEnvConfig<string>("HangfireMail_Server"); if (!string.IsNullOrEmpty(hangfireMailServer)) { settings.MailOption.Server = hangfireMailServer; } var hangfireMailPort = GetEnvConfig<int>("HangfireMail_Port"); if (hangfireMailPort > 0) { settings.MailOption.Port = hangfireMailPort; } var hangfireMailUseSsl = Environment.GetEnvironmentVariable("HangfireMail_UseSsl"); if (!string.IsNullOrEmpty(hangfireMailUseSsl)) { settings.MailOption.UseSsl = hangfireMailUseSsl.ToLower().Equals("true"); } var hangfireMailUser = GetEnvConfig<string>("HangfireMail_User"); if (!string.IsNullOrEmpty(hangfireMailUser)) { settings.MailOption.User = hangfireMailUser; } var hangfireMailPassword = GetEnvConfig<string>("HangfireMail_Password"); if (!string.IsNullOrEmpty(hangfireMailPassword)) { settings.MailOption.Password = hangfireMailPassword; } } private static T GetEnvConfig<T>(string key) { try { var value = Environment.GetEnvironmentVariable(key.Replace(":", "_")); if (!string.IsNullOrEmpty(value)) { return (T)TypeConversionUtils.ConvertValueIfNecessary(typeof(T), value, null); } } catch (Exception e) { Console.WriteLine(e); } return default; } #endregion }
BearerAuthAuthorizationFilter.cs
public class BearerAuthAuthorizationFilter : IDashboardAuthorizationFilter { public bool Authorize([NotNull] DashboardContext dashbordContext) { var context=dashbordContext.GetHttpContext(); if(!context.User.Identity.IsAuthenticated) { string redirectUri = new UriBuilder(context.Request.Scheme, context.Request.Host.Host, context.Request.Host.Port ?? -1, "Home/Index").ToString(); context.Response.StatusCode = 302; context.Response.Redirect(redirectUri); return false; } return true; } }
HomeController.cs
[Authorize] public class HomeController: Controller { public IActionResult Index() { return Redirect("/job"); } }