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");
        }
    }

 

posted @ 2023-04-13 16:07  hello_stone  阅读(149)  评论(0)    收藏  举报