Exemplo n.º 1
0
        public async Task <ApiResultUrl> Logout()
        {
            var userId = HttpContext.User.GetUserId();

            if (userId >= 0)
            {
                await _tokenService.RevokeRefreshTokenAsync(userId);

                await _userService.SignOutAsync(userId);

                await _userActionLogService.SaveAsync(new UserActionLogInput
                {
                    UserId       = userId,
                    ActionTypeId = 2,
                    ClientTypeId = null, // TODO: (alby)ClientTypeInput
                    ClientAgent  = null,
                    Remark       = "后台注销"
                }, ModelState);
            }

            var result = new ApiResultUrl
            {
                Code    = 200,
                Message = "注销成功",
                Url     = _frontendSettings.CoreEnvironment.IsDevelopment ? _frontendSettings.CoreEnvironment.DevelopmentHost + "/modules/login.html" : Url.Action("Login", "View"),
            };

            return(result);
        }
Exemplo n.º 2
0
        public async Task <ApiResultUrl> ChangeUserLogo([FromForm] UserImageInput userImageInput)
        {
            var result = new ApiResultUrl();
            var url    = await _userService.ChangeLogoAsync(userImageInput, ModelState);

            if (!ModelState.IsValid)
            {
                result.Code    = 400;
                result.Message = $"修改 Logo 失败:{ModelState.FirstErrorMessage()}";
                return(result);
            }

            result.Url     = url;
            result.Code    = 200;
            result.Message = "修改 Logo 成功";
            return(result);
        }
Exemplo n.º 3
0
        public async Task <ApiResultUrl> ChangeAvatar(IFormFile file)
        {
            var result = new ApiResultUrl();
            var url    = await _userService.ChangeAvatarAsync(HttpContext.User.GetUserId(), file, ModelState);

            if (!ModelState.IsValid)
            {
                result.Code    = 400;
                result.Message = $"修改头像失败:{ModelState.FirstErrorMessage()}";
                return(result);
            }

            result.Url     = url;
            result.Code    = 200;
            result.Message = "修改头像成功";
            return(result);
        }
Exemplo n.º 4
0
        public async Task <ApiResultUrl> Logout()
        {
            var userId = HttpContext.User.GetUserId();

            if (userId >= 0)
            {
                await _tokenService.RevokeRefreshTokenAsync(userId);

                await _userService.SignOutAsync(userId);
            }
            var result = new ApiResultUrl
            {
                Code    = 200,
                Message = "注销成功",
                Url     = _frontendSettings.CoreEnvironment.IsDevelopment ? _frontendSettings.CoreEnvironment.DevelopmentHost + "/modules/login.html" : Url.Action("Login", "View"),
            };

            return(result);
        }
Exemplo n.º 5
0
        /// <summary>
        /// ConfigureServices
        /// </summary>
        /// <param name="services"></param>
        public override void ConfigureServices(IServiceCollection services)
        {
            // Background Service
            services.AddSingleton <IBackgroundTask, IdleBackgroundTask>();

            // Cache
            services.AddDistributedRedisCache(options =>
            {
                options.Configuration = "localhost";
                options.InstanceName  = _environment.ApplicationName + ":";
            });

            services.AddMemoryCache();

            // Cors
            services.AddCors(options => options.AddPolicy("DefaultPolicy",
                                                          builder => builder.WithOrigins("http://localhost:9090", "http://localhost:8080").AllowAnyMethod().AllowAnyHeader().AllowCredentials())
                             // builder => builder.AllowAnyOrigin.AllowAnyMethod().AllowAnyHeader().AllowCredentials())
                             );

            // Cookie
            services.Configure <CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded    = context => false; // 需保持为 false, 否则 Web API 不会 Set-Cookie 。
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            // Session
            services.AddSession(options =>
            {
                options.IdleTimeout     = TimeSpan.FromMinutes(10);
                options.Cookie.Name     = ".Tubumu.Session";
                options.Cookie.HttpOnly = true;
            });

            // HTTP Client
            services.AddHttpClient();

            // ApiBehaviorOptions
            services.Configure <ApiBehaviorOptions>(options =>
            {
                options.InvalidModelStateResponseFactory = context => new OkObjectResult(new ApiResult
                {
                    Code    = 400,
                    Message = context.ModelState.FirstErrorMessage()
                });
            });

            // Authentication
            var registeredServiceDescriptor = services.FirstOrDefault(s => s.Lifetime == ServiceLifetime.Transient && s.ServiceType == typeof(IApplicationModelProvider) && s.ImplementationType == typeof(AuthorizationApplicationModelProvider));

            if (registeredServiceDescriptor != null)
            {
                services.Remove(registeredServiceDescriptor);
            }
            services.AddTransient <IApplicationModelProvider, PermissionAuthorizationApplicationModelProvider>();

            services.AddSingleton <ITokenService, TokenService>();
            var tokenValidationSettings = _configuration.GetSection("TokenValidationSettings").Get <TokenValidationSettings>();

            services.AddSingleton(tokenValidationSettings);
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidIssuer    = tokenValidationSettings.ValidIssuer,
                    ValidateIssuer = true,

                    ValidAudience    = tokenValidationSettings.ValidAudience,
                    ValidateAudience = true,

                    IssuerSigningKey         = SignatureHelper.GenerateSigningKey(tokenValidationSettings.IssuerSigningKey),
                    ValidateIssuerSigningKey = tokenValidationSettings.ValidateLifetime,

                    ValidateLifetime = true,
                    ClockSkew        = TimeSpan.FromSeconds(tokenValidationSettings.ClockSkewSeconds),
                };

                // We have to hook the OnMessageReceived event in order to
                // allow the JWT authentication handler to read the access
                // token from the query string when a WebSocket or
                // Server-Sent Events request comes in.
                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = context =>
                    {
                        var accessToken = context.Request.Query["access_token"];

                        // If the request is for our hub...
                        var path = context.HttpContext.Request.Path;
                        if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
                        {
                            // Read the token out of the query string
                            context.Token = accessToken;
                        }
                        return(Task.CompletedTask);
                    },
                    OnAuthenticationFailed = context =>
                    {
                        _logger.LogError($"Authentication Failed(OnAuthenticationFailed): {context.Request.Path} Error: {context.Exception}");
                        if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                        {
                            context.Response.Headers.Add("Token-Expired", "true");
                        }
                        return(Task.CompletedTask);
                    },
                    OnChallenge = context =>
                    {
                        _logger.LogError($"Authentication Challenge(OnChallenge): {context.Request.Path}");

                        // TODO: (alby)为不同客户端返回不同的内容
                        var result = new ApiResultUrl()
                        {
                            Code    = 400,
                            Message = "Authentication Challenge",
                            Url     = _environment.IsProduction() ? tokenValidationSettings.LoginUrl : null,
                        };
                        var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(result));
                        context.Response.StatusCode  = StatusCodes.Status401Unauthorized;
                        context.Response.ContentType = "application/json";
                        context.Response.Body.Write(body, 0, body.Length);
                        context.HandleResponse();
                        return(Task.CompletedTask);
                    }
                };
            });

            // JSON Date format
            void JsonSetup(MvcJsonOptions options) => options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";

            services.Configure((Action <MvcJsonOptions>)JsonSetup);

            // SignalR
            services.AddSignalR();
            services.Replace(ServiceDescriptor.Singleton(typeof(IUserIdProvider), typeof(NameUserIdProvider)));

            // AutoMapper
            services.AddAutoMapper();
            Initalizer.Initialize();

            // Swagger
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info {
                    Title = _environment.ApplicationName + " API", Version = "v1"
                });
                c.AddSecurityDefinition("Bearer", new ApiKeyScheme
                {
                    Description = "权限认证(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
                    Name        = "Authorization",
                    In          = "header",
                    Type        = "apiKey"
                });
                var security = new Dictionary <string, IEnumerable <string> >
                {
                    { "Bearer", new string[] { } },
                };
                c.AddSecurityRequirement(security);
                c.DescribeAllEnumsAsStrings();
                c.DocumentFilter <HiddenApiDocumentFilter>();
                IncludeXmlCommentsForModules(c);
                c.OrderActionsBy(m => m.ActionDescriptor.DisplayName);
            });
        }
Exemplo n.º 6
0
        /// <summary>
        /// ConfigureServices
        /// </summary>
        /// <param name="services"></param>
        public override void ConfigureServices(IServiceCollection services)
        {
            // Background Service
            services.AddSingleton <IBackgroundTask, IdleBackgroundTask>();
            services.AddSingleton <IBackgroundTask, DailyBackgroundTask>();

            // StackExchange.Redis.Extensions
            // https://github.com/imperugo/StackExchange.Redis.Extensions
            var redisConfiguration = _configuration.GetSection("RedisSettings").Get <RedisConfiguration>();

            services.AddSingleton(redisConfiguration);
            services.AddSingleton <IRedisCacheClient, RedisCacheClient>();
            services.AddSingleton <IRedisCacheConnectionPoolManager, RedisCacheConnectionPoolManager>();
            services.AddSingleton <IRedisDefaultCacheClient, RedisDefaultCacheClient>();
            services.AddSingleton <ISerializer, NewtonsoftSerializer>();
            var redisKeyPrefix = !redisConfiguration.KeyPrefix.IsNullOrWhiteSpace() ? redisConfiguration.KeyPrefix : _environment.ApplicationName;

            // Cache
            services.AddDistributedRedisCache(options =>
            {
                options.Configuration = "localhost";
                options.InstanceName  = redisKeyPrefix + ":";
            });
            services.AddMemoryCache();

            // Cors
            services.AddCors(options => options.AddPolicy("DefaultPolicy",
                                                          builder => builder.WithOrigins("http://localhost:9090", "http://localhost:8080").AllowAnyMethod().AllowAnyHeader().AllowCredentials())
                             // builder => builder.AllowAnyOrigin.AllowAnyMethod().AllowAnyHeader().AllowCredentials())
                             );

            // Cookie
            services.Configure <CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded    = context => false; // 需保持为 false, 否则 Web API 不会 Set-Cookie 。
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            // Session
            services.AddSession(options =>
            {
                options.IdleTimeout     = TimeSpan.FromMinutes(10);
                options.Cookie.Name     = ".Tubumu.Session";
                options.Cookie.HttpOnly = true;
            });

            // HTTP Client
            services.AddHttpClient();

            // ApiBehaviorOptions
            services.Configure <ApiBehaviorOptions>(options =>
            {
                options.InvalidModelStateResponseFactory = context => new OkObjectResult(new ApiResult
                {
                    Code    = 400,
                    Message = context.ModelState.FirstErrorMessage()
                });
            });

            // Authentication
            services.AddSingleton <IAuthorizationPolicyProvider, TubumuAuthorizationPolicyProvider>();

            services.AddSingleton <ITokenService, TokenService>();
            var tokenValidationSettings = _configuration.GetSection("TokenValidationSettings").Get <TokenValidationSettings>();

            services.AddSingleton(tokenValidationSettings);
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidIssuer    = tokenValidationSettings.ValidIssuer,
                    ValidateIssuer = true,

                    ValidAudience    = tokenValidationSettings.ValidAudience,
                    ValidateAudience = true,

                    IssuerSigningKey         = SignatureHelper.GenerateSigningKey(tokenValidationSettings.IssuerSigningKey),
                    ValidateIssuerSigningKey = true,

                    ValidateLifetime = tokenValidationSettings.ValidateLifetime,
                    ClockSkew        = TimeSpan.FromSeconds(tokenValidationSettings.ClockSkewSeconds),
                };

                // We have to hook the OnMessageReceived event in order to
                // allow the JWT authentication handler to read the access
                // token from the query string when a WebSocket or
                // Server-Sent Events request comes in.
                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = context =>
                    {
                        var accessToken = context.Request.Query["access_token"];

                        // If the request is for our hub...
                        var path = context.HttpContext.Request.Path;
                        if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
                        {
                            // Read the token out of the query string
                            context.Token = accessToken;
                        }
                        return(Task.CompletedTask);
                    },
                    OnAuthenticationFailed = context =>
                    {
                        _logger.LogError($"Authentication Failed(OnAuthenticationFailed): {context.Request.Path} Error: {context.Exception}");
                        if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                        {
                            context.Response.Headers.Add("Token-Expired", "true");
                        }
                        return(Task.CompletedTask);
                    },
                    OnChallenge = context =>
                    {
                        _logger.LogError($"Authentication Challenge(OnChallenge): {context.Request.Path}");

                        // TODO: (alby)为不同客户端返回不同的内容
                        var result = new ApiResultUrl()
                        {
                            Code    = 400,
                            Message = "Authentication Challenge",
                            // TODO: (alby)前端 IsDevelopment 为 true 时不返回 Url
                            Url = _environment.IsProduction() ? tokenValidationSettings.LoginUrl : null,
                        };
                        var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(result));
                        context.Response.StatusCode  = StatusCodes.Status401Unauthorized;
                        context.Response.ContentType = "application/json";
                        context.Response.Body.Write(body, 0, body.Length);
                        context.HandleResponse();
                        return(Task.CompletedTask);
                    }
                };
            });

            // JSON Date format
            void JsonSetup(MvcJsonOptions options) => options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";

            services.Configure((Action <MvcJsonOptions>)JsonSetup);

            // SignalR
            services.AddSignalR();
            services.Replace(ServiceDescriptor.Singleton(typeof(IUserIdProvider), typeof(NameUserIdProvider)));

            // AutoMapper
            services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
            AutoMapperInitalizer.Initialize();

            // RabbitMQ
            services.AddSingleton <IConnectionPool, ConnectionPool>();
            services.AddSingleton <IChannelPool, ChannelPool>();

            // Swagger
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1.0", new Info {
                    Title = _environment.ApplicationName + " API", Version = "v1.0"
                });
                c.AddSecurityDefinition("Bearer", new ApiKeyScheme
                {
                    Description = "权限认证(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
                    Name        = "Authorization",
                    In          = "header",
                    Type        = "apiKey"
                });
                c.AddSecurityRequirement(new Dictionary <string, IEnumerable <string> >
                {
                    { "Bearer", new string[] { } },
                });
                c.DescribeAllEnumsAsStrings();
                c.DocumentFilter <HiddenApiDocumentFilter>();
                IncludeXmlCommentsForModules(c);
                c.OrderActionsBy(m => m.ActionDescriptor.DisplayName);
                c.ApplyGrouping();
            });

            // Add Hangfire services.
            //services.AddHangfire(configuration => configuration
            //    .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
            //    .UseSimpleAssemblyNameTypeSerializer()
            //    .UseRecommendedSerializerSettings()
            //    .UseSqlServerStorage(_configuration.GetConnectionString("Tubumu"), new SqlServerStorageOptions
            //    {
            //        CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
            //        SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
            //        QueuePollInterval = TimeSpan.FromSeconds(15),           // 作业队列轮询间隔。默认值为15秒。
            //        JobExpirationCheckInterval = TimeSpan.FromHours(1),     // 作业到期检查间隔(管理过期记录)。默认值为1小时。
            //        UseRecommendedIsolationLevel = true,
            //        UsePageLocksOnDequeue = true,
            //        DisableGlobalLocks = true
            //    }));
            services.AddHangfire(configuration =>
            {
                // 推荐使用 ConnectionMultiplexer,见:https://github.com/marcoCasamento/Hangfire.Redis.StackExchange 。
                // 但是存在 StackExchange.Redis.StrongName 和 StackExchange.Redis 冲突问题。
                configuration.UseRedisStorage("localhost", new RedisStorageOptions
                {
                    Prefix = $"{redisKeyPrefix}:hangfire:",
                    Db     = 9,
                });
            });

            // Add the processing server as IHostedService
            services.AddHangfireServer();

            // Data version
            services.AddSingleton <IDataVersionService, DataVersionService>();
        }