package com.artfess.base.conf;

import com.artfess.base.filter.JwtAuthorizationTokenFilter;
import com.artfess.base.jwt.JwtAuthenticationEntryPoint;
import com.artfess.base.jwt.JwtTokenHandler;
import com.artfess.base.security.CustomAccessDeniedHandler;
import com.artfess.base.security.CustomPwdEncoder;
import com.artfess.base.security.HtDecisionManager;
import com.artfess.base.security.HtFilterSecurityInterceptor;
import com.artfess.base.util.StringUtil;
import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import javax.annotation.Resource;
import java.util.List;


@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
    @Resource
    UserDetailsService userDetailsService;
    @Resource
    JwtTokenHandler jwtTokenHandler;
    @Resource
    JwtConfig jwtConfig;
    @Value("${feign.encry.key:feignCallEncry}")
    private String encryKey;
    @Value("${artfess.security.ignore.httpUrls:''}")
    String permitAll;
    @Value("${artfess.security.deny.httpUrls:''}")
    String denyAll;
    @Value("${artfess.security.pswd.encoder:}")
    String passwordEncoder;
    @Value("${cors.enable:true}")
    Boolean corsEnable;
    @Resource
    JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Resource
    HtFilterSecurityInterceptor htFilterSecurityInterceptor;
    @Resource
    CustomAccessDeniedHandler customAccessDeniedHandler;
    @Value("${webjar.context:mvue,fvue,mobilevue}")
    private List<String> resourceContext;

//	@Resource
//	List<WebSecurityExtend> webExtends;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(getCustomPasswordEncoder());
    }

    /**
     * 获取配置的自定义密码加密类
     *
     * @return
     */
    public PasswordEncoder getCustomPasswordEncoder() {
        CustomPwdEncoder encoder = (CustomPwdEncoder) defaultPasswordEncoderBean();
        if (StringUtil.isNotEmpty(passwordEncoder)) {
            try {
                logger.info("Use config password encoder : " + passwordEncoder);
                PasswordEncoder delegate = (PasswordEncoder) Class.forName(passwordEncoder).newInstance();
                encoder.setDelegateEncoder(delegate);
            } catch (Exception e) {
                logger.error("Create custom password encoder config class[" + passwordEncoder + "] failed.");
            }
        }
        return encoder;
    }

    @Bean
    public WebSecurityExtend emptyExtend() {
        return new WebSecurityEmptyExtend();
    }

    @Bean
    public PasswordEncoder defaultPasswordEncoderBean() {
        return new CustomPwdEncoder();
    }

    // 注册后台权限控制器
    @Bean
    public AccessDecisionManager accessDecisionManager() {
        return new HtDecisionManager();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        String[] permitAlls = new String[]{};
        String[] denyAlls = new String[]{};
        if (StringUtil.isNotEmpty(permitAll)) {
            permitAlls = permitAll.split(",");
        }
        // 将webjar中的resource资源添加到可匿名访问中
        for (String rc : resourceContext) {
            permitAlls = (String[]) ArrayUtils.add(permitAlls, String.format("/%s/**", rc));
        }

        if (StringUtil.isNotEmpty(denyAll)) {
            denyAlls = denyAll.split(",");
        }
        httpSecurity
                // we don't need CSRF because our token is invulnerable
                .csrf().disable()
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
                .exceptionHandling().accessDeniedHandler(customAccessDeniedHandler).and()
                // don't create session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .antMatchers(permitAlls).permitAll()
                .antMatchers(denyAlls).denyAll()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .antMatchers("/auth/**").permitAll()
                .antMatchers("/static/**").permitAll()
                .antMatchers("/ueditor/**").permitAll()
                .antMatchers("/bizAPP/v1/appDownload").permitAll()
                .anyRequest().authenticated()
                .accessDecisionManager(accessDecisionManager());

        // Custom JWT based security filter
        JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenHandler, jwtConfig.getHeader());
        authenticationTokenFilter.setEncryKey(encryKey);
        httpSecurity
                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        httpSecurity
                .addFilterBefore(htFilterSecurityInterceptor, FilterSecurityInterceptor.class);
        httpSecurity
                .addFilterBefore(corsFilter(), ChannelProcessingFilter.class);

        // add custom filter
//		for (WebSecurityExtend extend : webExtends) {
//			for (Map.Entry<Class<? extends Filter>, Filter> entry : extend.getCustomBeforeFilter().entrySet()) {
//				httpSecurity.addFilterBefore(entry.getValue(), entry.getKey());
//			}
//		}

        // disable page caching
        httpSecurity
                .headers()
                .frameOptions().sameOrigin()  // required to set for H2 else H2 Console will be blank.
                .cacheControl();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // AuthenticationTokenFilter will ignore the below paths
        web
                .ignoring()
                .antMatchers(
                        HttpMethod.POST,
                        "/auth",
                        "/error",
                        "/sys/sysLogs/v1/loginLogs",
                        "/sys/sysLogs/v1/saveLogs",
                        "/api/user/v1/user/loadUserByUsername",
                        "/actuator/cert",
                        "/uc/AuthorizationModel/v1/downloadFileLic",
                        "/uc/AuthorizationModel/v1/uploadAuthorizationFile",
                        "/form/formServiceController/v1/getFormAndBoExportXml",
                        "/server/**",
                        "/bizAccessory"

                )
                .antMatchers(
                        HttpMethod.GET,
                        "/sso/**",
                        "/sys/sysLogsSettings/v1/getSysLogsSettingStatusMap",
                        "/sys/sysRoleAuth/v1/getMethodRoleAuth",
                        "/file/v1/getLogoFile",
                        /*"/flow/bpmTaskReminder/v1/executeTaskReminderJob",*/
                        "/flow/def/v1/bpmnXml",
                        "/file/onlinePreviewController/v1/getFileByPathAndId**",
                        "/file/onlinePreviewController/v1/getFileById**",
                        "/portal/main/v1/appProperties",
                        "/sys/sysProperties/v1/getByAlias",
                        "/uc/tenantManage/v1/getTenantByCode",
                        "/sys/sysProperties/v1/getDecryptBySysSetting",
                        "/portal/shorturlManage/v1/getLongUrlByShortUrl",
                        "/file/v1/downloadFile",
                        //"/websocket/**",
                        "/jmreport/**",
                        "/interface-ui/**",
                        "/dataway/api/v1/**",
                        "/server/**",
                        "/bizAccessory"
                )
                // allow anonymous resource requests
                .and()
                .ignoring()
                .antMatchers(
                        HttpMethod.GET,
                        "/",
                        "/error",
                        "/*.jpg",
                        "/*.gif",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/**/image",
                        "/**/json",
                        "/**/ftl",
                        "/interface-ui/**",
                        "/jmreport/**",
                        "/**/*.xlsx"
                )
                .and()
                .ignoring()
                .antMatchers("/v2/api-docs",
                        "/swagger-resources/configuration/ui",
                        "/swagger-resources",
                        "/swagger-resources/configuration/security",
                        "/swagger-ui.html",
                        "/proxy.stream",
                        "/hystrix.stream",
                        "/druid/**",
                        "/server/**",
                        "/hystrix/**",
                        "/actuator/**",
                        "/interface-ui/**",
                        "/service/**",
                        "/jmreport/**");

        // 添加扩展的路径过滤
//		for (WebSecurityExtend extend : webExtends) {
//			web
//			.ignoring()
//			.antMatchers(HttpMethod.POST, extend.getIgnoringPostUrl())
//			.and()
//			.ignoring()
//			.antMatchers(HttpMethod.GET, extend.getIgnoringGetUrl())
//			.and()
//			.ignoring()
//			.antMatchers(extend.getIgnoringUrl());
//		}
    }

    /**
     * 允许跨域访问的源
     *
     * @return
     */
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        if (corsEnable) {
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            corsConfiguration.addAllowedOrigin("*");
            corsConfiguration.addAllowedHeader("*");
            corsConfiguration.addAllowedMethod("*");
            source.registerCorsConfiguration("/**", corsConfiguration);
        }
        return new CorsFilter(source);
    }

    @Bean
    public HtFilterSecurityInterceptor htFilterSecurityInterceptor(AccessDecisionManager accessDecisionManager) throws Exception {
        HtFilterSecurityInterceptor htFilterSecurityInterceptor = new HtFilterSecurityInterceptor();
        htFilterSecurityInterceptor.setAccessDecisionManager(accessDecisionManager);
        return htFilterSecurityInterceptor;
    }
}
