package cn.reghao.oss.console.account.security;

import cn.reghao.oss.console.account.security.filter.LoginRedirectFilter;
import cn.reghao.oss.console.account.security.form.AccountAuthFilter;
import cn.reghao.oss.console.account.security.form.AccountAuthProvider;
import cn.reghao.oss.console.account.service.AccountAuthService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.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.session.SessionRegistry;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.session.*;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;

import java.util.ArrayList;
import java.util.List;

/**
 * Web 应用安全配置
 *
 * @author reghao
 * @date 2019-05-20 10:41:10
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 调用方法时检查权限
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    private final String loginPage = "/login";
    private final String loginApi = "/login";
    private final String logoutApi = "/logout";

    private final AccountAuthProvider userAuthProvider;
    private final AccountAuthService accountAuthService;
    private final AuthenticationSuccessHandler successHandler;
    private final AuthenticationFailureHandler failureHandler;
    private final LogoutHandler logoutHandler;
    private final LogoutSuccessHandler logoutSuccessHandler;
    private final SessionRegistry sessionRegistry;

    public WebSecurityConfig(AccountAuthProvider userAuthProvider, AccountAuthService accountAuthService,
                             AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler,
                             LogoutHandler logoutHandler, LogoutSuccessHandler logoutSuccessHandler,
                             SessionRegistry sessionRegistry) {
        this.userAuthProvider = userAuthProvider;
        this.accountAuthService = accountAuthService;
        this.successHandler = successHandler;
        this.failureHandler = failureHandler;
        this.logoutHandler = logoutHandler;
        this.logoutSuccessHandler = logoutSuccessHandler;
        this.sessionRegistry = sessionRegistry;
    }

    /**
     * 放行静态资源
     *
     * @param
     * @return
     * @date 2021-04-04 下午9:06
     */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring()
                .antMatchers("/ws/**")
                .antMatchers("/css/**")
                .antMatchers("/imgs/**")
                .antMatchers("/js/**")
                .antMatchers("/lib/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                // 放行匹配前缀的请求
                .antMatchers("/api/account/code/**").permitAll()
                .antMatchers("/api/oss/**").permitAll()
                .anyRequest().authenticated();

        // 配置 FilterChainProxy 过滤器链
        http.addFilterAfter(new LoginRedirectFilter(), SecurityContextPersistenceFilter.class);
        http.addFilterBefore(accountAuthFilter(sessionRegistry), UsernamePasswordAuthenticationFilter.class);

        // 禁用 UsernamePasswordAuthenticationFilter, 使用自定义的 AccountAuthFilter
        http.formLogin().disable();

        // 配置 LogoutFilter
        http.logout()
                .logoutUrl(logoutApi)
                .addLogoutHandler(logoutHandler)
                .logoutSuccessHandler(logoutSuccessHandler);

        // 配置 ExceptionTranslationFilter, 登录认证接口失败时的处理, 不会重定向到 loginPage
        http.exceptionHandling()
                .authenticationEntryPoint(new ExceptionAuthenticationEntryPoint());

        // 配置 SessionManagementFilter 和 ConcurrentSessionFilter
        http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                //.sessionAuthenticationStrategy(new MySessionAuthenticationStrategy(sessionRegistry()));
                .maximumSessions(1)
                .expiredUrl(loginPage);

        // 配置 RememberMeAuthenticationFilter, 禁用 RememberMeAuthenticationFilter
        http.rememberMe().disable();
                /*.key("DExNzAyNTQ2Nzo3NDI3MTNhYmM5MGE5")
                .rememberMeParameter("rememberMe");*/

        // 配置 CorsFilter, 禁用 CorsFilter
        http.cors().disable();

        // 配置 CsrfFilter, 禁用 CsrfFilter
        http.csrf().disable();

        // 配置 HeaderWriterFilter, 禁用 HeaderWriterFilter
        http.headers().disable();
    }

    /**
     * 配置认证管理器
     *
     * @param
     * @return
     * @date 2021-07-25 下午2:28
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 配置认证使用的 provider
     *
     * @param
     * @return
     * @date 2021-07-25 下午2:31
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(userAuthProvider);
    }

    /**
     * 配置账号密码登录 filter
     *
     * @param
     * @return
     * @date 2022-07-06 上午9:54
     */
    @Bean
    public AccountAuthFilter accountAuthFilter(SessionRegistry sessionRegistry) throws Exception {
        AccountAuthFilter filter = new AccountAuthFilter(loginApi, "POST", accountAuthService);
        filter.setAuthenticationManager(super.authenticationManager());
        filter.setAuthenticationSuccessHandler(successHandler);
        filter.setAuthenticationFailureHandler(failureHandler);
        filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy(sessionRegistry));
        return filter;
    }

    public CompositeSessionAuthenticationStrategy sessionAuthenticationStrategy(SessionRegistry sessionRegistry){
        ConcurrentSessionControlAuthenticationStrategy controlAuthenticationStrategy =
                new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
        // 设置帐号同时登录的最大数量
        controlAuthenticationStrategy.setMaximumSessions(1);
        //controlAuthenticationStrategy.setExceptionIfMaximumExceeded(true);

        List<SessionAuthenticationStrategy> authenticationStrategies = new ArrayList<>();
        authenticationStrategies.add(controlAuthenticationStrategy);
        authenticationStrategies.add(new SessionFixationProtectionStrategy());
        authenticationStrategies.add(new RegisterSessionAuthenticationStrategy(sessionRegistry));

        return new CompositeSessionAuthenticationStrategy(authenticationStrategies);
    }

    /**
     * 角色继承
     * ADMIN 可以访问 USER 的权限，反之不可
     *
     * @date 2019-07-05 上午11:18
     */
    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
        return roleHierarchy;
    }
}
