package cc.mrbird.security.config;

import cc.mrbird.common.domain.FebsConstant;
import cc.mrbird.security.code.ValidateCodeGenerator;
import cc.mrbird.security.code.img.ImageCodeFilter;
import cc.mrbird.security.code.img.ImageCodeGenerator;
import cc.mrbird.security.code.sms.DefaultSmsSender;
import cc.mrbird.security.code.sms.SmsCodeFilter;
import cc.mrbird.security.code.sms.SmsCodeSender;
import cc.mrbird.security.handler.FebsAuthenticationAccessDeniedHandler;
import cc.mrbird.security.handler.FebsAuthenticationFailureHandler;
import cc.mrbird.security.handler.FebsAuthenticationSucessHandler;
import cc.mrbird.security.handler.FebsLogoutHandler;
import cc.mrbird.security.properties.FebsSecurityProperties;
import cc.mrbird.security.service.FebsUserDetailService;
import cc.mrbird.security.session.FebsExpiredSessionStrategy;
import cc.mrbird.security.session.FebsInvalidSessionStrategy;
import cc.mrbird.security.xss.XssFilter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.social.security.SpringSocialConfigurer;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * security 配置中心
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class FebsSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private FebsAuthenticationSucessHandler febsAuthenticationSucessHandler;

    @Autowired
    private FebsAuthenticationFailureHandler febsAuthenticationFailureHandler;

    @Autowired
    private FebsSecurityProperties febsSecurityProperties;

    @Autowired
    private FebsSmsCodeAuthenticationSecurityConfig febsSmsCodeAuthenticationSecurityConfig;

    @Autowired
    private FebsUserDetailService febsUserDetailService;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private SpringSocialConfigurer febsSocialSecurityConfig;

    // spring security自带的密码加密工具类
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 处理 rememberMe 自动登录认证
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        jdbcTokenRepository.setCreateTableOnStartup(false);
        return jdbcTokenRepository;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        String[] anonResourcesUrl = StringUtils.splitByWholeSeparatorPreserveAllTokens(febsSecurityProperties.getAnonResourcesUrl(),",");

        ImageCodeFilter imageCodeFilter = new ImageCodeFilter();
        imageCodeFilter.setAuthenticationFailureHandler(febsAuthenticationFailureHandler);
        imageCodeFilter.setSecurityProperties(febsSecurityProperties);
        imageCodeFilter.afterPropertiesSet();

        SmsCodeFilter smsCodeFilter = new SmsCodeFilter();
        smsCodeFilter.setAuthenticationFailureHandler(febsAuthenticationFailureHandler);
        smsCodeFilter.setSecurityProperties(febsSecurityProperties);
        smsCodeFilter.setSessionRegistry(sessionRegistry());
        smsCodeFilter.afterPropertiesSet();

        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler()) // 权限不足处理器
             .and()
                .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class) // 短信验证码校验
                .addFilterBefore(imageCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加图形证码校验过滤器
                .formLogin() // 表单方式
                .loginPage(febsSecurityProperties.getLoginUrl()) // 未认证跳转 URL
                .loginProcessingUrl(febsSecurityProperties.getCode().getImage().getLoginProcessingUrl()) // 处理登录认证 URL
                .successHandler(febsAuthenticationSucessHandler) // 处理登录成功
                .failureHandler(febsAuthenticationFailureHandler) // 处理登录失败
            .and()
                .rememberMe() // 添加记住我功能
                .tokenRepository(persistentTokenRepository()) // 配置 token 持久化仓库
                .tokenValiditySeconds(febsSecurityProperties.getRememberMeTimeout()) // rememberMe 过期时间,单为秒
                .userDetailsService(febsUserDetailService) // 处理自动登录逻辑
            .and()
                .sessionManagement() // 配置 session管理器
                .invalidSessionStrategy(invalidSessionStrategy()) // 处理 session失效
                .maximumSessions(febsSecurityProperties.getSession().getMaximumSessions()) // 最大并发登录数量
                .expiredSessionStrategy(new FebsExpiredSessionStrategy()) // 处理并发登录被踢出
                .sessionRegistry(sessionRegistry()) // 配置 session注册中心
            .and()
            .and()
                .logout() // 配置登出
                .addLogoutHandler(logoutHandler()) // 配置登出处理器
                .logoutUrl(febsSecurityProperties.getLogoutUrl()) // 处理登出 url
                .logoutSuccessUrl("/") // 登出后跳转到 /
                .deleteCookies("JSESSIONID") // 删除 JSESSIONID
            .and()
                .authorizeRequests() // 授权配置
                .antMatchers(anonResourcesUrl).permitAll() // 免认证静态资源路径
                .antMatchers(
                        febsSecurityProperties.getLoginUrl(), // 登录路径
                        FebsConstant.FEBS_REGIST_URL, // 用户注册 url
                        febsSecurityProperties.getCode().getImage().getCreateUrl(), // 创建图片验证码路径
                        febsSecurityProperties.getCode().getSms().getCreateUrl(), // 创建短信验证码路径
                        febsSecurityProperties.getSocial().getSocialRedirectUrl(), // 重定向到社交账号注册(绑定)页面路径
                        febsSecurityProperties.getSocial().getSocialBindUrl(), // 社交账号绑定 URL
                        febsSecurityProperties.getSocial().getSocialRegistUrl() // 注册并绑定社交账号 URL
                ).permitAll() // 配置免认证路径
                .anyRequest()  // 所有请求
                .authenticated() // 都需要认证
            .and()
                .csrf().disable()
                .apply(febsSmsCodeAuthenticationSecurityConfig) // 添加短信验证码认证流程
            .and()
                .apply(febsSocialSecurityConfig); // social 配置
    }


    @Bean
    @ConditionalOnMissingBean(name = "imageCodeGenerator")
    public ValidateCodeGenerator imageCodeGenerator() {
        ImageCodeGenerator imageCodeGenerator = new ImageCodeGenerator();
        imageCodeGenerator.setSecurityProperties(febsSecurityProperties);
        return imageCodeGenerator;
    }

    @Bean
    @ConditionalOnMissingBean(SmsCodeSender.class)
    public SmsCodeSender smsCodeSender() {
        return new DefaultSmsSender();
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    // 使用 javaconfig 的方式配置是为了注入 sessionRegistry
    @Bean
    public FebsAuthenticationSucessHandler febsAuthenticationSucessHandler() {
        FebsAuthenticationSucessHandler authenticationSucessHandler = new FebsAuthenticationSucessHandler();
        authenticationSucessHandler.setSessionRegistry(sessionRegistry());
        return authenticationSucessHandler;
    }

    // 配置登出处理器
    @Bean
    public LogoutHandler logoutHandler(){
        FebsLogoutHandler febsLogoutHandler = new FebsLogoutHandler();
        febsLogoutHandler.setSessionRegistry(sessionRegistry());
        return febsLogoutHandler;
    }

    @Bean
    public InvalidSessionStrategy invalidSessionStrategy(){
        FebsInvalidSessionStrategy febsInvalidSessionStrategy = new FebsInvalidSessionStrategy();
        febsInvalidSessionStrategy.setSecurityProperties(febsSecurityProperties);
        return febsInvalidSessionStrategy;
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return new FebsAuthenticationAccessDeniedHandler();
    }

    /**
     * XssFilter Bean
     */
    @Bean
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public FilterRegistrationBean xssFilterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new XssFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.setEnabled(true);
        filterRegistrationBean.addUrlPatterns("/*");
        Map<String, String> initParameters = new HashMap<>();
        initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*");
        initParameters.put("isIncludeRichText", "true");
        filterRegistrationBean.setInitParameters(initParameters);
        return filterRegistrationBean;
    }

}